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 }