2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 13:17:43 +00:00

Have a single PairingHandler for all links

This commit is contained in:
Albert Vaca Cintora 2023-06-02 19:04:49 +02:00
parent 0eb4b5bced
commit d0923b845b
16 changed files with 301 additions and 780 deletions

View File

@ -30,14 +30,13 @@ public abstract class BaseLink {
private final ArrayList<PacketReceiver> receivers = new ArrayList<>(); private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
protected BaseLink(@NonNull Context context, @NonNull String deviceId, @NonNull BaseLinkProvider linkProvider) { protected BaseLink(@NonNull Context context, @NonNull String deviceId, @NonNull BaseLinkProvider linkProvider) {
this.context = context; this.context = context;
this.linkProvider = linkProvider; this.linkProvider = linkProvider;
this.deviceId = deviceId; this.deviceId = deviceId;
} }
/* To be implemented by each link for pairing handlers */ /* To be implemented by each link for pairing handlers */
public abstract String getName(); public abstract String getName();
public abstract BasePairingHandler getPairingHandler(@NonNull Device device, @NonNull BasePairingHandler.PairingHandlerCallback callback);
public String getDeviceId() { public String getDeviceId() {
return deviceId; return deviceId;

View File

@ -1,68 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* 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();
}

View File

@ -16,7 +16,6 @@ import androidx.annotation.WorkerThread;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.NetworkPacket;
@ -112,11 +111,6 @@ public class BluetoothLink extends BaseLink {
return "BluetoothLink"; return "BluetoothLink";
} }
@Override
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
return new BluetoothPairingHandler(device, callback);
}
public void disconnect() { public void disconnect() {
if (connection == null) { if (connection == null) {
return; return;

View File

@ -1,181 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* 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);
}
}

View File

@ -14,7 +14,6 @@ import androidx.annotation.WorkerThread;
import org.json.JSONObject; import org.json.JSONObject;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper; import org.kde.kdeconnect.Helpers.ThreadHelper;
@ -117,11 +116,6 @@ public class LanLink extends BaseLink {
return "LanLink"; return "LanLink";
} }
@Override
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
return new LanPairingHandler(device, callback);
}
//Blocking, do not call from main thread //Blocking, do not call from main thread
@WorkerThread @WorkerThread
@Override @Override

View File

@ -9,7 +9,6 @@ package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;

View File

@ -1,202 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* 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);
}
}

View File

