diff --git a/src/org/kde/kdeconnect/Backends/BaseLink.java b/src/org/kde/kdeconnect/Backends/BaseLink.java index 0758cb4f..42589539 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLink.java +++ b/src/org/kde/kdeconnect/Backends/BaseLink.java @@ -44,6 +44,10 @@ public abstract class BaseLink { this.deviceId = deviceId; } + /* To be implemented by each link for pairing handlers */ + public abstract String getName(); + public abstract BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback); + public String getDeviceId() { return deviceId; } diff --git a/src/org/kde/kdeconnect/Backends/BasePairingHandler.java b/src/org/kde/kdeconnect/Backends/BasePairingHandler.java index ac2c2e2f..902bb1d2 100644 --- a/src/org/kde/kdeconnect/Backends/BasePairingHandler.java +++ b/src/org/kde/kdeconnect/Backends/BasePairingHandler.java @@ -23,14 +23,70 @@ package org.kde.kdeconnect.Backends; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPackage; -public interface BasePairingHandler { +import java.util.Timer; - NetworkPackage createPairPackage(Device device); - void packageReceived(Device device, NetworkPackage np) throws Exception; - void requestPairing(Device device, Device.SendPackageStatusCallback callback); - void acceptPairing(Device device, Device.SendPackageStatusCallback callback); - void rejectPairing(Device device); - void pairingDone(Device device); - void unpair(Device device); +/** + * 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 Device mDevice; + protected BaseLink mBaseLink; + protected PairStatus mPairStatus; + protected PairingHandlerCallback mCallback; + protected Timer mPairingTimer; + + public BasePairingHandler(Device device, PairingHandlerCallback callback) { + this.mDevice = device; + this.mCallback = callback; + } + + public void setLink(BaseLink baseLink) { + this.mBaseLink = baseLink; + } + + public 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 NetworkPackage createPairPackage(); + public abstract void packageReceived(NetworkPackage np) throws Exception; + public abstract void requestPairing(); + public abstract void acceptPairing(); + public abstract void rejectPairing(); + public abstract void pairingDone(); + public abstract void unpair(); } diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java index 61225724..2f324cf1 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -27,6 +27,7 @@ import android.util.Log; import org.json.JSONObject; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; @@ -40,7 +41,9 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.nio.channels.NotYetConnectedException; +import java.security.PrivateKey; import java.security.PublicKey; +import java.util.ArrayList; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; @@ -80,6 +83,16 @@ public class LanLink extends BaseLink { return startTime; } + @Override + public String getName() { + return "LanLink"; + } + + @Override + public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) { + return new LanPairingHandler(device, callback); + } + @Override public void addPackageReceiver(PackageReceiver pr) { super.addPackageReceiver(pr); @@ -91,7 +104,7 @@ public class LanLink extends BaseLink { if (!device.isPaired()) return; // If the device is already paired due to other link, just send a pairing request to get required attributes for this link if (device.publicKey == null) { - getLinkProvider().getPairingHandler().requestPairing(device, null); + device.requestPairing(); } } }); diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index acda64c1..deec6fdd 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -416,8 +416,6 @@ public class LanLinkProvider extends BaseLinkProvider { clientGroup = new NioEventLoopGroup(); - pairingHandler = new LanPairingHandler(); - } @Override diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java index f04eb6d0..1b5c448e 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanPairingHandler.java @@ -26,9 +26,7 @@ import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; -import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BasePairingHandler; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect_tp.R; @@ -36,71 +34,202 @@ import org.kde.kdeconnect_tp.R; import java.security.KeyFactory; import java.security.cert.CertificateEncodingException; import java.security.spec.X509EncodedKeySpec; +import java.util.Timer; +import java.util.TimerTask; -public class LanPairingHandler implements BasePairingHandler { +public class LanPairingHandler extends BasePairingHandler { + + public LanPairingHandler(Device device, final PairingHandlerCallback callback) { + super(device, callback); + + if (device.isPaired()) { + if (device.publicKey != null) { + mPairStatus = PairStatus.Paired; + } else { + /* Request pairing if device is paired but public key is not there */ + requestPairing(); + } + } else { + mPairStatus = PairStatus.NotPaired; + } + } @Override - public NetworkPackage createPairPackage(Device device) { + public NetworkPackage createPairPackage() { NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR); np.set("pair", true); - SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(device.getContext()); + np.set("link", mBaseLink.getName()); + SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(mDevice.getContext()); String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n"; np.set("publicKey", publicKey); return np; } @Override - public void packageReceived(Device device, NetworkPackage np) throws Exception{ - try { - String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", ""); - byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0); - device.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes)); - } catch(Exception e) { - e.printStackTrace(); - Log.e("KDE/Device","Pairing exception: Received incorrect key"); - throw e; - } - } + public void packageReceived(NetworkPackage np) throws Exception{ - @Override - public void requestPairing(Device device, Device.SendPackageStatusCallback callback) { - if (callback == null) { - device.sendPackage(createPairPackage(device)); + if (!np.getString("link").equals(mBaseLink.getName())) { + return; + } + + boolean wantsPair = np.getBoolean("pair"); + + Log.e("KDE/LPHPackageReceived", "Wants pair " + wantsPair + ", isPaired " + isPaired() ); + + if (wantsPair == isPaired()) { + if (mPairStatus == PairStatus.Requested) { + //Log.e("Device","Unpairing (pair rejected)"); + mPairStatus = PairStatus.NotPaired; + if (mPairingTimer != null) mPairingTimer.cancel(); + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer)); + return; + } else if (mPairStatus == PairStatus.Paired) { + /** + * If wants pair is true and is paired is true, this means other device is trying to pair again, might be because it unpaired this device somehow + * and we don't know it, unpair it internally + */ + mCallback.unpaired(); + } + } + + if (wantsPair) { + + //Retrieve their public key + try { + String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", ""); + byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0); + mDevice.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes)); + } catch (Exception e) { + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_invalid_key)); + } + + if (mPairStatus == PairStatus.Requested) { //We started pairing + + Log.i("KDE/Pairing","Pair answer"); + + if (mPairingTimer != null) mPairingTimer.cancel(); + + pairingDone(); + + } else { + + Log.i("KDE/Pairing", "Pair request"); + + /** + * 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 + */ + mDevice.displayPairingNotification(); + + if (mPairingTimer != null) mPairingTimer.cancel(); + mPairingTimer = new Timer(); + + mPairingTimer.schedule(new TimerTask() { + @Override + public void run() { + Log.e("KDE/Device","Unpairing (timeout B)"); + mPairStatus = PairStatus.NotPaired; + + mDevice.cancelPairingNotification(); + } + }, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) + mPairStatus = PairStatus.RequestedByPeer; + mCallback.incomingRequest(); + + } } else { - device.sendPackage(createPairPackage(device), callback); + Log.i("KDE/Pairing", "Unpair request"); + + if (mPairStatus == PairStatus.Requested) { + if (mPairingTimer != null) mPairingTimer.cancel(); + 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 acceptPairing(Device device, Device.SendPackageStatusCallback callback) { - if (callback == null) { - device.sendPackage(createPairPackage(device)); - } else { - device.sendPackage(createPairPackage(device), callback); - } + public void requestPairing() { + + Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() { + @Override + protected void onSuccess() { + if (mPairingTimer != null) mPairingTimer.cancel(); + mPairingTimer = new Timer(); + mPairingTimer.schedule(new TimerTask() { + @Override + public void run() { + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out)); + Log.e("KDE/Device","Unpairing (timeout A)"); + mPairStatus = PairStatus.NotPaired; + } + }, 30*1000); //Time to wait for the other to accept + mPairStatus = PairStatus.Requested; + } + + @Override + protected void onFailure(Throwable e) { + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package)); + } + }; + mDevice.sendPackage(createPairPackage(), statusCallback); } @Override - public void rejectPairing(Device device) { + public void acceptPairing() { + if (mPairingTimer != null) mPairingTimer.cancel(); + Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() { + @Override + protected void onSuccess() { + pairingDone(); + } + + @Override + protected void onFailure(Throwable e) { + mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable)); + } + }; + mDevice.sendPackage(createPairPackage(), statusCallback); + } + + @Override + public void rejectPairing() { + if (mPairingTimer != null) mPairingTimer.cancel(); + mPairStatus = PairStatus.NotPaired; NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR); np.set("pair", false); - device.sendPackage(np); + np.set("link", mBaseLink.getName()); + mDevice.sendPackage(np); } @Override - public void pairingDone(Device device) { - //Store device information needed to create a Device object in a future - SharedPreferences.Editor editor = device.getContext().getSharedPreferences(device.getDeviceId(), Context.MODE_PRIVATE).edit(); + public 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 encodedPublicKey = Base64.encodeToString(device.publicKey.getEncoded(), 0); + String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0); editor.putString("publicKey", encodedPublicKey); } catch (Exception e) { Log.e("KDE/PairingDone", "Error encoding public key"); } try { - String encodedCertificate = Base64.encodeToString(device.certificate.getEncoded(), 0); + String encodedCertificate = Base64.encodeToString(mDevice.certificate.getEncoded(), 0); editor.putString("certificate", encodedCertificate); } catch (NullPointerException n) { Log.e("KDE/PairingDone", "Certificate is null, remote device does not support ssl"); @@ -112,12 +241,17 @@ public class LanPairingHandler implements BasePairingHandler { } editor.apply(); + mPairStatus = PairStatus.Paired; + mCallback.pairingDone(); + } @Override - public void unpair(Device device) { + public void unpair() { + mPairStatus = PairStatus.NotPaired; NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR); np.set("pair", false); - device.sendPackage(np); + np.set("link", mBaseLink.getName()); + mDevice.sendPackage(np); } } diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java index e63815d8..16a88b83 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -24,11 +24,13 @@ import android.content.Context; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; import org.kde.kdeconnect.NetworkPackage; import java.security.PublicKey; +import java.util.ArrayList; public class LoopbackLink extends BaseLink { @@ -36,6 +38,16 @@ public class LoopbackLink extends BaseLink { super(context, "loopback", linkProvider); } + @Override + public String getName() { + return "LoopbackLink"; + } + + @Override + public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) { + return new LoopbackPairingHandler(device, callback); + } + @Override public void sendPackage(NetworkPackage in, Device.SendPackageStatusCallback callback) { sendPackageEncrypted(in, callback, null); diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java index 8c06eea2..c25ae39d 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java @@ -31,7 +31,6 @@ public class LoopbackLinkProvider extends BaseLinkProvider { public LoopbackLinkProvider(Context context) { this.context = context; - pairingHandler = new LoopbackPairingHandler(); } @Override diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java index 1abf6f5e..7b0c2d30 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackPairingHandler.java @@ -21,7 +21,12 @@ package org.kde.kdeconnect.Backends.LoopbackBackend; import org.kde.kdeconnect.Backends.LanBackend.LanPairingHandler; +import org.kde.kdeconnect.Device; public class LoopbackPairingHandler extends LanPairingHandler{ + + public LoopbackPairingHandler(Device device, PairingHandlerCallback callback) { + super(device, callback); + } // Extending from LanPairingHandler, as it is similar to it } diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index 221ccdd4..e08ddc30 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -36,6 +36,7 @@ import android.util.Base64; import android.util.Log; import org.kde.kdeconnect.Backends.BaseLink; +import org.kde.kdeconnect.Backends.BasePairingHandler; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.PairActivity; @@ -52,9 +53,8 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; public class Device implements BaseLink.PackageReceiver { @@ -69,8 +69,6 @@ public class Device implements BaseLink.PackageReceiver { public enum PairStatus { NotPaired, - Requested, - RequestedByPeer, Paired } @@ -94,16 +92,16 @@ public class Device implements BaseLink.PackageReceiver { } public interface PairingCallback { - abstract void incomingRequest(); - abstract void pairingSuccessful(); - abstract void pairingFailed(String error); - abstract void unpaired(); + void incomingRequest(); + void pairingSuccessful(); + void pairingFailed(String error); + void unpaired(); } private DeviceType deviceType; private PairStatus pairStatus; private ArrayList pairingCallback = new ArrayList(); - private Timer pairingTimer; + private Map pairingHandlers = new HashMap(); private final ArrayList links = new ArrayList(); private final HashMap plugins = new HashMap(); @@ -196,16 +194,31 @@ public class Device implements BaseLink.PackageReceiver { return pairStatus == PairStatus.Paired; } + /* Asks all pairing handlers that, is pair requested? */ public boolean isPairRequested() { - return pairStatus == PairStatus.Requested; + boolean pairRequested = false; + for (BasePairingHandler ph: pairingHandlers.values()) { + pairRequested = pairRequested || ph.isPairRequested(); + } + return pairRequested; + } + + /* Asks all pairing handlers that, is pair requested by peer? */ + public boolean isPairRequestedByPeer() { + boolean pairRequestedByPeer = false; + for (BasePairingHandler ph : pairingHandlers.values()) { + pairRequestedByPeer = pairRequestedByPeer || ph.isPairRequestedByPeer(); + } + return pairRequestedByPeer; } public void addPairingCallback(PairingCallback callback) { pairingCallback.add(callback); - if (pairStatus == PairStatus.RequestedByPeer) { + if (isPairRequestedByPeer()) { callback.incomingRequest(); } } + public void removePairingCallback(PairingCallback callback) { pairingCallback.remove(callback); } @@ -220,15 +233,6 @@ public class Device implements BaseLink.PackageReceiver { cb.pairingFailed(res.getString(R.string.error_already_paired)); } return; - case Requested: - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(res.getString(R.string.error_already_requested)); - } - return; - case RequestedByPeer: - Log.d("requestPairing", "Pairing already started by the other end, accepting their request."); - acceptPairing(); - return; case NotPaired: ; } @@ -240,73 +244,47 @@ public class Device implements BaseLink.PackageReceiver { return; } - SendPackageStatusCallback callback = new SendPackageStatusCallback() { - @Override - public void onSuccess() { - if (pairingTimer != null) pairingTimer.cancel(); - pairingTimer = new Timer(); - pairingTimer.schedule(new TimerTask() { - @Override - public void run() { - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_timed_out)); - } - Log.e("KDE/Device","Unpairing (timeout A)"); - pairStatus = PairStatus.NotPaired; - } - }, 30*1000); //Time to wait for the other to accept - pairStatus = PairStatus.Requested; - } - - @Override - public void onFailure(Throwable e) { - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_could_not_send_package)); - } - Log.e("KDE/Device","Unpairing (sendFailed A)"); - pairStatus = PairStatus.NotPaired; - } - }; - - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - link.getLinkProvider().getPairingHandler().requestPairing(this,callback); + for (BasePairingHandler ph : pairingHandlers.values()) { + ph.requestPairing(); } } - public int getNotificationId() { - return notificationId; - } - public void unpair() { - //Log.e("Device","Unpairing (unpair)"); + for (BasePairingHandler ph : pairingHandlers.values()) { + ph.unpair(); + } + unpairInternal(); // Even if there are no pairing handlers, unpair + } + + /** + * This method does not send an unpair package, instead it unpairs internally by deleting trusted device info. . Likely to be called after sending package 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(); + // FIXME : We delete all device info here, but the xml file still persists SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); devicePreferences.edit().clear().apply(); - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - link.getLinkProvider().getPairingHandler().unpair(this); - } - 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); - if (pairingTimer != null) pairingTimer.cancel(); - pairStatus = PairStatus.Paired; //Store as trusted device @@ -318,11 +296,6 @@ public class Device implements BaseLink.PackageReceiver { editor.putString("deviceType", deviceType.toString()); editor.apply(); - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - link.getLinkProvider().getPairingHandler().pairingDone(this); - } - reloadPluginsFromSettings(); for (PairingCallback cb : pairingCallback) { @@ -331,32 +304,18 @@ public class Device implements BaseLink.PackageReceiver { } + /* This method is called after accepting pair request form GUI */ public void acceptPairing() { Log.i("KDE/Device", "Accepted pair request started by the other device"); - SendPackageStatusCallback callback = new SendPackageStatusCallback() { - @Override - protected void onSuccess() { - pairingDone(); - } - @Override - protected void onFailure(Throwable e) { - Log.e("Device","Unpairing (sendFailed B)"); - pairStatus = PairStatus.NotPaired; - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_not_reachable)); - } - } - }; - - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - link.getLinkProvider().getPairingHandler().acceptPairing(this, callback); + for (BasePairingHandler ph : pairingHandlers.values()) { + ph.acceptPairing(); } } + /* This method is called after rejecting pairing from GUI */ public void rejectPairing() { Log.i("KDE/Device", "Rejected pair request started by the other device"); @@ -364,9 +323,8 @@ public class Device implements BaseLink.PackageReceiver { //Log.e("Device","Unpairing (rejectPairing)"); pairStatus = PairStatus.NotPaired; - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - link.getLinkProvider().getPairingHandler().rejectPairing(this); + for (BasePairingHandler ph : pairingHandlers.values()) { + ph.rejectPairing(); } for (PairingCallback cb : pairingCallback) { @@ -375,7 +333,48 @@ public class Device implements BaseLink.PackageReceiver { } + // + // Notification related methods used during pairing + // + public int getNotificationId() { + return notificationId; + } + public void displayPairingNotification() { + + notificationId = (int)System.currentTimeMillis(); + + Intent intent = new Intent(getContext(), PairActivity.class); + intent.putExtra("deviceId", getDeviceId()); + intent.putExtra("notificationId", notificationId); + PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_ONE_SHOT); + + Resources res = getContext().getResources(); + + Notification noti = new NotificationCompat.Builder(getContext()) + .setContentTitle(res.getString(R.string.pairing_request_from, getName())) + .setContentText(res.getString(R.string.tap_to_answer)) + .setContentIntent(pendingIntent) + .setTicker(res.getString(R.string.pair_requested)) + .setSmallIcon(android.R.drawable.ic_menu_help) + .setAutoCancel(true) + .setDefaults(Notification.DEFAULT_ALL) + .build(); + + final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); + + try { + notificationManager.notify(notificationId, noti); + } catch(Exception e) { + //4.1 will throw an exception about not having the VIBRATE permission, ignore it. + //https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/ + } + } + + public void cancelPairingNotification() { + final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(notificationId); + } // @@ -432,6 +431,36 @@ public class Device implements BaseLink.PackageReceiver { 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)); + } + pairingHandlers.get(link.getName()).setLink(link); + /* Collections.sort(links, new Comparator() { @Override @@ -451,6 +480,18 @@ public class Device implements BaseLink.PackageReceiver { public void removeLink(BaseLink link) { //FilesHelper.LogOpenFileCount(); + /* Remove pairing handler corresponding to that link too if it was the only link*/ + boolean linkPresent = false; + for (BaseLink bl : links) { + if (bl.getName().equals(link.getName())) { + linkPresent = true; + break; + } + } + if (!linkPresent) { + pairingHandlers.remove(link.getName()); + } + link.removePackageReceiver(this); links.remove(link); Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size()); @@ -465,113 +506,15 @@ public class Device implements BaseLink.PackageReceiver { if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) { Log.i("KDE/Device", "Pair package"); - - boolean wantsPair = np.getBoolean("pair"); - - if (wantsPair == isPaired()) { - if (pairStatus == PairStatus.Requested) { - //Log.e("Device","Unpairing (pair rejected)"); - pairStatus = PairStatus.NotPaired; - if (pairingTimer != null) pairingTimer.cancel(); - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer)); - } - } else if (pairStatus == PairStatus.Paired) { - // If I consider the device be paired, but still it is sending a pair request send my pairing package - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - // Send required link attributes for link - link.getLinkProvider().getPairingHandler().acceptPairing(this, null); - } + Log.e("KDE/Device", "Pairing Handler Count " + pairingHandlers.size()); + for (BasePairingHandler ph: pairingHandlers.values()) { + try { + ph.packageReceived(np); + } catch (Exception e) { + // There should be no exception here } - return; } - if (wantsPair) { - - //Retrieve their public key - ArrayList mLinks = new ArrayList(links); - for (final BaseLink link : mLinks) { - try { - link.getLinkProvider().getPairingHandler().packageReceived(this, np); - } catch (Exception e) { - for (Device.PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_invalid_key)); - } - } - } - - if (pairStatus == PairStatus.Requested) { //We started pairing - - Log.i("KDE/Pairing","Pair answer"); - - if (pairingTimer != null) pairingTimer.cancel(); - - pairingDone(); - - } else { - - Log.i("KDE/Pairing","Pair request"); - - Intent intent = new Intent(context, PairActivity.class); - intent.putExtra("deviceId", deviceId); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT); - - Resources res = context.getResources(); - - Notification noti = new NotificationCompat.Builder(context) - .setContentTitle(res.getString(R.string.pairing_request_from, getName())) - .setContentText(res.getString(R.string.tap_to_answer)) - .setContentIntent(pendingIntent) - .setTicker(res.getString(R.string.pair_requested)) - .setSmallIcon(android.R.drawable.ic_menu_help) - .setAutoCancel(true) - .setDefaults(Notification.DEFAULT_ALL) - .build(); - - - final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - notificationId = (int)System.currentTimeMillis(); - try { - notificationManager.notify(notificationId, noti); - } catch(Exception e) { - //4.1 will throw an exception about not having the VIBRATE permission, ignore it. - //https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/ - } - - if (pairingTimer != null) pairingTimer.cancel(); - pairingTimer = new Timer(); - - pairingTimer.schedule(new TimerTask() { - @Override - public void run() { - Log.e("KDE/Device","Unpairing (timeout B)"); - pairStatus = PairStatus.NotPaired; - notificationManager.cancel(notificationId); - } - }, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds) - pairStatus = PairStatus.RequestedByPeer; - for (PairingCallback cb : pairingCallback) cb.incomingRequest(); - - } - } else { - Log.i("KDE/Pairing","Unpair request"); - - if (pairStatus == PairStatus.Requested) { - pairingTimer.cancel(); - for (PairingCallback cb : pairingCallback) { - cb.pairingFailed(context.getString(R.string.error_canceled_by_other_peer)); - } - } else if (pairStatus == PairStatus.Paired) { - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().remove(deviceId).apply(); - reloadPluginsFromSettings(); - } - - pairStatus = PairStatus.NotPaired; - for (PairingCallback cb : pairingCallback) cb.unpaired(); - - } } else if (isPaired()) { for (Plugin plugin : plugins.values()) { @@ -588,9 +531,11 @@ public class Device implements BaseLink.PackageReceiver { Log.e("KDE/onPackageReceived","Device not paired, ignoring package!"); - if (pairStatus != PairStatus.Requested) { - unpair(); - } + // If it is pair package, it should be captured by "if" at start + // If not and device is paired, it should be captured by isPaired + // Else unpair, this handles the situation when one device unpairs, but other dont know like unpairing when wi-fi is off + + unpair(); }