1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc.handover; 18 19 import static com.android.nfc.handover.BluetoothPeripheralHandover.ACTION_CANCEL_CONNECT; 20 21 import android.app.Service; 22 import android.bluetooth.BluetoothAdapter; 23 import android.bluetooth.BluetoothClass; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.OobData; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.nfc.NfcAdapter; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.Message; 35 import android.os.Messenger; 36 import android.os.ParcelUuid; 37 import android.os.Parcelable; 38 import android.os.RemoteException; 39 import android.text.TextUtils; 40 import android.util.Log; 41 42 import androidx.annotation.VisibleForTesting; 43 44 import java.util.Objects; 45 import java.util.Set; 46 47 public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback { 48 static final String TAG = "PeripheralHandoverService"; 49 static final boolean DBG = true; 50 51 static final int MSG_PAUSE_POLLING = 0; 52 53 public static final String BUNDLE_TRANSFER = "transfer"; 54 public static final String EXTRA_PERIPHERAL_DEVICE = "device"; 55 public static final String EXTRA_PERIPHERAL_NAME = "headsetname"; 56 public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype"; 57 public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata"; 58 public static final String EXTRA_PERIPHERAL_UUIDS = "uuids"; 59 public static final String EXTRA_PERIPHERAL_CLASS = "class"; 60 public static final String EXTRA_CLIENT = "client"; 61 public static final String EXTRA_BT_ENABLED = "bt_enabled"; 62 63 public static final int MSG_HEADSET_CONNECTED = 0; 64 public static final int MSG_HEADSET_NOT_CONNECTED = 1; 65 66 // Amount of time to pause polling when connecting to peripherals 67 private static final int PAUSE_POLLING_TIMEOUT_MS = 35000; 68 private static final int PAUSE_DELAY_MILLIS = 300; 69 70 private static final Object sLock = new Object(); 71 72 // Variables below only accessed on main thread 73 final Messenger mMessenger; 74 75 int mStartId; 76 77 BluetoothAdapter mBluetoothAdapter; 78 NfcAdapter mNfcAdapter; 79 Handler mHandler; 80 BluetoothPeripheralHandover mBluetoothPeripheralHandover; 81 BluetoothDevice mDevice; 82 String mName; 83 Messenger mClient; 84 boolean mBluetoothHeadsetConnected; 85 boolean mBluetoothEnabledByNfc; 86 Bundle mPendingMsgData = null; 87 88 class MessageHandler extends Handler { 89 @Override handleMessage(Message msg)90 public void handleMessage(Message msg) { 91 switch (msg.what) { 92 case MSG_PAUSE_POLLING: 93 mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS); 94 break; 95 } 96 } 97 } 98 99 final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() { 100 @Override 101 public void onReceive(Context context, Intent intent) { 102 String action = intent.getAction(); 103 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 104 handleBluetoothStateChanged(intent); 105 } 106 } 107 }; 108 PeripheralHandoverService()109 public PeripheralHandoverService() { 110 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 111 mHandler = new MessageHandler(); 112 mMessenger = new Messenger(mHandler); 113 mBluetoothHeadsetConnected = false; 114 mBluetoothEnabledByNfc = false; 115 mStartId = 0; 116 } 117 118 @Override onStartCommand(Intent intent, int flags, int startId)119 public int onStartCommand(Intent intent, int flags, int startId) { 120 if (intent == null) { 121 if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover."); 122 synchronized (sLock) { 123 stopSelf(startId); 124 mStartId = 0; 125 } 126 return START_NOT_STICKY; 127 } 128 129 Bundle msgData = intent.getExtras(); 130 BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE); 131 String name = msgData.getString(EXTRA_PERIPHERAL_NAME); 132 133 synchronized (sLock) { 134 if (mStartId != 0) { 135 Log.d(TAG, "Ongoing handover to " + mDevice); 136 if (!Objects.equals(mDevice, device) || !TextUtils.equals(mName, name)) { 137 Log.w(TAG, "Cancel ongoing handover"); 138 sendBroadcast(new Intent(ACTION_CANCEL_CONNECT)); 139 // Wait for the previous attempt to be fully cancelled. Store the new pairing 140 // data to start the pairing after cancellation. 141 mPendingMsgData = new Bundle(msgData); 142 } 143 mStartId = startId; 144 // already running 145 return START_STICKY; 146 } 147 mStartId = startId; 148 } 149 150 if (doPeripheralHandover(intent.getExtras())) { 151 return START_STICKY; 152 } else { 153 onBluetoothPeripheralHandoverComplete(false); 154 return START_NOT_STICKY; 155 } 156 } 157 158 @Override onCreate()159 public void onCreate() { 160 super.onCreate(); 161 mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); 162 163 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 164 registerReceiver(mBluetoothStatusReceiver, filter); 165 } 166 167 @Override onDestroy()168 public void onDestroy() { 169 super.onDestroy(); 170 unregisterReceiver(mBluetoothStatusReceiver); 171 } 172 doPeripheralHandover(Bundle msgData)173 boolean doPeripheralHandover(Bundle msgData) { 174 Log.d(TAG, "doPeripheralHandover: " + msgData); 175 if (mBluetoothPeripheralHandover != null) { 176 Log.d(TAG, "Ignoring pairing request, existing handover in progress."); 177 return true; 178 } 179 180 if (msgData == null) { 181 return false; 182 } 183 184 mDevice = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE); 185 mName = msgData.getString(EXTRA_PERIPHERAL_NAME); 186 int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT); 187 OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA); 188 Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS); 189 BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS); 190 191 ParcelUuid[] uuids = null; 192 if (parcelables != null) { 193 uuids = new ParcelUuid[parcelables.length]; 194 for (int i = 0; i < parcelables.length; i++) { 195 uuids[i] = (ParcelUuid)parcelables[i]; 196 } 197 } 198 199 mClient = msgData.getParcelable(EXTRA_CLIENT); 200 mBluetoothEnabledByNfc = msgData.getBoolean(EXTRA_BT_ENABLED); 201 202 mBluetoothPeripheralHandover = new BluetoothPeripheralHandover( 203 this, mDevice, mName, transport, oobData, uuids, btClass, this); 204 205 if (transport == BluetoothDevice.TRANSPORT_LE) { 206 mHandler.sendMessageDelayed( 207 mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS); 208 } 209 if (mBluetoothAdapter.isEnabled()) { 210 if (!mBluetoothPeripheralHandover.start()) { 211 return false; 212 } 213 } else { 214 // Once BT is enabled, the headset pairing will be started 215 if (!enableBluetooth()) { 216 Log.e(TAG, "Error enabling Bluetooth."); 217 return false; 218 } 219 } 220 221 return true; 222 } 223 handleBluetoothStateChanged(Intent intent)224 private void handleBluetoothStateChanged(Intent intent) { 225 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 226 BluetoothAdapter.ERROR); 227 if (state == BluetoothAdapter.STATE_OFF) { 228 sendBroadcast(new Intent(ACTION_CANCEL_CONNECT)); 229 } else if (state == BluetoothAdapter.STATE_ON) { 230 // If there is a pending device pairing, start it 231 if (mBluetoothPeripheralHandover != null && 232 !mBluetoothPeripheralHandover.hasStarted()) { 233 if (!mBluetoothPeripheralHandover.start()) { 234 onBluetoothPeripheralHandoverComplete(false); 235 } 236 } 237 } 238 } 239 240 @Override onBluetoothPeripheralHandoverComplete(boolean connected)241 public void onBluetoothPeripheralHandoverComplete(boolean connected) { 242 // Called on the main thread 243 int transport = mBluetoothPeripheralHandover.getTransport(); 244 mBluetoothPeripheralHandover = null; 245 mBluetoothHeadsetConnected = connected; 246 247 // <hack> resume polling immediately if the connection failed, 248 // otherwise just wait for polling to come back up after the timeout 249 // This ensures we don't disconnect if the user left the volantis 250 // on the tag after pairing completed, which results in automatic 251 // disconnection </hack> 252 if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { 253 if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { 254 mHandler.removeMessages(MSG_PAUSE_POLLING); 255 } 256 257 // do this unconditionally as the polling could have been paused as we were removing 258 // the message in the handler. It's a no-op if polling is already enabled. 259 mNfcAdapter.resumePolling(); 260 } 261 disableBluetoothIfNeeded(); 262 replyToClient(connected); 263 264 if (mPendingMsgData != null) { 265 Log.d(TAG, "Resume next handover after cancellation of previous handover"); 266 doPeripheralHandover(mPendingMsgData); 267 mPendingMsgData = null; 268 } else { 269 synchronized (sLock) { 270 stopSelf(mStartId); 271 mStartId = 0; 272 } 273 } 274 } 275 276 enableBluetooth()277 boolean enableBluetooth() { 278 if (!mBluetoothAdapter.isEnabled()) { 279 mBluetoothEnabledByNfc = true; 280 return mBluetoothAdapter.enableNoAutoConnect(); 281 } 282 return true; 283 } 284 disableBluetoothIfNeeded()285 void disableBluetoothIfNeeded() { 286 if (!mBluetoothEnabledByNfc) return; 287 if (hasConnectedBluetoothDevices()) return; 288 289 if (!mBluetoothHeadsetConnected) { 290 mBluetoothAdapter.disable(); 291 mBluetoothEnabledByNfc = false; 292 } 293 } 294 hasConnectedBluetoothDevices()295 boolean hasConnectedBluetoothDevices() { 296 Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); 297 298 if (bondedDevices != null) { 299 for (BluetoothDevice device : bondedDevices) { 300 if (device.equals(mDevice)) { 301 // Not required to check the remote BT "target" device 302 // connection status, because sometimes the connection 303 // state is not yet been updated upon disconnection. 304 // It is enough to check the connection status for 305 // "other" remote BT device/s. 306 continue; 307 } 308 if (device.isConnected()) return true; 309 } 310 } 311 return false; 312 } 313 replyToClient(boolean connected)314 void replyToClient(boolean connected) { 315 if (mClient == null) { 316 return; 317 } 318 319 final int msgId = connected ? MSG_HEADSET_CONNECTED : MSG_HEADSET_NOT_CONNECTED; 320 final Message msg = Message.obtain(null, msgId); 321 msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0; 322 try { 323 mClient.send(msg); 324 } catch (RemoteException e) { 325 // Ignore 326 } 327 } 328 329 @Override onBind(Intent intent)330 public IBinder onBind(Intent intent) { 331 return null; 332 } 333 334 @Override onUnbind(Intent intent)335 public boolean onUnbind(Intent intent) { 336 // prevent any future callbacks to the client, no rebind call needed. 337 return false; 338 } 339 340 @VisibleForTesting PeripheralHandoverService(BluetoothAdapter bluetoothAdapter, NfcAdapter nfcAdapter, BluetoothPeripheralHandover bluetoothPeripheralHandover, MessageHandler handler, Messenger messenger, BluetoothDevice device, boolean bluetoothEnabledByNfc)341 public PeripheralHandoverService(BluetoothAdapter bluetoothAdapter, NfcAdapter nfcAdapter, 342 BluetoothPeripheralHandover bluetoothPeripheralHandover, MessageHandler handler, 343 Messenger messenger, BluetoothDevice device, boolean bluetoothEnabledByNfc) { 344 mBluetoothAdapter = bluetoothAdapter; 345 mNfcAdapter = nfcAdapter; 346 mBluetoothPeripheralHandover = bluetoothPeripheralHandover; 347 mHandler = handler; 348 mClient = messenger; 349 mMessenger = messenger; 350 mDevice = device; 351 mBluetoothEnabledByNfc = bluetoothEnabledByNfc; 352 mBluetoothHeadsetConnected = false; 353 mStartId = 0; 354 mPendingMsgData = null; 355 mName = "Test name"; 356 } 357 } 358