@ -13,7 +13,6 @@ import androidx.annotation.WorkerThread;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.NetworkPacket;
@ -28,11 +27,6 @@ public class LoopbackLink extends BaseLink {
return "LoopbackLink"; return "LoopbackLink";
} }
@Override
public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
return new LoopbackPairingHandler(device, callback);
}
@WorkerThread @WorkerThread
@Override @Override
public boolean sendPacket(@NonNull NetworkPacket in, @NonNull Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) { public boolean sendPacket(@NonNull NetworkPacket in, @NonNull Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) {

View File

@ -1,50 +0,0 @@
/*
* SPDX-FileCopyrightText: 2015 Vineet Garg <grg.vineet@gmail.com>
*
* 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();
}
}

View File

@ -80,6 +80,7 @@ public class BackgroundService extends Service {
private void registerLinkProviders() { private void registerLinkProviders() {
linkProviders.add(new LanLinkProvider(this)); linkProviders.add(new LanLinkProvider(this));
linkProviders.add(new LoopbackLinkProvider(this));
String testLabSetting = Settings.System.getString(getContentResolver(), "firebase.test.lab"); String testLabSetting = Settings.System.getString(getContentResolver(), "firebase.test.lab");
if ("true".equals(testLabSetting)) { if ("true".equals(testLabSetting)) {
linkProviders.add(new LoopbackLinkProvider(this)); linkProviders.add(new LoopbackLinkProvider(this));

View File

@ -28,23 +28,22 @@ import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MainActivity; import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import java.io.IOException; import java.io.IOException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -59,24 +58,17 @@ public class Device implements BaseLink.PacketReceiver {
public Certificate certificate; public Certificate certificate;
private int notificationId; private int notificationId;
private int protocolVersion; private int protocolVersion;
private DeviceType deviceType; private DeviceType deviceType;
private PairStatus pairStatus; PairingHandler pairingHandler;
private final CopyOnWriteArrayList<PairingHandler.PairingCallback> pairingCallbacks = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
private final Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
private DevicePacketQueue packetQueue; private DevicePacketQueue packetQueue;
private List<String> supportedPlugins = new ArrayList<>(); private List<String> supportedPlugins = new ArrayList<>();
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>(); private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
private final SharedPreferences settings; private final SharedPreferences settings;
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
private Set<String> incomingCapabilities = new HashSet<>(); private Set<String> incomingCapabilities = new HashSet<>();
@ -92,11 +84,6 @@ public class Device implements BaseLink.PacketReceiver {
void onPluginsChanged(@NonNull Device device); void onPluginsChanged(@NonNull Device device);
} }
public enum PairStatus {
NotPaired,
Paired
}
public enum DeviceType { public enum DeviceType {
Phone, Phone,
Tablet, 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 //Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device(Context context, String deviceId) { Device(Context context, String deviceId) {
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
@ -162,7 +139,7 @@ public class Device implements BaseLink.PacketReceiver {
this.context = context; this.context = context;
this.deviceId = deviceId; this.deviceId = deviceId;
this.name = settings.getString("deviceName", context.getString(R.string.unknown_device)); 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.protocolVersion = DeviceHelper.ProtocolVersion; //We don't know it yet
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop")); this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
@ -181,7 +158,7 @@ public class Device implements BaseLink.PacketReceiver {
this.context = context; this.context = context;
this.deviceId = np.getString("deviceId"); this.deviceId = np.getString("deviceId");
this.name = context.getString(R.string.unknown_device); //We read it in addLink 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.protocolVersion = 0;
this.deviceType = DeviceType.Computer; this.deviceType = DeviceType.Computer;
@ -221,144 +198,109 @@ public class Device implements BaseLink.PacketReceiver {
// //
public boolean isPaired() { public boolean isPaired() {
return pairStatus == PairStatus.Paired; return pairingHandler.getState() == PairingHandler.PairState.Paired;
} }
/* Asks all pairing handlers that, is pair requested? */
public boolean isPairRequested() { public boolean isPairRequested() {
for (BasePairingHandler ph : pairingHandlers.values()) { return pairingHandler.getState() == PairingHandler.PairState.Requested;
if (ph.isPairRequested()) {
return true;
}
}
return false;
} }
/* Asks all pairing handlers that, is pair requested by peer? */
public boolean isPairRequestedByPeer() { public boolean isPairRequestedByPeer() {
for (BasePairingHandler ph : pairingHandlers.values()) { return pairingHandler.getState() == PairingHandler.PairState.RequestedByPeer;
if (ph.isPairRequestedByPeer()) {
return true;
}
}
return false;
} }
public void addPairingCallback(PairingCallback callback) { public void addPairingCallback(PairingHandler.PairingCallback callback) {
pairingCallback.add(callback); pairingCallbacks.add(callback);
} }
public void removePairingCallback(PairingCallback callback) { public void removePairingCallback(PairingHandler.PairingCallback callback) {
pairingCallback.remove(callback); pairingCallbacks.remove(callback);
} }
public void requestPairing() { public void requestPairing() {
pairingHandler.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();
}
} }
public void unpair() { public void unpair() {
pairingHandler.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();
}
} }
/* This method is called after accepting pair request form GUI */ /* This method is called after accepting pair request form GUI */
public void acceptPairing() { public void acceptPairing() {
Log.i("KDE/Device", "Accepted pair request started by the other device"); Log.i("KDE/Device", "Accepted pair request started by the other device");
pairingHandler.acceptPairing();
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.acceptPairing();
}
} }
/* This method is called after rejecting pairing from GUI */ /* This method is called after rejecting pairing from GUI */
public void cancelPairing() { public void cancelPairing() {
Log.i("KDE/Device", "This side cancelled the pair request"); Log.i("KDE/Device", "This side cancelled the pair request");
pairingHandler.cancelPairing();
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.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 // 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() { 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()); 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<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null); Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null); Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
if (incomingCapabilities != null && outgoingCapabilities != null) { if (incomingCapabilities != null && outgoingCapabilities != null) {
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities)); supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
} else { } else {
@ -504,18 +416,6 @@ public class Device implements BaseLink.PacketReceiver {
public void removeLink(BaseLink link) { public void removeLink(BaseLink link) {
//FilesHelper.LogOpenFileCount(); //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); link.removePacketReceiver(this);
links.remove(link); links.remove(link);
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); 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()); DeviceStats.countReceived(getDeviceId(), np.getType());
if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) { if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) {
Log.i("KDE/Device", "Pair packet"); Log.i("KDE/Device", "Pair packet");
pairingHandler.packetReceived(np);
for (BasePairingHandler ph : pairingHandlers.values()) {
try {
ph.packetReceived(np);
} catch (Exception e) {
Log.e("PairingPacketReceived", "Exception", e);
}
}
} else if (isPaired()) { } else if (isPaired()) {
// pluginsByIncomingInterface may not be built yet // pluginsByIncomingInterface may not be built yet
if(pluginsByIncomingInterface.isEmpty()) { if(pluginsByIncomingInterface.isEmpty()) {

View File

@ -120,4 +120,5 @@ class DevicePacketQueue {
} }
} }
} }
} }

