File indexing completed on 2024-12-22 04:41:43

0001 /*
0002  * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 package org.kde.kdeconnect;
0008 
0009 import android.util.Log;
0010 
0011 import org.kde.kdeconnect_tp.R;
0012 
0013 import java.util.Timer;
0014 import java.util.TimerTask;
0015 
0016 public class PairingHandler {
0017 
0018     public enum PairState {
0019         NotPaired,
0020         Requested,
0021         RequestedByPeer,
0022         Paired
0023     }
0024 
0025     public interface PairingCallback {
0026         void incomingPairRequest();
0027 
0028         void pairingFailed(String error);
0029 
0030         void pairingSuccessful();
0031 
0032         void unpaired();
0033     }
0034 
0035     protected final Device mDevice;
0036     protected PairState mPairState;
0037     protected final PairingCallback mCallback;
0038 
0039     public PairState getState() {
0040         return mPairState;
0041     }
0042 
0043     private Timer mPairingTimer;
0044 
0045     public PairingHandler(Device device, final PairingCallback callback, PairState initialState) {
0046         this.mDevice = device;
0047         this.mCallback = callback;
0048         this.mPairState = initialState;
0049     }
0050 
0051     public void packetReceived(NetworkPacket np) {
0052         cancelTimer();
0053         boolean wantsPair = np.getBoolean("pair");
0054         if (wantsPair) {
0055             switch (mPairState) {
0056                 case Requested: // We started pairing and tis is a confirmation
0057                     pairingDone();
0058                     break;
0059                 case RequestedByPeer:
0060                     Log.w("PairingHandler", "Ignoring second pairing request before the first one timed out");
0061                     break;
0062                 case Paired:
0063                 case NotPaired:
0064                     if (mPairState == PairState.Paired) {
0065                         Log.w("PairingHandler", "Received pairing request from a device we already trusted.");
0066                         // It would be nice to auto-accept the pairing request here, but since the pairing accept and pairing request
0067                         // messages are identical, this could create an infinite loop if both devices are "accepting" each other pairs.
0068                         // Instead, unpair and handle as if "NotPaired".
0069                         mPairState = PairState.NotPaired;
0070                         mCallback.unpaired();
0071                     }
0072                     mPairState = PairState.RequestedByPeer;
0073 
0074                     mPairingTimer = new Timer();
0075                     mPairingTimer.schedule(new TimerTask() {
0076                         @Override
0077                         public void run() {
0078                             Log.w("PairingHandler", "Unpairing (timeout after we started pairing)");
0079                             mPairState = PairState.NotPaired;
0080                             mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
0081                         }
0082                     }, 25 * 1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
0083 
0084                     mCallback.incomingPairRequest();
0085                     break;
0086             }
0087         } else {
0088             Log.i("PairingHandler", "Unpair request received");
0089             switch (mPairState) {
0090                 case NotPaired:
0091                     Log.i("PairingHandler", "Ignoring unpair request for already unpaired device");
0092                     break;
0093                 case Requested: // We started pairing and got rejected
0094                 case RequestedByPeer: // They stared pairing, then cancelled
0095                     mPairState = PairState.NotPaired;
0096                     mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
0097                     break;
0098                 case Paired:
0099                     mPairState = PairState.NotPaired;
0100                     mCallback.unpaired();
0101                     break;
0102             }
0103         }
0104     }
0105 
0106     public void requestPairing() {
0107         cancelTimer();
0108 
0109         if (mPairState == PairState.Paired) {
0110             Log.w("PairingHandler", "requestPairing was called on an already paired device");
0111             mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_already_paired));
0112             return;
0113         }
0114 
0115         if (mPairState == PairState.RequestedByPeer) {
0116             Log.w("PairingHandler", "Pairing already started by the other end, accepting their request.");
0117             acceptPairing();
0118             return;
0119         }
0120 
0121         if (!mDevice.isReachable()) {
0122             mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
0123             return;
0124         }
0125 
0126         mPairState = PairState.Requested;
0127 
0128         mPairingTimer = new Timer();
0129         mPairingTimer.schedule(new TimerTask() {
0130             @Override
0131             public void run() {
0132                 Log.w("PairingHandler","Unpairing (timeout after receiving pair request)");
0133                 mPairState = PairState.NotPaired;
0134                 mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
0135             }
0136         }, 30*1000); //Time to wait for the other to accept
0137 
0138         Device.SendPacketStatusCallback statusCallback = new Device.SendPacketStatusCallback() {
0139             @Override
0140             public void onSuccess() { }
0141 
0142             @Override
0143             public void onFailure(Throwable e) {
0144                 cancelTimer();
0145                 Log.e("PairingHandler", "Exception sending pairing request", e);
0146                 mPairState = PairState.NotPaired;
0147                 mCallback.pairingFailed(mDevice.getContext().getString(R.string.runcommand_notreachable));
0148             }
0149         };
0150         NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
0151         np.set("pair", true);
0152         mDevice.sendPacket(np, statusCallback);
0153     }
0154 
0155     public void acceptPairing() {
0156         cancelTimer();
0157         Device.SendPacketStatusCallback StateCallback = new Device.SendPacketStatusCallback() {
0158             @Override
0159             public void onSuccess() {
0160                 pairingDone();
0161             }
0162 
0163             @Override
0164             public void onFailure(Throwable e) {
0165                 Log.e("PairingHandler", "Exception sending accept pairing packet", e);
0166                 mPairState = PairState.NotPaired;
0167                 mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
0168             }
0169         };
0170         NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
0171         np.set("pair", true);
0172         mDevice.sendPacket(np, StateCallback);
0173     }
0174 
0175     public void cancelPairing() {
0176         cancelTimer();
0177         mPairState = PairState.NotPaired;
0178         NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
0179         np.set("pair", false);
0180         mDevice.sendPacket(np);
0181         mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_user));
0182     }
0183 
0184     private void pairingDone() {
0185         Log.i("PairingHandler", "Pairing done");
0186         mPairState = PairState.Paired;
0187         try {
0188             mCallback.pairingSuccessful();
0189         } catch (Exception e) {
0190             Log.e("PairingHandler", "Exception in pairingSuccessful callback, unpairing");
0191             e.printStackTrace();
0192             mPairState = PairState.NotPaired;
0193         }
0194     }
0195 
0196     public void unpair() {
0197         mPairState = PairState.NotPaired;
0198         if (mDevice.isReachable()) {
0199             NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
0200             np.set("pair", false);
0201             mDevice.sendPacket(np);
0202         }
0203         mCallback.unpaired();
0204     }
0205 
0206     private void cancelTimer() {
0207         if (mPairingTimer != null) {
0208             mPairingTimer.cancel();
0209         }
0210     }
0211 }