From d0923b845b13617d5877d58a85c6e25f89ebf81e Mon Sep 17 00:00:00 2001 From: Albert Vaca Cintora Date: Fri, 2 Jun 2023 19:04:49 +0200 Subject: [PATCH] Have a single PairingHandler for all links --- src/org/kde/kdeconnect/Backends/BaseLink.java | 3 +- .../Backends/BasePairingHandler.java | 68 ----- .../BluetoothBackend/BluetoothLink.java | 6 - .../BluetoothPairingHandler.java | 181 ------------ .../Backends/LanBackend/LanLink.java | 6 - .../Backends/LanBackend/LanLinkProvider.java | 1 - .../LanBackend/LanPairingHandler.java | 202 ------------- .../LoopbackBackend/LoopbackLink.java | 6 - .../LoopbackPairingHandler.java | 50 ---- src/org/kde/kdeconnect/BackgroundService.java | 1 + src/org/kde/kdeconnect/Device.java | 274 ++++++------------ src/org/kde/kdeconnect/DevicePacketQueue.java | 1 + src/org/kde/kdeconnect/KdeConnect.java | 5 +- .../UserInterface/DeviceFragment.kt | 5 +- .../UserInterface/PairingHandler.java | 203 +++++++++++++ tests/org/kde/kdeconnect/DeviceTest.java | 69 +---- 16 files changed, 301 insertions(+), 780 deletions(-) delete mode 100644 src/org/kde/kdeconnect/Backends/BasePairingHandler.java delete mode 100644 src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java delete mode 100644 src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java delete mode 100644 src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java create mode 100644 src/org/kde/kdeconnect/UserInterface/PairingHandler.java diff --git a/src/org/kde/kdeconnect/Backends/BaseLink.java b/src/org/kde/kdeconnect/Backends/BaseLink.java index 0f974eb0..df4991f2 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLink.java +++ b/src/org/kde/kdeconnect/Backends/BaseLink.java @@ -30,14 +30,13 @@ public abstract class BaseLink { private final ArrayList receivers = new ArrayList<>(); protected BaseLink(@NonNull Context context, @NonNull String deviceId, @NonNull BaseLinkProvider linkProvider) { - this.context = context; + this.context = context; this.linkProvider = linkProvider; this.deviceId = deviceId; } /* To be implemented by each link for pairing handlers */ public abstract String getName(); - public abstract BasePairingHandler getPairingHandler(@NonNull Device device, @NonNull BasePairingHandler.PairingHandlerCallback callback); public String getDeviceId() { return deviceId; diff --git a/src/org/kde/kdeconnect/Backends/BasePairingHandler.java b/src/org/kde/kdeconnect/Backends/BasePairingHandler.java deleted file mode 100644 index 9a513c70..00000000 --- a/src/org/kde/kdeconnect/Backends/BasePairingHandler.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015 Vineet Garg - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Backends; - -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.NetworkPacket; - -/** - * This class separates the pairing interface for each type of link. - * Since different links can pair via different methods, like for LanLink certificate and public key should be shared, - * for Bluetooth link they should be paired via bluetooth etc. - * Each "Device" instance maintains a hash map for these pairing handlers so that there can be single pairing handler per - * per link type per device. - * Pairing handler keeps information about device, latest link, and pair status of the link - * During first pairing process, the pairing process is nearly same as old process. - * After that if any one of the link is paired, then we can say that device is paired, so new link will pair automatically - */ - -public abstract class BasePairingHandler { - - protected enum PairStatus{ - NotPaired, - Requested, - RequestedByPeer, - Paired - } - - public interface PairingHandlerCallback { - void incomingRequest(); - void pairingDone(); - void pairingFailed(String error); - void unpaired(); - } - - - protected final Device mDevice; - protected PairStatus mPairStatus; - protected final PairingHandlerCallback mCallback; - - protected BasePairingHandler(Device device, PairingHandlerCallback callback) { - this.mDevice = device; - this.mCallback = callback; - } - - protected boolean isPaired() { - return mPairStatus == PairStatus.Paired; - } - - public boolean isPairRequested() { - return mPairStatus == PairStatus.Requested; - } - - public boolean isPairRequestedByPeer() { - return mPairStatus == PairStatus.RequestedByPeer; - } - - /* To be implemented by respective pairing handler */ - public abstract void packetReceived(NetworkPacket np); - public abstract void requestPairing(); - public abstract void acceptPairing(); - public abstract void cancelPairing(); - public abstract void unpair(); - -} diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java index fa72872d..ce6af007 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java @@ -16,7 +16,6 @@ import androidx.annotation.WorkerThread; import org.json.JSONException; import org.json.JSONObject; import org.kde.kdeconnect.Backends.BaseLink; -import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPacket; @@ -112,11 +111,6 @@ public class BluetoothLink extends BaseLink { return "BluetoothLink"; } - @Override - public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) { - return new BluetoothPairingHandler(device, callback); - } - public void disconnect() { if (connection == null) { return; diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java deleted file mode 100644 index 65d6ad66..00000000 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015 Vineet Garg - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Backends.BluetoothBackend; - -import android.util.Log; - -import org.kde.kdeconnect.Backends.BasePairingHandler; -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.NetworkPacket; -import org.kde.kdeconnect_tp.R; - -import java.util.Timer; -import java.util.TimerTask; - -public class BluetoothPairingHandler extends BasePairingHandler { - - private Timer mPairingTimer; - - public BluetoothPairingHandler(Device device, final PairingHandlerCallback callback) { - super(device, callback); - - if (device.isPaired()) { - mPairStatus = PairStatus.Paired; - } else { - mPairStatus = PairStatus.NotPaired; - } - } - - // @Override - private NetworkPacket createPairPacket() { - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", true); - return np; - } - - @Override - public void packetReceived(NetworkPacket np) { - - boolean wantsPair = np.getBoolean("pair"); - - if (wantsPair == isPaired()) { - if (mPairStatus == PairStatus.Requested) { - //Log.e("Device","Unpairing (pair rejected)"); - mPairStatus = PairStatus.NotPaired; - hidePairingNotification(); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); - } - return; - } - - if (wantsPair) { - - if (mPairStatus == PairStatus.Requested) { //We started pairing - hidePairingNotification(); - pairingDone(); - } else { - - // If device is already paired, accept pairing silently - if (mDevice.isPaired()) { - acceptPairing(); - return; - } - - // Pairing notifications are still managed by device as there is no other way to - // know about notificationId to cancel notification when PairActivity is started - // Even putting notificationId in intent does not work because PairActivity can be - // started from MainActivity too, so then notificationId cannot be set - hidePairingNotification(); - mDevice.displayPairingNotification(); - - mPairingTimer = new Timer(); - - mPairingTimer.schedule(new TimerTask() { - @Override - public void run() { - Log.w("KDE/Device", "Unpairing (timeout B)"); - mPairStatus = PairStatus.NotPaired; - hidePairingNotification(); - } - }, 25 * 1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) - mPairStatus = PairStatus.RequestedByPeer; - mCallback.incomingRequest(); - - } - } else { - Log.i("KDE/Pairing", "Unpair request"); - - if (mPairStatus == PairStatus.Requested) { - hidePairingNotification(); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); - } else if (mPairStatus == PairStatus.Paired) { - mCallback.unpaired(); - } - - mPairStatus = PairStatus.NotPaired; - - } - - } - - @Override - public void requestPairing() { - - Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() { - @Override - public void onSuccess() { - hidePairingNotification(); //Will stop the pairingTimer if it was running - mPairingTimer = new Timer(); - mPairingTimer.schedule(new TimerTask() { - @Override - public void run() { - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out)); - Log.w("KDE/Device", "Unpairing (timeout A)"); - mPairStatus = PairStatus.NotPaired; - } - }, 30 * 1000); //Time to wait for the other to accept - mPairStatus = PairStatus.Requested; - } - - @Override - public void onFailure(Throwable e) { - mCallback.pairingFailed(mDevice.getContext().getString(R.string.runcommand_notreachable)); - } - }; - mDevice.sendPacket(createPairPacket(), statusCallback); - } - - private void hidePairingNotification() { - mDevice.hidePairingNotification(); - if (mPairingTimer != null) { - mPairingTimer.cancel(); - } - } - - @Override - public void acceptPairing() { - hidePairingNotification(); - Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() { - @Override - public void onSuccess() { - pairingDone(); - } - - @Override - public void onFailure(Throwable e) { - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable)); - } - }; - mDevice.sendPacket(createPairPacket(), statusCallback); - } - - @Override - public void cancelPairing() { - hidePairingNotification(); - mPairStatus = PairStatus.NotPaired; - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", false); - mDevice.sendPacket(np); - } - - //@Override - private void pairingDone() { - // Store device information needed to create a Device object in a future - //Log.e("KDE/PairingDone", "Pairing Done"); - mPairStatus = PairStatus.Paired; - mCallback.pairingDone(); - - } - - @Override - public void unpair() { - mPairStatus = PairStatus.NotPaired; - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", false); - mDevice.sendPacket(np); - } -} diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java index 8b84fa63..cc840630 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -14,7 +14,6 @@ import androidx.annotation.WorkerThread; import org.json.JSONObject; import org.kde.kdeconnect.Backends.BaseLink; -import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.ThreadHelper; @@ -117,11 +116,6 @@ public class LanLink extends BaseLink { return "LanLink"; } - @Override - public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) { - return new LanPairingHandler(device, callback); - } - //Blocking, do not call from main thread @WorkerThread @Override diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index 5188fa2e..23f67f6b 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -9,7 +9,6 @@ package org.kde.kdeconnect.Backends.LanBackend; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.util.Base64; import android.util.Log; import org.kde.kdeconnect.Backends.BaseLink; diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java deleted file mode 100644 index fad5edf6..00000000 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015 Vineet Garg - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Backends.LanBackend; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Base64; -import android.util.Log; - -import org.kde.kdeconnect.Backends.BasePairingHandler; -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.NetworkPacket; -import org.kde.kdeconnect_tp.R; - -import java.security.cert.CertificateEncodingException; -import java.util.Timer; -import java.util.TimerTask; - -public class LanPairingHandler extends BasePairingHandler { - - private Timer mPairingTimer; - - public LanPairingHandler(Device device, final PairingHandlerCallback callback) { - super(device, callback); - - if (device.isPaired()) { - mPairStatus = PairStatus.Paired; - } else { - mPairStatus = PairStatus.NotPaired; - } - } - - private NetworkPacket createPairPacket() { - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", true); - return np; - } - - @Override - public void packetReceived(NetworkPacket np) { - - boolean wantsPair = np.getBoolean("pair"); - - if (wantsPair == isPaired()) { - if (mPairStatus == PairStatus.Requested || mPairStatus == PairStatus.RequestedByPeer) { - //Log.e("Device","Unpairing (pair rejected)"); - mPairStatus = PairStatus.NotPaired; - hidePairingNotification(); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); - } - return; - } - - if (wantsPair) { - - if (mPairStatus == PairStatus.Requested) { //We started pairing - - hidePairingNotification(); - - pairingDone(); - - } else { - - // If device is already paired, accept pairing silently - if (mDevice.isPaired()) { - acceptPairing(); - return; - } - - // Pairing notifications are still managed by device as there is no other way to - // know about notificationId to cancel notification when PairActivity is started - // Even putting notificationId in intent does not work because PairActivity can be - // started from MainActivity too, so then notificationId cannot be set - hidePairingNotification(); - mDevice.displayPairingNotification(); - - mPairingTimer = new Timer(); - - mPairingTimer.schedule(new TimerTask() { - @Override - public void run() { - Log.w("KDE/Device","Unpairing (timeout B)"); - mPairStatus = PairStatus.NotPaired; - hidePairingNotification(); - } - }, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) - mPairStatus = PairStatus.RequestedByPeer; - mCallback.incomingRequest(); - - } - } else { - Log.i("KDE/Pairing", "Unpair request"); - - if (mPairStatus == PairStatus.Requested) { - hidePairingNotification(); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); - } else if (mPairStatus == PairStatus.Paired) { - mCallback.unpaired(); - } - - mPairStatus = PairStatus.NotPaired; - - } - - } - - @Override - public void requestPairing() { - - Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() { - @Override - public void onSuccess() { - hidePairingNotification(); //Will stop the pairingTimer if it was running - mPairingTimer = new Timer(); - mPairingTimer.schedule(new TimerTask() { - @Override - public void run() { - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out)); - Log.w("KDE/Device","Unpairing (timeout A)"); - mPairStatus = PairStatus.NotPaired; - } - }, 30*1000); //Time to wait for the other to accept - mPairStatus = PairStatus.Requested; - } - - @Override - public void onFailure(Throwable e) { - Log.e("LanPairing/onFailure", "Exception", e); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.runcommand_notreachable)); - } - }; - mDevice.sendPacket(createPairPacket(), statusCallback); - } - - private void hidePairingNotification() { - mDevice.hidePairingNotification(); - if (mPairingTimer != null) { - mPairingTimer .cancel(); - } - } - - @Override - public void acceptPairing() { - hidePairingNotification(); - Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() { - @Override - public void onSuccess() { - pairingDone(); - } - - @Override - public void onFailure(Throwable e) { - Log.e("LanPairing/onFailure", "Exception", e); - mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable)); - } - }; - mDevice.sendPacket(createPairPacket(), statusCallback); - } - - @Override - public void cancelPairing() { - hidePairingNotification(); - mPairStatus = PairStatus.NotPaired; - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", false); - mDevice.sendPacket(np); - } - - private void pairingDone() { - // Store device information needed to create a Device object in a future - //Log.e("KDE/PairingDone", "Pairing Done"); - SharedPreferences.Editor editor = mDevice.getContext().getSharedPreferences(mDevice.getDeviceId(), Context.MODE_PRIVATE).edit(); - - try { - String encodedCertificate = Base64.encodeToString(mDevice.certificate.getEncoded(), 0); - editor.putString("certificate", encodedCertificate); - } catch (NullPointerException n) { - Log.w("KDE/PairingDone", "Certificate is null, remote device does not support ssl", n); - } catch (CertificateEncodingException c) { - Log.e("KDE/PairingDOne", "Error encoding certificate", c); - } catch (Exception e) { - Log.e("KDE/Pairng", "Exception", e); - } - editor.apply(); - - mPairStatus = PairStatus.Paired; - mCallback.pairingDone(); - - } - - @Override - public void unpair() { - mPairStatus = PairStatus.NotPaired; - NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); - np.set("pair", false); - mDevice.sendPacket(np); - } -} diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java index 65c15446..118148a4 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -13,7 +13,6 @@ import androidx.annotation.WorkerThread; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPacket; @@ -28,11 +27,6 @@ public class LoopbackLink extends BaseLink { return "LoopbackLink"; } - @Override - public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) { - return new LoopbackPairingHandler(device, callback); - } - @WorkerThread @Override public boolean sendPacket(@NonNull NetworkPacket in, @NonNull Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) { diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java deleted file mode 100644 index 2e39f351..00000000 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015 Vineet Garg - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Backends.LoopbackBackend; - -import android.util.Log; - -import org.kde.kdeconnect.Backends.BasePairingHandler; -import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.NetworkPacket; - -public class LoopbackPairingHandler extends BasePairingHandler { - - public LoopbackPairingHandler(Device device, PairingHandlerCallback callback) { - super(device, callback); - } - - @Override - public void packetReceived(NetworkPacket np) { - - } - - @Override - public void requestPairing() { - Log.i("LoopbackPairing", "requestPairing"); - mCallback.pairingDone(); - } - - @Override - public void acceptPairing() { - Log.i("LoopbackPairing", "acceptPairing"); - mCallback.pairingDone(); - } - - @Override - public void cancelPairing() { - Log.i("LoopbackPairing", "cancelPairing"); - mCallback.unpaired(); - } - - @Override - public void unpair() { - Log.i("LoopbackPairing", "unpair"); - mCallback.unpaired(); - } - -} diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index 692508f5..0e0a9d37 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -80,6 +80,7 @@ public class BackgroundService extends Service { private void registerLinkProviders() { linkProviders.add(new LanLinkProvider(this)); + linkProviders.add(new LoopbackLinkProvider(this)); String testLabSetting = Settings.System.getString(getContentResolver(), "firebase.test.lab"); if ("true".equals(testLabSetting)) { linkProviders.add(new LoopbackLinkProvider(this)); diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index f4bd240d..a2e98f3d 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -28,23 +28,22 @@ import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.lang3.StringUtils; import org.kde.kdeconnect.Backends.BaseLink; -import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.MainActivity; +import org.kde.kdeconnect.UserInterface.PairingHandler; import org.kde.kdeconnect_tp.R; import java.io.IOException; import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; @@ -59,24 +58,17 @@ public class Device implements BaseLink.PacketReceiver { public Certificate certificate; private int notificationId; private int protocolVersion; - private DeviceType deviceType; - private PairStatus pairStatus; - - private final CopyOnWriteArrayList pairingCallback = new CopyOnWriteArrayList<>(); - private final Map pairingHandlers = new HashMap<>(); - + PairingHandler pairingHandler; + private final CopyOnWriteArrayList pairingCallbacks = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); private DevicePacketQueue packetQueue; - private List supportedPlugins = new ArrayList<>(); private final ConcurrentHashMap plugins = new ConcurrentHashMap<>(); private final ConcurrentHashMap pluginsWithoutPermissions = new ConcurrentHashMap<>(); private final ConcurrentHashMap pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>(); private MultiValuedMap pluginsByIncomingInterface = new ArrayListValuedHashMap<>(); - private final SharedPreferences settings; - private final CopyOnWriteArrayList pluginsChangedListeners = new CopyOnWriteArrayList<>(); private Set incomingCapabilities = new HashSet<>(); @@ -92,11 +84,6 @@ public class Device implements BaseLink.PacketReceiver { void onPluginsChanged(@NonNull Device device); } - public enum PairStatus { - NotPaired, - Paired - } - public enum DeviceType { Phone, Tablet, @@ -143,16 +130,6 @@ public class Device implements BaseLink.PacketReceiver { } } - public interface PairingCallback { - void incomingRequest(); - - void pairingSuccessful(); - - void pairingFailed(String error); - - void unpaired(); - } - //Remembered trusted device, we need to wait for a incoming devicelink to communicate Device(Context context, String deviceId) { settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); @@ -162,7 +139,7 @@ public class Device implements BaseLink.PacketReceiver { this.context = context; this.deviceId = deviceId; this.name = settings.getString("deviceName", context.getString(R.string.unknown_device)); - this.pairStatus = PairStatus.Paired; + this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired); this.protocolVersion = DeviceHelper.ProtocolVersion; //We don't know it yet this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop")); @@ -181,7 +158,7 @@ public class Device implements BaseLink.PacketReceiver { this.context = context; this.deviceId = np.getString("deviceId"); this.name = context.getString(R.string.unknown_device); //We read it in addLink - this.pairStatus = PairStatus.NotPaired; + this.pairingHandler= new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired); this.protocolVersion = 0; this.deviceType = DeviceType.Computer; @@ -221,144 +198,109 @@ public class Device implements BaseLink.PacketReceiver { // public boolean isPaired() { - return pairStatus == PairStatus.Paired; + return pairingHandler.getState() == PairingHandler.PairState.Paired; } - /* Asks all pairing handlers that, is pair requested? */ public boolean isPairRequested() { - for (BasePairingHandler ph : pairingHandlers.values()) { - if (ph.isPairRequested()) { - return true; - } - } - return false; + return pairingHandler.getState() == PairingHandler.PairState.Requested; } - /* Asks all pairing handlers that, is pair requested by peer? */ public boolean isPairRequestedByPeer() { - for (BasePairingHandler ph : pairingHandlers.values()) { - if (ph.isPairRequestedByPeer()) { - return true; - } - } - return false; + return pairingHandler.getState() == PairingHandler.PairState.RequestedByPeer; } - public void addPairingCallback(PairingCallback callback) { - pairingCallback.add(callback); + public void addPairingCallback(PairingHandler.PairingCallback callback) { + pairingCallbacks.add(callback); } - public void removePairingCallback(PairingCallback callback) { - pairingCallback.remove(callback); + public void removePairingCallback(PairingHandler.PairingCallback callback) { + pairingCallbacks.remove(callback); } public void requestPairing() { - - Resources res = context.getResources(); - - if (isPaired()) { - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(res.getString(R.string.error_already_paired)); - } - return; - } - - if (!isReachable()) { - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(res.getString(R.string.error_not_reachable)); - } - return; - } - - for (BasePairingHandler ph : pairingHandlers.values()) { - ph.requestPairing(); - } - + pairingHandler.requestPairing(); } public void unpair() { - - for (BasePairingHandler ph : pairingHandlers.values()) { - ph.unpair(); - } - unpairInternal(); // Even if there are no pairing handlers, unpair - } - - /** - * This method does not send an unpair packet, instead it unpairs internally by deleting trusted device info. - * Likely to be called after sending packet from pairing handler - */ - private void unpairInternal() { - - //Log.e("Device","Unpairing (unpairInternal)"); - pairStatus = PairStatus.NotPaired; - - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().remove(deviceId).apply(); - - SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); - devicePreferences.edit().clear().apply(); - - for (PairingCallback cb : pairingCallback) cb.unpaired(); - - reloadPluginsFromSettings(); - - } - - /* This method should be called after pairing is done from pairing handler. Calling this method again should not create any problem as most of the things will get over writter*/ - private void pairingDone() { - - //Log.e("Device", "Storing as trusted, deviceId: "+deviceId); - - hidePairingNotification(); - - pairStatus = PairStatus.Paired; - - //Store as trusted device - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().putBoolean(deviceId, true).apply(); - - SharedPreferences.Editor editor = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE).edit(); - editor.putString("deviceName", name); - editor.putString("deviceType", deviceType.toString()); - editor.apply(); - - reloadPluginsFromSettings(); - - for (PairingCallback cb : pairingCallback) { - cb.pairingSuccessful(); - } - + pairingHandler.unpair(); } /* This method is called after accepting pair request form GUI */ public void acceptPairing() { - Log.i("KDE/Device", "Accepted pair request started by the other device"); - - for (BasePairingHandler ph : pairingHandlers.values()) { - ph.acceptPairing(); - } - + pairingHandler.acceptPairing(); } /* This method is called after rejecting pairing from GUI */ public void cancelPairing() { - Log.i("KDE/Device", "This side cancelled the pair request"); - - pairStatus = PairStatus.NotPaired; - - for (BasePairingHandler ph : pairingHandlers.values()) { - ph.cancelPairing(); - } - - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_canceled_by_user)); - } - + pairingHandler.cancelPairing(); } + PairingHandler.PairingCallback pairingCallback = new PairingHandler.PairingCallback() { + @Override + public void incomingPairRequest() { + displayPairingNotification(); + for (PairingHandler.PairingCallback cb : pairingCallbacks) { + cb.incomingPairRequest(); + } + } + + @Override + public void pairingSuccessful() { + hidePairingNotification(); + + // Store current device certificate so we can check it in the future (TOFU) + SharedPreferences.Editor editor = context.getSharedPreferences(getDeviceId(), Context.MODE_PRIVATE).edit(); + try { + String encodedCertificate = Base64.encodeToString(certificate.getEncoded(), 0); + editor.putString("certificate", encodedCertificate); + } catch (NullPointerException n) { + Log.w("PairingHandler", "Certificate is null, remote device does not support SSL", n); + return; + } catch (CertificateEncodingException c) { + Log.e("PairingHandler", "Error encoding certificate", c); + return; + } + editor.putString("deviceName", name); + editor.putString("deviceType", deviceType.toString()); + editor.apply(); + + // Store as trusted device + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + preferences.edit().putBoolean(deviceId, true).apply(); + + reloadPluginsFromSettings(); + + for (PairingHandler.PairingCallback cb : pairingCallbacks) { + cb.pairingSuccessful(); + } + } + + @Override + public void pairingFailed(String error) { + hidePairingNotification(); + for (PairingHandler.PairingCallback cb : pairingCallbacks) { + cb.pairingFailed(error); + } + } + + @Override + public void unpaired() { + SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + preferences.edit().remove(deviceId).apply(); + + SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); + devicePreferences.edit().clear().apply(); + + for (PairingHandler.PairingCallback cb : pairingCallbacks) { + cb.unpaired(); + } + + reloadPluginsFromSettings(); + } + }; + // // Notification related methods used during pairing // @@ -414,7 +356,7 @@ public class Device implements BaseLink.PacketReceiver { } // - // ComputerLink-related functions + // Link-related functions // public boolean isReachable() { @@ -457,39 +399,9 @@ public class Device implements BaseLink.PacketReceiver { Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); - if (!pairingHandlers.containsKey(link.getName())) { - BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() { - @Override - public void incomingRequest() { - for (PairingCallback cb : pairingCallback) { - cb.incomingRequest(); - } - } - - @Override - public void pairingDone() { - Device.this.pairingDone(); - } - - @Override - public void pairingFailed(String error) { - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(error); - } - } - - @Override - public void unpaired() { - unpairInternal(); - } - }; - pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback)); - } - Set outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null); Set incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null); - if (incomingCapabilities != null && outgoingCapabilities != null) { supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities)); } else { @@ -504,18 +416,6 @@ public class Device implements BaseLink.PacketReceiver { public void removeLink(BaseLink link) { //FilesHelper.LogOpenFileCount(); - /* Remove pairing handler corresponding to that link too if it was the only link*/ - boolean linkPresent = false; - for (BaseLink bl : links) { - if (bl.getName().equals(link.getName())) { - linkPresent = true; - break; - } - } - if (!linkPresent) { - pairingHandlers.remove(link.getName()); - } - link.removePacketReceiver(this); links.remove(link); Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); @@ -534,16 +434,8 @@ public class Device implements BaseLink.PacketReceiver { DeviceStats.countReceived(getDeviceId(), np.getType()); if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) { - Log.i("KDE/Device", "Pair packet"); - - for (BasePairingHandler ph : pairingHandlers.values()) { - try { - ph.packetReceived(np); - } catch (Exception e) { - Log.e("PairingPacketReceived", "Exception", e); - } - } + pairingHandler.packetReceived(np); } else if (isPaired()) { // pluginsByIncomingInterface may not be built yet if(pluginsByIncomingInterface.isEmpty()) { diff --git a/src/org/kde/kdeconnect/DevicePacketQueue.java b/src/org/kde/kdeconnect/DevicePacketQueue.java index f5f3f832..6ebd98ef 100644 --- a/src/org/kde/kdeconnect/DevicePacketQueue.java +++ b/src/org/kde/kdeconnect/DevicePacketQueue.java @@ -120,4 +120,5 @@ class DevicePacketQueue { } } } + } diff --git a/src/org/kde/kdeconnect/KdeConnect.java b/src/org/kde/kdeconnect/KdeConnect.java index 7ad1b0ae..747f2197 100644 --- a/src/org/kde/kdeconnect/KdeConnect.java +++ b/src/org/kde/kdeconnect/KdeConnect.java @@ -14,6 +14,7 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; +import org.kde.kdeconnect.UserInterface.PairingHandler; import org.kde.kdeconnect.UserInterface.ThemeUtil; import java.util.Set; @@ -111,9 +112,9 @@ public class KdeConnect extends Application { } } - private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { + private final PairingHandler.PairingCallback devicePairingCallback = new PairingHandler.PairingCallback() { @Override - public void incomingRequest() { + public void incomingPairRequest() { onDeviceListChanged(); } diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt index c18bf48e..43966fa2 100644 --- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt +++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt @@ -31,7 +31,6 @@ import com.google.accompanist.themeadapter.material3.Mdc3Theme import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.kde.kdeconnect.BackgroundService import org.kde.kdeconnect.Device -import org.kde.kdeconnect.Device.PairingCallback import org.kde.kdeconnect.Device.PluginsChangedListener import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper import org.kde.kdeconnect.KdeConnect @@ -300,8 +299,8 @@ class DeviceFragment : Fragment() { } } - private val pairingCallback: PairingCallback = object : PairingCallback { - override fun incomingRequest() { + private val pairingCallback: PairingHandler.PairingCallback = object : PairingHandler.PairingCallback { + override fun incomingPairRequest() { mActivity?.runOnUiThread { refreshUI() } } diff --git a/src/org/kde/kdeconnect/UserInterface/PairingHandler.java b/src/org/kde/kdeconnect/UserInterface/PairingHandler.java new file mode 100644 index 00000000..40b37f57 --- /dev/null +++ b/src/org/kde/kdeconnect/UserInterface/PairingHandler.java @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + */ + +package org.kde.kdeconnect.UserInterface; + +import android.util.Log; + +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.NetworkPacket; +import org.kde.kdeconnect_tp.R; + +import java.util.Timer; +import java.util.TimerTask; + +public class PairingHandler { + + public enum PairState { + NotPaired, + Requested, + RequestedByPeer, + Paired + } + + public interface PairingCallback { + void incomingPairRequest(); + + void pairingFailed(String error); + + void pairingSuccessful(); + + void unpaired(); + } + + protected final Device mDevice; + protected PairState mPairState; + protected final PairingCallback mCallback; + + public PairState getState() { + return mPairState; + } + + private Timer mPairingTimer; + + public PairingHandler(Device device, final PairingCallback callback, PairState initialState) { + this.mDevice = device; + this.mCallback = callback; + this.mPairState = initialState; + } + + public void packetReceived(NetworkPacket np) { + cancelTimer(); + boolean wantsPair = np.getBoolean("pair"); + if (wantsPair) { + switch (mPairState) { + case Requested: // We started pairing and tis is a confirmation + pairingDone(); + break; + case RequestedByPeer: + Log.w("PairingHandler", "Ignoring second pairing request before the first one timed out"); + break; + case Paired: + Log.w("PairingHandler", "Auto-accepting pairing request from a device we already trusted"); + acceptPairing(); + case NotPaired: + mPairState = PairState.RequestedByPeer; + + mPairingTimer = new Timer(); + mPairingTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.w("PairingHandler", "Unpairing (timeout after we started pairing)"); + mPairState = PairState.NotPaired; + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out)); + } + }, 25 * 1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) + + mCallback.incomingPairRequest(); + break; + } + } else { + Log.i("PairingHandler", "Unpair request received"); + switch (mPairState) { + case NotPaired: + Log.i("PairingHandler", "Ignoring unpair request for already unpaired device"); + break; + case Requested: // We started pairing and got rejected + case RequestedByPeer: // They stared pairing, then cancelled + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); + break; + case Paired: + mCallback.unpaired(); + break; + } + mPairState = PairState.NotPaired; + } + } + + public void requestPairing() { + cancelTimer(); + + if (mPairState == PairState.Paired) { + Log.w("PairingHandler", "requestPairing was called on an already paired device"); + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_already_paired)); + return; + } + + if (mPairState == PairState.RequestedByPeer) { + Log.w("PairingHandler", "Pairing already started by the other end, accepting their request."); + acceptPairing(); + return; + } + + if (!mDevice.isReachable()) { + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable)); + return; + } + + mPairState = PairState.Requested; + + Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() { + @Override + public void onSuccess() { + mPairingTimer = new Timer(); + mPairingTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.w("PairingHandler","Unpairing (timeout after receiving pair request)"); + mPairState = PairState.NotPaired; + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out)); + } + }, 30*1000); //Time to wait for the other to accept + } + + @Override + public void onFailure(Throwable e) { + Log.e("PairingHandler", "Exception sending pairing request", e); + mPairState = PairState.NotPaired; + mCallback.pairingFailed(mDevice.getContext().getString(R.string.runcommand_notreachable)); + } + }; + NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); + np.set("pair", true); + mDevice.sendPacket(np, statusCallback); + } + + public void acceptPairing() { + cancelTimer(); + Device.SendPacketStatusCallback StateCallback = new Device.SendPacketStatusCallback() { + @Override + public void onSuccess() { + pairingDone(); + } + + @Override + public void onFailure(Throwable e) { + Log.e("PairingHandler", "Exception sending accept pairing packet", e); + mPairState = PairState.NotPaired; + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable)); + } + }; + NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); + np.set("pair", true); + mDevice.sendPacket(np, StateCallback); + } + + public void cancelPairing() { + cancelTimer(); + mPairState = PairState.NotPaired; + NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); + np.set("pair", false); + mDevice.sendPacket(np); + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_user)); + } + + private void pairingDone() { + Log.i("PairingHandler", "Pairing done"); + mPairState = PairState.Paired; + try { + mCallback.pairingSuccessful(); + } catch (Exception e) { + Log.e("PairingHandler", "Exception in pairingSuccessful callback, unpairing"); + e.printStackTrace(); + mPairState = PairState.NotPaired; + } + } + + public void unpair() { + mPairState = PairState.NotPaired; + NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR); + np.set("pair", false); + mDevice.sendPacket(np); + mCallback.unpaired(); + } + + private void cancelTimer() { + if (mPairingTimer != null) { + mPairingTimer.cancel(); + } + } +} diff --git a/tests/org/kde/kdeconnect/DeviceTest.java b/tests/org/kde/kdeconnect/DeviceTest.java index 14e30387..5c3a5d3b 100644 --- a/tests/org/kde/kdeconnect/DeviceTest.java +++ b/tests/org/kde/kdeconnect/DeviceTest.java @@ -28,18 +28,17 @@ import androidx.core.content.ContextCompat; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Backends.LanBackend.LanLink; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; -import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; -import org.mockito.ArgumentCaptor; +import org.kde.kdeconnect.UserInterface.PairingHandler; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -125,55 +124,7 @@ public class DeviceTest { assertTrue(device.isPaired()); } - // Testing pairing done - // Created an unpaired device inside this test - @Test - public void testPairingDone() { - NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); - fakeNetworkPacket.set("deviceId", "unpairedTestDevice"); - fakeNetworkPacket.set("deviceName", "Unpaired Test Device"); - fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion); - fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString()); - - LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); - Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); - LanLink link = Mockito.mock(LanLink.class); - Mockito.when(link.getLinkProvider()).thenReturn(linkProvider); - Mockito.when(link.getPairingHandler(any(Device.class), any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); - Device device = new Device(context, fakeNetworkPacket, link); - - Device.PairingCallback pairingCallback = Mockito.mock(Device.PairingCallback.class); - device.addPairingCallback(pairingCallback); - - - ArgumentCaptor pairingHandlerCallback = ArgumentCaptor.forClass(BasePairingHandler.PairingHandlerCallback.class); - Mockito.verify(link, Mockito.times(1)).getPairingHandler(eq(device), pairingHandlerCallback.capture()); - - assertNotNull(device); - assertEquals(device.getDeviceId(), "unpairedTestDevice"); - assertEquals(device.getName(), "Unpaired Test Device"); - assertEquals(device.getDeviceType(), Device.DeviceType.Phone); - assertNull(device.certificate); - - pairingHandlerCallback.getValue().pairingDone(); - - assertTrue(device.isPaired()); - Mockito.verify(pairingCallback, Mockito.times(1)).pairingSuccessful(); - - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - assertTrue(preferences.getBoolean(device.getDeviceId(), false)); - - SharedPreferences settings = context.getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE); - assertEquals(settings.getString("deviceName", "Unknown device"), "Unpaired Test Device"); - assertEquals(settings.getString("deviceType", "tablet"), "phone"); - - // Cleanup for unpaired test device - preferences.edit().remove(device.getDeviceId()).apply(); - settings.edit().clear().apply(); - } - - @Test - public void testPairingDoneWithCertificate() { + public void testPairingDoneWithCertificate() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException { NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); fakeNetworkPacket.set("deviceId", "unpairedTestDevice"); @@ -201,7 +152,6 @@ public class DeviceTest { LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); LanLink link = Mockito.mock(LanLink.class); - Mockito.when(link.getPairingHandler(any(Device.class), any(BasePairingHandler.PairingHandlerCallback.class))).thenReturn(Mockito.mock(LanPairingHandler.class)); Mockito.when(link.getLinkProvider()).thenReturn(linkProvider); Device device = new Device(context, fakeNetworkPacket, link); @@ -211,14 +161,9 @@ public class DeviceTest { assertEquals(device.getDeviceType(), Device.DeviceType.Phone); assertNotNull(device.certificate); - Method method; - try { - method = Device.class.getDeclaredMethod("pairingDone"); - method.setAccessible(true); - method.invoke(device); - } catch (Exception e) { - Log.e("KDEConnect", "Exception", e); - } + Method method = PairingHandler.class.getDeclaredMethod("pairingDone"); + method.setAccessible(true); + method.invoke(device.pairingHandler); assertTrue(device.isPaired()); @@ -236,7 +181,7 @@ public class DeviceTest { @Test public void testUnpair() { - Device.PairingCallback pairingCallback = Mockito.mock(Device.PairingCallback.class); + PairingHandler.PairingCallback pairingCallback = Mockito.mock(PairingHandler.PairingCallback.class); Device device = new Device(context, "testDevice"); device.addPairingCallback(pairingCallback);