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:
parent
476304d6fb
commit
a20fb00b7d
@ -37,7 +37,6 @@ public abstract class BaseLink {
|
||||
|
||||
/* 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;
|
||||
|
@ -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();
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
|
@ -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> pairingCallback = new CopyOnWriteArrayList<>();
|
||||
private final Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
|
||||
|
||||
PairingHandler pairingHandler;
|
||||
private final CopyOnWriteArrayList<PairingHandler.PairingCallback> pairingCallbacks = new CopyOnWriteArrayList<>();
|
||||
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
||||
private DevicePacketQueue packetQueue;
|
||||
|
||||
private List<String> supportedPlugins = new ArrayList<>();
|
||||
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
|
||||
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
||||
|
||||
private final SharedPreferences settings;
|
||||
|
||||
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
||||
private Set<String> 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,143 +198,108 @@ 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
|
||||
pairingHandler.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() {
|
||||
/* 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");
|
||||
pairingHandler.acceptPairing();
|
||||
}
|
||||
|
||||
//Log.e("Device","Unpairing (unpairInternal)");
|
||||
pairStatus = PairStatus.NotPaired;
|
||||
/* This method is called after rejecting pairing from GUI */
|
||||
public void cancelPairing() {
|
||||
Log.i("KDE/Device", "This side cancelled the pair request");
|
||||
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 (PairingCallback cb : pairingCallback) cb.unpaired();
|
||||
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
|
||||
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 */
|
||||
public void acceptPairing() {
|
||||
|
||||
Log.i("KDE/Device", "Accepted pair request started by the other device");
|
||||
|
||||
for (BasePairingHandler ph : pairingHandlers.values()) {
|
||||
ph.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));
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// 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<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
|
||||
Set<String> 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()) {
|
||||
|
@ -120,4 +120,5 @@ class DevicePacketQueue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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() }
|
||||
}
|
||||
|
||||
|
203
src/org/kde/kdeconnect/UserInterface/PairingHandler.java
Normal file
203
src/org/kde/kdeconnect/UserInterface/PairingHandler.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<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() {
|
||||
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 method = PairingHandler.class.getDeclaredMethod("pairingDone");
|
||||
method.setAccessible(true);
|
||||
method.invoke(device);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDEConnect", "Exception", e);
|
||||
}
|
||||
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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user