View File

@ -14,6 +14,7 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect.UserInterface.ThemeUtil;
import java.util.Set; 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 @Override
public void incomingRequest() { public void incomingPairRequest() {
onDeviceListChanged(); onDeviceListChanged();
} }

View File

@ -31,7 +31,6 @@ import com.google.accompanist.themeadapter.material3.Mdc3Theme
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.kde.kdeconnect.BackgroundService import org.kde.kdeconnect.BackgroundService
import org.kde.kdeconnect.Device import org.kde.kdeconnect.Device
import org.kde.kdeconnect.Device.PairingCallback
import org.kde.kdeconnect.Device.PluginsChangedListener import org.kde.kdeconnect.Device.PluginsChangedListener
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.KdeConnect
@ -300,8 +299,8 @@ class DeviceFragment : Fragment() {
} }
} }
private val pairingCallback: PairingCallback = object : PairingCallback { private val pairingCallback: PairingHandler.PairingCallback = object : PairingHandler.PairingCallback {
override fun incomingRequest() { override fun incomingPairRequest() {
mActivity?.runOnUiThread { refreshUI() } mActivity?.runOnUiThread { refreshUI() }
} }

View File

@ -0,0 +1,203 @@
/*
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
*
* 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();
}
}
}

View File

@ -28,18 +28,17 @@ import androidx.core.content.ContextCompat;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Backends.LanBackend.LanLink; import org.kde.kdeconnect.Backends.LanBackend.LanLink;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; 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.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.mockito.ArgumentCaptor; import org.kde.kdeconnect.UserInterface.PairingHandler;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito; import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunner;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -125,55 +124,7 @@ public class DeviceTest {
assertTrue(device.isPaired()); assertTrue(device.isPaired());
} }
// Testing pairing done public void testPairingDoneWithCertificate() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
// 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<BasePairingHandler.PairingHandlerCallback> 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() {
NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY); NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
fakeNetworkPacket.set("deviceId", "unpairedTestDevice"); fakeNetworkPacket.set("deviceId", "unpairedTestDevice");
@ -201,7 +152,6 @@ public class DeviceTest {
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
LanLink link = Mockito.mock(LanLink.class); 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); Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
Device device = new Device(context, fakeNetworkPacket, link); Device device = new Device(context, fakeNetworkPacket, link);
@ -211,14 +161,9 @@ public class DeviceTest {
assertEquals(device.getDeviceType(), Device.DeviceType.Phone); assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
assertNotNull(device.certificate); assertNotNull(device.certificate);
Method method; Method method = PairingHandler.class.getDeclaredMethod("pairingDone");
try { method.setAccessible(true);
method = Device.class.getDeclaredMethod("pairingDone"); method.invoke(device.pairingHandler);
method.setAccessible(true);
method.invoke(device);
} catch (Exception e) {
Log.e("KDEConnect", "Exception", e);
}
assertTrue(device.isPaired()); assertTrue(device.isPaired());
@ -236,7 +181,7 @@ public class DeviceTest {
@Test @Test
public void testUnpair() { 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 device = new Device(context, "testDevice");
device.addPairingCallback(pairingCallback); device.addPairingCallback(pairingCallback);