mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-31 22:25:08 +00:00
Compare commits
10 Commits
master
...
work/1-26-
Author | SHA1 | Date | |
---|---|---|---|
|
ee806f2a22 | ||
|
5a620b4a7a | ||
|
e9732d009a | ||
|
2386c9cb48 | ||
|
5384cb18a6 | ||
|
9e958b23f4 | ||
|
6a3d4de995 | ||
|
a1ccc7b64e | ||
|
d0923b845b | ||
|
0eb4b5bced |
@@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="org.kde.kdeconnect_tp"
|
package="org.kde.kdeconnect_tp"
|
||||||
android:versionCode="12500"
|
android:versionCode="12591"
|
||||||
android:versionName="1.25.0">
|
android:versionName="1.26.0 beta2">
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.telephony"
|
android:name="android.hardware.telephony"
|
||||||
|
@@ -180,7 +180,6 @@ dependencies {
|
|||||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.9.0'
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps
|
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps
|
||||||
implementation 'com.jaredrummler:android-device-names:1.1.9' //To get a human-friendly device name
|
|
||||||
|
|
||||||
implementation 'org.apache.sshd:sshd-core:0.14.0'
|
implementation 'org.apache.sshd:sshd-core:0.14.0'
|
||||||
implementation 'org.apache.mina:mina-core:2.0.19' //For some reason, makes sshd-core:0.14.0 work without NIO, which isn't available until Android 8 (api 26)
|
implementation 'org.apache.mina:mina-core:2.0.19' //For some reason, makes sshd-core:0.14.0 work without NIO, which isn't available until Android 8 (api 26)
|
||||||
@@ -203,6 +202,8 @@ dependencies {
|
|||||||
implementation 'org.apache.commons:commons-collections4:4.4'
|
implementation 'org.apache.commons:commons-collections4:4.4'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||||
|
|
||||||
|
implementation 'com.univocity:univocity-parsers:2.9.1'
|
||||||
|
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
|
||||||
|
@@ -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;
|
||||||
|
@@ -6,8 +6,11 @@
|
|||||||
|
|
||||||
package org.kde.kdeconnect.Backends;
|
package org.kde.kdeconnect.Backends;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
|
import java.security.cert.Certificate;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
public abstract class BaseLinkProvider {
|
public abstract class BaseLinkProvider {
|
||||||
@@ -15,7 +18,10 @@ public abstract class BaseLinkProvider {
|
|||||||
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public interface ConnectionReceiver {
|
public interface ConnectionReceiver {
|
||||||
void onConnectionReceived(NetworkPacket identityPacket, BaseLink link);
|
void onConnectionReceived(@NonNull final String deviceId,
|
||||||
|
@NonNull final Certificate certificate,
|
||||||
|
@NonNull final NetworkPacket identityPacket,
|
||||||
|
@NonNull final BaseLink link);
|
||||||
void onConnectionLost(BaseLink link);
|
void onConnectionLost(BaseLink link);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,10 +34,13 @@ public abstract class BaseLinkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//These two should be called when the provider links to a new computer
|
//These two should be called when the provider links to a new computer
|
||||||
protected void connectionAccepted(NetworkPacket identityPacket, BaseLink link) {
|
protected void connectionAccepted(@NonNull final String deviceId,
|
||||||
|
@NonNull final Certificate certificate,
|
||||||
|
@NonNull final NetworkPacket identityPacket,
|
||||||
|
@NonNull final BaseLink link) {
|
||||||
//Log.i("KDE/LinkProvider", "connectionAccepted");
|
//Log.i("KDE/LinkProvider", "connectionAccepted");
|
||||||
for(ConnectionReceiver cr : connectionReceivers) {
|
for(ConnectionReceiver cr : connectionReceivers) {
|
||||||
cr.onConnectionReceived(identityPacket, link);
|
cr.onConnectionReceived(deviceId, certificate, identityPacket, link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected void connectionLost(BaseLink link) {
|
protected void connectionLost(BaseLink link) {
|
||||||
@@ -45,8 +54,6 @@ public abstract class BaseLinkProvider {
|
|||||||
public abstract void onStart();
|
public abstract void onStart();
|
||||||
public abstract void onStop();
|
public abstract void onStop();
|
||||||
public abstract void onNetworkChange();
|
public abstract void onNetworkChange();
|
||||||
|
|
||||||
//public abstract int getPriority();
|
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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.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;
|
||||||
|
@@ -15,10 +15,12 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
@@ -27,6 +29,8 @@ import java.io.InputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -48,8 +52,12 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
private ServerRunnable serverRunnable;
|
private ServerRunnable serverRunnable;
|
||||||
private ClientRunnable clientRunnable;
|
private ClientRunnable clientRunnable;
|
||||||
|
|
||||||
private void addLink(NetworkPacket identityPacket, BluetoothLink link) {
|
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
|
||||||
String deviceId = identityPacket.getString("deviceId");
|
String deviceId = identityPacket.getString("deviceId");
|
||||||
|
String certificateString = identityPacket.getString("certificate");
|
||||||
|
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
||||||
|
|
||||||
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
||||||
BluetoothLink oldLink = visibleComputers.get(deviceId);
|
BluetoothLink oldLink = visibleComputers.get(deviceId);
|
||||||
if (oldLink == link) {
|
if (oldLink == link) {
|
||||||
@@ -57,7 +65,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visibleComputers.put(deviceId, link);
|
visibleComputers.put(deviceId, link);
|
||||||
connectionAccepted(identityPacket, link);
|
connectionAccepted(deviceId, certificate, identityPacket, link);
|
||||||
link.startListening();
|
link.startListening();
|
||||||
if (oldLink != null) {
|
if (oldLink != null) {
|
||||||
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
|
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
|
||||||
@@ -189,6 +197,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
InputStream inputStream = connection.getDefaultInputStream();
|
InputStream inputStream = connection.getDefaultInputStream();
|
||||||
|
|
||||||
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
||||||
|
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
||||||
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
||||||
outputStream.write(message);
|
outputStream.write(message);
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
@@ -371,7 +380,11 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
addLink(identityPacket, link);
|
try {
|
||||||
|
addLink(identityPacket, link);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -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.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
|
||||||
|
@@ -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;
|
||||||
@@ -212,9 +211,8 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
|||||||
String mode = clientMode ? "client" : "server";
|
String mode = clientMode ? "client" : "server";
|
||||||
try {
|
try {
|
||||||
Certificate certificate = event.getPeerCertificates()[0];
|
Certificate certificate = event.getPeerCertificates()[0];
|
||||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
|
||||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||||
addLink(identityPacket, sslsocket);
|
addLink(deviceId, certificate, identityPacket, sslsocket);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
|
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
|
||||||
Device device = KdeConnect.getInstance().getDevice(deviceId);
|
Device device = KdeConnect.getInstance().getDevice(deviceId);
|
||||||
@@ -253,14 +251,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
|||||||
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
|
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param identityPacket representation of remote device
|
* @param deviceId remote device id
|
||||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
* @param certificate remote device certificate
|
||||||
* @param connectionOrigin which side started this connection
|
* @param identityPacket identity packet with the remote device's device name, type, protocol version, etc.
|
||||||
|
* @param socket a new Socket, which should be used to send and receive packets from the remote device
|
||||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket, LanLink.ConnectionStarted)}
|
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket, LanLink.ConnectionStarted)}
|
||||||
*/
|
*/
|
||||||
private void addLink(final NetworkPacket identityPacket, SSLSocket socket) throws IOException {
|
private void addLink(String deviceId, Certificate certificate, final NetworkPacket identityPacket, SSLSocket socket) throws IOException {
|
||||||
|
|
||||||
String deviceId = identityPacket.getString("deviceId");
|
|
||||||
LanLink currentLink = visibleComputers.get(deviceId);
|
LanLink currentLink = visibleComputers.get(deviceId);
|
||||||
if (currentLink != null) {
|
if (currentLink != null) {
|
||||||
//Update old link
|
//Update old link
|
||||||
@@ -272,7 +269,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
|||||||
//Let's create the link
|
//Let's create the link
|
||||||
LanLink link = new LanLink(context, deviceId, this, socket);
|
LanLink link = new LanLink(context, deviceId, this, socket);
|
||||||
visibleComputers.put(deviceId, link);
|
visibleComputers.put(deviceId, link);
|
||||||
connectionAccepted(identityPacket, link);
|
connectionAccepted(deviceId, certificate, identityPacket, link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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.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) {
|
||||||
|
@@ -9,6 +9,8 @@ package org.kde.kdeconnect.Backends.LoopbackBackend;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
public class LoopbackLinkProvider extends BaseLinkProvider {
|
public class LoopbackLinkProvider extends BaseLinkProvider {
|
||||||
@@ -31,14 +33,10 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void onNetworkChange() {
|
public void onNetworkChange() {
|
||||||
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
||||||
connectionAccepted(np, new LoopbackLink(context, this));
|
String deviceId = DeviceHelper.getDeviceId(context);
|
||||||
|
connectionAccepted(deviceId, SslHelper.certificate, np, new LoopbackLink(context, this));
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "LoopbackLinkProvider";
|
return "LoopbackLinkProvider";
|
||||||
|
@@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -28,23 +28,23 @@ 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.security.cert.CertificateException;
|
||||||
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 +59,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 +85,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,28 +131,21 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface PairingCallback {
|
// Remembered trusted device, we need to wait for a incoming Link to communicate
|
||||||
void incomingRequest();
|
Device(@NonNull Context context, @NonNull String deviceId) throws CertificateException {
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
//Log.e("Device","Constructor A");
|
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
|
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||||
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
|
||||||
|
|
||||||
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.protocolVersion = 0; //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"));
|
||||||
|
this.certificate = SslHelper.getDeviceCertificate(context, deviceId);
|
||||||
|
|
||||||
|
Log.i("Device","Loading trusted device: " + this.name);
|
||||||
|
|
||||||
//Assume every plugin is supported until addLink is called and we can get the actual list
|
//Assume every plugin is supported until addLink is called and we can get the actual list
|
||||||
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
||||||
@@ -173,21 +154,24 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
//reloadPluginsFromSettings();
|
//reloadPluginsFromSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
|
// Device known via an incoming connection sent to us via a Link, we don't trust it yet
|
||||||
Device(Context context, NetworkPacket np, BaseLink dl) {
|
Device(@NonNull Context context, @NonNull String deviceId, @NonNull Certificate certificate, @NonNull NetworkPacket identityPacket, @NonNull BaseLink dl) {
|
||||||
|
Log.i("Device","Creating untrusted device");
|
||||||
//Log.e("Device","Constructor B");
|
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.deviceId = np.getString("deviceId");
|
|
||||||
this.name = context.getString(R.string.unknown_device); //We read it in addLink
|
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||||
this.pairStatus = PairStatus.NotPaired;
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
|
||||||
this.protocolVersion = 0;
|
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
this.certificate = certificate;
|
||||||
|
|
||||||
|
// The following properties are read from the identityPacket in addLink since they can change in future identity packets
|
||||||
|
this.name = context.getString(R.string.unknown_device);
|
||||||
this.deviceType = DeviceType.Computer;
|
this.deviceType = DeviceType.Computer;
|
||||||
|
this.protocolVersion = 0;
|
||||||
|
|
||||||
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
addLink(identityPacket, dl);
|
||||||
|
|
||||||
addLink(np, dl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -221,144 +205,105 @@ 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(CertificateEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
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 +359,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// ComputerLink-related functions
|
// Link-related functions
|
||||||
//
|
//
|
||||||
|
|
||||||
public boolean isReachable() {
|
public boolean isReachable() {
|
||||||
@@ -442,54 +387,11 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
|
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identityPacket.has("certificate")) {
|
|
||||||
String certificateString = identityPacket.getString("certificate");
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
|
||||||
certificate = SslHelper.parseCertificate(certificateBytes);
|
|
||||||
Log.i("KDE/Device", "Got certificate ");
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("KDE/Device", "Error getting certificate", e);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 +406,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 +424,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()) {
|
||||||
|
@@ -120,4 +120,5 @@ class DevicePacketQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,14 +11,22 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.jaredrummler.android.device.DeviceName;
|
import com.univocity.parsers.csv.CsvParser;
|
||||||
|
import com.univocity.parsers.csv.CsvParserSettings;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -27,10 +35,13 @@ public class DeviceHelper {
|
|||||||
public static final int ProtocolVersion = 7;
|
public static final int ProtocolVersion = 7;
|
||||||
|
|
||||||
public static final String KEY_DEVICE_NAME_PREFERENCE = "device_name_preference";
|
public static final String KEY_DEVICE_NAME_PREFERENCE = "device_name_preference";
|
||||||
|
public static final String KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET = "device_name_downloaded_preference";
|
||||||
public static final String KEY_DEVICE_ID_PREFERENCE = "device_id_preference";
|
public static final String KEY_DEVICE_ID_PREFERENCE = "device_id_preference";
|
||||||
|
|
||||||
private static boolean fetchingName = false;
|
private static boolean fetchingName = false;
|
||||||
|
|
||||||
|
public static final String DEVICE_DATABASE = "https://storage.googleapis.com/play_public/supported_devices.csv";
|
||||||
|
|
||||||
private static boolean isTablet() {
|
private static boolean isTablet() {
|
||||||
Configuration config = Resources.getSystem().getConfiguration();
|
Configuration config = Resources.getSystem().getConfiguration();
|
||||||
//This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE
|
//This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE
|
||||||
@@ -52,35 +63,55 @@ public class DeviceHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//It returns getAndroidDeviceName() if no user-defined name has been set with setDeviceName().
|
|
||||||
public static String getDeviceName(Context context) {
|
public static String getDeviceName(Context context) {
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
// Could use preferences.contains but would need to check for empty String anyway.
|
if (!preferences.contains(KEY_DEVICE_NAME_PREFERENCE)
|
||||||
String deviceName = preferences.getString(KEY_DEVICE_NAME_PREFERENCE, "");
|
&& !preferences.getBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, false)
|
||||||
if (deviceName.isEmpty()) {
|
&& !fetchingName) {
|
||||||
//DeviceName.init(context); // Needed in DeviceName 2.x +
|
fetchingName = true;
|
||||||
if (!fetchingName) {
|
DeviceHelper.backgroundFetchDeviceName(context);
|
||||||
fetchingName = true;
|
return Build.MODEL;
|
||||||
DeviceHelper.backgroundFetchDeviceName(context); //Starts a background thread that will eventually update the shared pref
|
|
||||||
}
|
|
||||||
return DeviceName.getDeviceName(); //Temp name while we fetch it from the internet
|
|
||||||
}
|
}
|
||||||
return deviceName;
|
return preferences.getString(KEY_DEVICE_NAME_PREFERENCE, Build.MODEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void backgroundFetchDeviceName(final Context context) {
|
private static void backgroundFetchDeviceName(final Context context) {
|
||||||
DeviceName.with(context).request((info, error) -> {
|
ThreadHelper.execute(() -> {
|
||||||
|
try {
|
||||||
|
URL url = new URL(DEVICE_DATABASE);
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
|
// If we get here we managed to download the file. Mark that as done so we don't try again even if we don't end up finding a name.
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
preferences.edit().putBoolean(KEY_DEVICE_NAME_FETCHED_FROM_THE_INTERNET, true).apply();
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_16))) {
|
||||||
|
CsvParserSettings settings = new CsvParserSettings();
|
||||||
|
settings.setHeaderExtractionEnabled(true);
|
||||||
|
CsvParser parser = new CsvParser(settings);
|
||||||
|
boolean found = false;
|
||||||
|
for (String[] records : parser.iterate(reader)) {
|
||||||
|
if (records.length < 4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String buildModel = records[3];
|
||||||
|
if (Build.MODEL.equals(buildModel)) {
|
||||||
|
String deviceName = records[1];
|
||||||
|
Log.i("DeviceHelper", "Got device name: " + deviceName);
|
||||||
|
// Update the shared preference. Places that display the name should be listening to this change and update it
|
||||||
|
setDeviceName(context, deviceName);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
Log.e("DeviceHelper", "Didn't find a device name for " + Build.MODEL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
fetchingName = false;
|
fetchingName = false;
|
||||||
if (error != null) {
|
|
||||||
Log.e("DeviceHelper", "Error fetching device name");
|
|
||||||
error.printStackTrace();
|
|
||||||
}
|
|
||||||
if (info != null) {
|
|
||||||
String deviceName = info.getName();
|
|
||||||
Log.i("DeviceHelper", "Got device name: " + deviceName);
|
|
||||||
// Update the shared preference. Places that display the name should be listening to this change and update it
|
|
||||||
setDeviceName(context, deviceName);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -176,26 +176,29 @@ public class SslHelper {
|
|||||||
return !cert.isEmpty();
|
return !cert.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SSLContext getSslContext(Context context, String deviceId, boolean isDeviceTrusted) {
|
/**
|
||||||
|
* Returns the stored certificate for a trusted device
|
||||||
|
**/
|
||||||
|
public static Certificate getDeviceCertificate(Context context, String deviceId) throws CertificateException {
|
||||||
|
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||||
|
byte[] certificateBytes = Base64.decode(devicePreferences.getString("certificate", ""), 0);
|
||||||
|
return parseCertificate(certificateBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SSLContext getSslContextForDevice(Context context, String deviceId, boolean isDeviceTrusted) {
|
||||||
//TODO: Cache
|
//TODO: Cache
|
||||||
try {
|
try {
|
||||||
// Get device private key
|
// Get device private key
|
||||||
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
||||||
|
|
||||||
// Get remote device certificate if trusted
|
|
||||||
Certificate remoteDeviceCertificate = null;
|
|
||||||
if (isDeviceTrusted) {
|
|
||||||
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
|
||||||
byte[] certificateBytes = Base64.decode(devicePreferences.getString("certificate", ""), 0);
|
|
||||||
remoteDeviceCertificate = parseCertificate(certificateBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup keystore
|
// Setup keystore
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
keyStore.load(null, null);
|
keyStore.load(null, null);
|
||||||
keyStore.setKeyEntry("key", privateKey, "".toCharArray(), new Certificate[]{certificate});
|
keyStore.setKeyEntry("key", privateKey, "".toCharArray(), new Certificate[]{certificate});
|
||||||
// Set certificate if device trusted
|
|
||||||
if (remoteDeviceCertificate != null) {
|
// Add device certificate if device trusted
|
||||||
|
if (isDeviceTrusted) {
|
||||||
|
Certificate remoteDeviceCertificate = getDeviceCertificate(context, deviceId);
|
||||||
keyStore.setCertificateEntry(deviceId, remoteDeviceCertificate);
|
keyStore.setCertificateEntry(deviceId, remoteDeviceCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +242,7 @@ public class SslHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static SSLSocket convertToSslSocket(Context context, Socket socket, String deviceId, boolean isDeviceTrusted, boolean clientMode) throws IOException {
|
public static SSLSocket convertToSslSocket(Context context, Socket socket, String deviceId, boolean isDeviceTrusted, boolean clientMode) throws IOException {
|
||||||
SSLSocketFactory sslsocketFactory = SslHelper.getSslContext(context, deviceId, isDeviceTrusted).getSocketFactory();
|
SSLSocketFactory sslsocketFactory = SslHelper.getSslContextForDevice(context, deviceId, isDeviceTrusted).getSocketFactory();
|
||||||
SSLSocket sslsocket = (SSLSocket) sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
|
SSLSocket sslsocket = (SSLSocket) sslsocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true);
|
||||||
SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
|
SslHelper.configureSslSocket(sslsocket, isDeviceTrusted, clientMode);
|
||||||
return sslsocket;
|
return sslsocket;
|
||||||
|
@@ -5,6 +5,8 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
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.Helpers.DeviceHelper;
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
@@ -14,8 +16,11 @@ 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.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@@ -104,16 +109,21 @@ public class KdeConnect extends Application {
|
|||||||
for (String deviceId : trustedDevices) {
|
for (String deviceId : trustedDevices) {
|
||||||
//Log.e("BackgroundService", "Loading device "+deviceId);
|
//Log.e("BackgroundService", "Loading device "+deviceId);
|
||||||
if (preferences.getBoolean(deviceId, false)) {
|
if (preferences.getBoolean(deviceId, false)) {
|
||||||
Device device = new Device(this, deviceId);
|
try {
|
||||||
devices.put(deviceId, device);
|
Device device = new Device(this, deviceId);
|
||||||
device.addPairingCallback(devicePairingCallback);
|
devices.put(deviceId, device);
|
||||||
|
device.addPairingCallback(devicePairingCallback);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
Log.e("KdeConnect", "Could not load trusted device, certificate not valid: " + deviceId);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,15 +145,17 @@ public class KdeConnect extends Application {
|
|||||||
|
|
||||||
private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() {
|
private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) {
|
public void onConnectionReceived(@NonNull final String deviceId,
|
||||||
String deviceId = identityPacket.getString("deviceId");
|
@NonNull final Certificate certificate,
|
||||||
|
@NonNull final NetworkPacket identityPacket,
|
||||||
|
@NonNull final BaseLink link) {
|
||||||
Device device = devices.get(deviceId);
|
Device device = devices.get(deviceId);
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
Log.i("KDE/Application", "addLink, known device: " + deviceId);
|
Log.i("KDE/Application", "addLink, known device: " + deviceId);
|
||||||
device.addLink(identityPacket, link);
|
device.addLink(identityPacket, link);
|
||||||
} else {
|
} else {
|
||||||
Log.i("KDE/Application", "addLink,unknown device: " + deviceId);
|
Log.i("KDE/Application", "addLink,unknown device: " + deviceId);
|
||||||
device = new Device(KdeConnect.this, identityPacket, link);
|
device = new Device(KdeConnect.this, deviceId, certificate, identityPacket, link);
|
||||||
devices.put(deviceId, device);
|
devices.put(deviceId, device);
|
||||||
device.addPairingCallback(devicePairingCallback);
|
device.addPairingCallback(devicePairingCallback);
|
||||||
}
|
}
|
||||||
|
@@ -86,7 +86,7 @@ internal fun updateAppWidget(
|
|||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetId: Int
|
appWidgetId: Int
|
||||||
) {
|
) {
|
||||||
Log.i("WidgetProvider", "updateAppWidget: $appWidgetId")
|
Log.d("WidgetProvider", "updateAppWidget: $appWidgetId")
|
||||||
|
|
||||||
val deviceId = loadWidgetDeviceIdPref(context, appWidgetId)
|
val deviceId = loadWidgetDeviceIdPref(context, appWidgetId)
|
||||||
val device: Device? = if (deviceId != null) KdeConnect.getInstance().getDevice(deviceId) else null
|
val device: Device? = if (deviceId != null) KdeConnect.getInstance().getDevice(deviceId) else null
|
||||||
@@ -100,6 +100,8 @@ internal fun updateAppWidget(
|
|||||||
val setDevicePendingIntent = PendingIntent.getActivity(context, appWidgetId, setDeviceIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
val setDevicePendingIntent = PendingIntent.getActivity(context, appWidgetId, setDeviceIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
views.setOnClickPendingIntent(R.id.runcommandWidgetTitleHeader, setDevicePendingIntent)
|
views.setOnClickPendingIntent(R.id.runcommandWidgetTitleHeader, setDevicePendingIntent)
|
||||||
|
|
||||||
|
Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name)
|
||||||
|
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
views.setTextViewText(R.id.runcommandWidgetTitle, context.getString(R.string.kde_connect))
|
views.setTextViewText(R.id.runcommandWidgetTitle, context.getString(R.string.kde_connect))
|
||||||
views.setViewVisibility(R.id.run_commands_list, View.VISIBLE)
|
views.setViewVisibility(R.id.run_commands_list, View.VISIBLE)
|
||||||
|
@@ -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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
205
src/org/kde/kdeconnect/UserInterface/PairingHandler.java
Normal file
205
src/org/kde/kdeconnect/UserInterface/PairingHandler.java
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
break;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable e) {
|
||||||
|
cancelTimer();
|
||||||
|
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,21 +28,24 @@ 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.Helpers.SecurityHelpers.SslHelper;
|
||||||
|
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;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({Base64.class, Log.class, PreferenceManager.class, ContextCompat.class})
|
@PrepareForTest({Base64.class, Log.class, PreferenceManager.class, ContextCompat.class})
|
||||||
@@ -57,16 +60,22 @@ public class DeviceTest {
|
|||||||
|
|
||||||
String deviceId = "testDevice";
|
String deviceId = "testDevice";
|
||||||
String name = "Test Device";
|
String name = "Test Device";
|
||||||
|
String encodedCertificate = "MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
|
||||||
KeyPair keyPair;
|
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
|
||||||
try {
|
"bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" +
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
"MV9mMDY0XzQ3OGVfYmU4Y18xOTM1ZDc1NDRkNTRfMQwwCgYDVQQKDANLREUxFDASBgNVBAsMC0tk\n" +
|
||||||
keyGen.initialize(2048);
|
"ZSBjb25uZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzH9GxS1lctpwYdSGAoPH\n" +
|
||||||
keyPair = keyGen.genKeyPair();
|
"ws+MnVaL0PVDCuzrpxzXc+bChR87xofhQIesLPLZEcmUJ1MlEJ6jx4W+gVhvY2tUN7SoiKKbnq8s\n" +
|
||||||
} catch (Exception e) {
|
"WjI5ovs5yML3C1zPbOSJAdK613FcdkK+UGd/9dQk54gIozinC58iyTAChVVpB3pAF38EPxwKkuo2\n" +
|
||||||
Log.e("KDE/initializeRsaKeys", "Exception", e);
|
"qTzwk24d6PRxz1skkzwEphUQQzGboyHsAlJHN1MzM2/yFGB4l8iUua2d3ETyfy/xFEh/SwtGtXE5\n" +
|
||||||
return;
|
"KLz4cpb0fxjeYQZVruBKxzE07kgDO3zOhmP3LJ/KSPHWYImd1DWmpY9iDvoXr6+V7FAnRloaEIyg\n" +
|
||||||
}
|
"7WwdlSCpo3TXVuIjLwIDAQABozIwMDAdBgNVHQ4EFgQUwmbHo8YbiR463GRKSLL3eIKyvDkwDwYD\n" +
|
||||||
|
"VR0TAQH/BAUwAwIBADANBgkqhkiG9w0BAQUFAAOCAQEAydijH3rbnvpBDB/30w2PCGMT7O0N/XYM\n" +
|
||||||
|
"wBtUidqa4NFumJrNrccx5Ehp4UP66BfP61HW8h2U/EekYfOsZyyWd4KnsDD6ycR8h/WvpK3BC2cn\n" +
|
||||||
|
"I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" +
|
||||||
|
"/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" +
|
||||||
|
"yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" +
|
||||||
|
"7n+KOQ==";
|
||||||
|
|
||||||
this.context = Mockito.mock(Context.class);
|
this.context = Mockito.mock(Context.class);
|
||||||
|
|
||||||
@@ -81,7 +90,7 @@ public class DeviceTest {
|
|||||||
SharedPreferences.Editor editor = deviceSettings.edit();
|
SharedPreferences.Editor editor = deviceSettings.edit();
|
||||||
editor.putString("deviceName", name);
|
editor.putString("deviceName", name);
|
||||||
editor.putString("deviceType", Device.DeviceType.Phone.toString());
|
editor.putString("deviceType", Device.DeviceType.Phone.toString());
|
||||||
editor.putString("publicKey", Base64.encodeToString(keyPair.getPublic().getEncoded(), 0).trim() + "\n");
|
editor.putString("certificate", encodedCertificate);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
|
Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
|
||||||
|
|
||||||
@@ -116,71 +125,25 @@ public class DeviceTest {
|
|||||||
|
|
||||||
// Basic paired device testing
|
// Basic paired device testing
|
||||||
@Test
|
@Test
|
||||||
public void testDevice() {
|
public void testDevice() throws CertificateException {
|
||||||
Device device = new Device(context, "testDevice");
|
Device device = new Device(context, "testDevice");
|
||||||
|
|
||||||
assertEquals(device.getDeviceId(), "testDevice");
|
assertEquals(device.getDeviceId(), "testDevice");
|
||||||
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
||||||
assertEquals(device.getName(), "Test Device");
|
assertEquals(device.getName(), "Test Device");
|
||||||
assertTrue(device.isPaired());
|
assertTrue(device.isPaired());
|
||||||
|
assertNotNull(device.certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Testing pairing done
|
public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException {
|
||||||
// Created an unpaired device inside this test
|
|
||||||
@Test
|
|
||||||
public void testPairingDone() {
|
|
||||||
NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
|
NetworkPacket fakeNetworkPacket = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
|
||||||
fakeNetworkPacket.set("deviceId", "unpairedTestDevice");
|
String deviceId = "unpairedTestDevice";
|
||||||
|
fakeNetworkPacket.set("deviceId", deviceId);
|
||||||
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
||||||
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
|
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
|
||||||
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
||||||
|
String certificateString =
|
||||||
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);
|
|
||||||
fakeNetworkPacket.set("deviceId", "unpairedTestDevice");
|
|
||||||
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
|
||||||
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
|
|
||||||
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
|
||||||
fakeNetworkPacket.set("certificate",
|
|
||||||
"MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
|
"MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
|
||||||
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
|
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
|
||||||
"bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" +
|
"bmVjdDAeFw0xNTA2MDMxMzE0MzhaFw0yNTA2MDMxMzE0MzhaMFUxLzAtBgNVBAMMJl9kYTE3OWE5\n" +
|
||||||
@@ -196,29 +159,25 @@ public class DeviceTest {
|
|||||||
"I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" +
|
"I299wbqCEZmk5ZFFaEIDHdLAdgMCuxJkAzy9mMrWEa05Soxi2/ZXdrU9nXo5dzuPGYlirVPDHl7r\n" +
|
||||||
"/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" +
|
"/urBxD6HVX3ObQJRJ7r/nAWyUVdX3/biJaDRsydftOpGU6Gi5c1JK4MWIz8Bsjh6mEjCsVatbPPl\n" +
|
||||||
"yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" +
|
"yygGiJbDZfAvN2XoaVEBii2GDDCWfaFwPVPYlNTvjkUkMP8YThlMsiJ8Q4693XoLOL94GpNlCfUg\n" +
|
||||||
"7n+KOQ==");
|
"7n+KOQ==";
|
||||||
|
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
||||||
|
|
||||||
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, deviceId, certificate, fakeNetworkPacket, link);
|
||||||
|
|
||||||
assertNotNull(device);
|
assertNotNull(device);
|
||||||
assertEquals(device.getDeviceId(), "unpairedTestDevice");
|
assertEquals(device.getDeviceId(), deviceId);
|
||||||
assertEquals(device.getName(), "Unpaired Test Device");
|
assertEquals(device.getName(), "Unpaired Test Device");
|
||||||
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());
|
||||||
|
|
||||||
@@ -235,8 +194,8 @@ public class DeviceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUnpair() {
|
public void testUnpair() throws CertificateException {
|
||||||
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);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user