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.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothClass; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.bluetooth.OobData; 24 import android.content.Intent; 25 import android.os.Message; 26 import android.os.UserHandle; 27 import android.util.Log; 28 29 import com.android.bluetooth.Utils; 30 import com.android.bluetooth.a2dp.A2dpService; 31 import com.android.bluetooth.a2dpsink.A2dpSinkService; 32 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; 33 import com.android.bluetooth.hfp.HeadsetService; 34 import com.android.bluetooth.hfpclient.HeadsetClientService; 35 import com.android.bluetooth.hid.HidHostService; 36 import com.android.bluetooth.pbapclient.PbapClientService; 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.util.State; 39 import com.android.internal.util.StateMachine; 40 41 import java.util.ArrayList; 42 import java.util.HashSet; 43 import java.util.Set; 44 45 /** 46 * This state machine handles Bluetooth Adapter State. 47 * States: 48 * {@link StableState} : No device is in bonding / unbonding state. 49 * {@link PendingCommandState} : Some device is in bonding / unbonding state. 50 * TODO(BT) This class can be removed and this logic moved to the stack. 51 */ 52 53 final class BondStateMachine extends StateMachine { 54 private static final boolean DBG = false; 55 private static final String TAG = "BluetoothBondStateMachine"; 56 57 static final int CREATE_BOND = 1; 58 static final int CANCEL_BOND = 2; 59 static final int REMOVE_BOND = 3; 60 static final int BONDING_STATE_CHANGE = 4; 61 static final int SSP_REQUEST = 5; 62 static final int PIN_REQUEST = 6; 63 static final int UUID_UPDATE = 10; 64 static final int BOND_STATE_NONE = 0; 65 static final int BOND_STATE_BONDING = 1; 66 static final int BOND_STATE_BONDED = 2; 67 68 private AdapterService mAdapterService; 69 private AdapterProperties mAdapterProperties; 70 private RemoteDevices mRemoteDevices; 71 private BluetoothAdapter mAdapter; 72 73 private PendingCommandState mPendingCommandState = new PendingCommandState(); 74 private StableState mStableState = new StableState(); 75 76 public static final String OOBDATA = "oobdata"; 77 78 @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>(); 79 BondStateMachine(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)80 private BondStateMachine(AdapterService service, AdapterProperties prop, 81 RemoteDevices remoteDevices) { 82 super("BondStateMachine:"); 83 addState(mStableState); 84 addState(mPendingCommandState); 85 mRemoteDevices = remoteDevices; 86 mAdapterService = service; 87 mAdapterProperties = prop; 88 mAdapter = BluetoothAdapter.getDefaultAdapter(); 89 setInitialState(mStableState); 90 } 91 make(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)92 public static BondStateMachine make(AdapterService service, AdapterProperties prop, 93 RemoteDevices remoteDevices) { 94 Log.d(TAG, "make"); 95 BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices); 96 bsm.start(); 97 return bsm; 98 } 99 doQuit()100 public void doQuit() { 101 quitNow(); 102 } 103 cleanup()104 private void cleanup() { 105 mAdapterService = null; 106 mRemoteDevices = null; 107 mAdapterProperties = null; 108 } 109 110 @Override onQuitting()111 protected void onQuitting() { 112 cleanup(); 113 } 114 115 private class StableState extends State { 116 @Override enter()117 public void enter() { 118 infoLog("StableState(): Entering Off State"); 119 } 120 121 @Override processMessage(Message msg)122 public boolean processMessage(Message msg) { 123 124 BluetoothDevice dev = (BluetoothDevice) msg.obj; 125 126 switch (msg.what) { 127 128 case CREATE_BOND: 129 OobData oobData = null; 130 if (msg.getData() != null) { 131 oobData = msg.getData().getParcelable(OOBDATA); 132 } 133 134 createBond(dev, msg.arg1, oobData, true); 135 break; 136 case REMOVE_BOND: 137 removeBond(dev, true); 138 break; 139 case BONDING_STATE_CHANGE: 140 int newState = msg.arg1; 141 /* if incoming pairing, transition to pending state */ 142 if (newState == BluetoothDevice.BOND_BONDING) { 143 sendIntent(dev, newState, 0); 144 transitionTo(mPendingCommandState); 145 } else if (newState == BluetoothDevice.BOND_NONE) { 146 /* if the link key was deleted by the stack */ 147 sendIntent(dev, newState, 0); 148 } else { 149 Log.e(TAG, "In stable state, received invalid newState: " 150 + state2str(newState)); 151 } 152 break; 153 case UUID_UPDATE: 154 if (mPendingBondedDevices.contains(dev)) { 155 sendIntent(dev, BluetoothDevice.BOND_BONDED, 0); 156 } 157 break; 158 case CANCEL_BOND: 159 default: 160 Log.e(TAG, "Received unhandled state: " + msg.what); 161 return false; 162 } 163 return true; 164 } 165 } 166 167 168 private class PendingCommandState extends State { 169 private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>(); 170 171 @Override enter()172 public void enter() { 173 infoLog("Entering PendingCommandState State"); 174 BluetoothDevice dev = (BluetoothDevice) getCurrentMessage().obj; 175 } 176 177 @Override processMessage(Message msg)178 public boolean processMessage(Message msg) { 179 BluetoothDevice dev = (BluetoothDevice) msg.obj; 180 DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev); 181 boolean result = false; 182 if (mDevices.contains(dev) && msg.what != CANCEL_BOND 183 && msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST 184 && msg.what != PIN_REQUEST) { 185 deferMessage(msg); 186 return true; 187 } 188 189 switch (msg.what) { 190 case CREATE_BOND: 191 OobData oobData = null; 192 if (msg.getData() != null) { 193 oobData = msg.getData().getParcelable(OOBDATA); 194 } 195 196 result = createBond(dev, msg.arg1, oobData, false); 197 break; 198 case REMOVE_BOND: 199 result = removeBond(dev, false); 200 break; 201 case CANCEL_BOND: 202 result = cancelBond(dev); 203 break; 204 case BONDING_STATE_CHANGE: 205 int newState = msg.arg1; 206 int reason = getUnbondReasonFromHALCode(msg.arg2); 207 sendIntent(dev, newState, reason); 208 if (newState != BluetoothDevice.BOND_BONDING) { 209 /* this is either none/bonded, remove and transition */ 210 result = !mDevices.remove(dev); 211 if (mDevices.isEmpty()) { 212 // Whenever mDevices is empty, then we need to 213 // set result=false. Else, we will end up adding 214 // the device to the list again. This prevents us 215 // from pairing with a device that we just unpaired 216 result = false; 217 transitionTo(mStableState); 218 } 219 if (newState == BluetoothDevice.BOND_NONE) { 220 mAdapterService.setPhonebookAccessPermission(dev, 221 BluetoothDevice.ACCESS_UNKNOWN); 222 mAdapterService.setMessageAccessPermission(dev, 223 BluetoothDevice.ACCESS_UNKNOWN); 224 mAdapterService.setSimAccessPermission(dev, 225 BluetoothDevice.ACCESS_UNKNOWN); 226 // Set the profile Priorities to undefined 227 clearProfilePriority(dev); 228 } 229 } else if (!mDevices.contains(dev)) { 230 result = true; 231 } 232 break; 233 case SSP_REQUEST: 234 int passkey = msg.arg1; 235 int variant = msg.arg2; 236 sendDisplayPinIntent(devProp.getAddress(), passkey, variant); 237 break; 238 case PIN_REQUEST: 239 BluetoothClass btClass = dev.getBluetoothClass(); 240 int btDeviceClass = btClass.getDeviceClass(); 241 if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass 242 == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) { 243 // Its a keyboard. Follow the HID spec recommendation of creating the 244 // passkey and displaying it to the user. If the keyboard doesn't follow 245 // the spec recommendation, check if the keyboard has a fixed PIN zero 246 // and pair. 247 //TODO: Maintain list of devices that have fixed pin 248 // Generate a variable 6-digit PIN in range of 100000-999999 249 // This is not truly random but good enough. 250 int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000))); 251 sendDisplayPinIntent(devProp.getAddress(), pin, 252 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); 253 break; 254 } 255 256 if (msg.arg2 == 1) { // Minimum 16 digit pin required here 257 sendDisplayPinIntent(devProp.getAddress(), 0, 258 BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS); 259 } else { 260 // In PIN_REQUEST, there is no passkey to display.So do not send the 261 // EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() ) 262 sendDisplayPinIntent(devProp.getAddress(), 0, 263 BluetoothDevice.PAIRING_VARIANT_PIN); 264 } 265 266 break; 267 default: 268 Log.e(TAG, "Received unhandled event:" + msg.what); 269 return false; 270 } 271 if (result) { 272 mDevices.add(dev); 273 } 274 275 return true; 276 } 277 } 278 cancelBond(BluetoothDevice dev)279 private boolean cancelBond(BluetoothDevice dev) { 280 if (dev.getBondState() == BluetoothDevice.BOND_BONDING) { 281 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 282 if (!mAdapterService.cancelBondNative(addr)) { 283 Log.e(TAG, "Unexpected error while cancelling bond:"); 284 } else { 285 return true; 286 } 287 } 288 return false; 289 } 290 removeBond(BluetoothDevice dev, boolean transition)291 private boolean removeBond(BluetoothDevice dev, boolean transition) { 292 if (dev.getBondState() == BluetoothDevice.BOND_BONDED) { 293 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 294 if (!mAdapterService.removeBondNative(addr)) { 295 Log.e(TAG, "Unexpected error while removing bond:"); 296 } else { 297 if (transition) { 298 transitionTo(mPendingCommandState); 299 } 300 return true; 301 } 302 303 } 304 return false; 305 } 306 createBond(BluetoothDevice dev, int transport, OobData oobData, boolean transition)307 private boolean createBond(BluetoothDevice dev, int transport, OobData oobData, 308 boolean transition) { 309 if (dev.getBondState() == BluetoothDevice.BOND_NONE) { 310 infoLog("Bond address is:" + dev); 311 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 312 boolean result; 313 if (oobData != null) { 314 result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData); 315 } else { 316 result = mAdapterService.createBondNative(addr, transport); 317 } 318 319 if (!result) { 320 sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED); 321 return false; 322 } else if (transition) { 323 transitionTo(mPendingCommandState); 324 } 325 return true; 326 } 327 return false; 328 } 329 sendDisplayPinIntent(byte[] address, int pin, int variant)330 private void sendDisplayPinIntent(byte[] address, int pin, int variant) { 331 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 332 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address)); 333 if (pin != 0) { 334 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin); 335 } 336 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant); 337 intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 338 // Workaround for Android Auto until pre-accepting pairing requests is added. 339 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 340 mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM); 341 } 342 343 @VisibleForTesting sendIntent(BluetoothDevice device, int newState, int reason)344 void sendIntent(BluetoothDevice device, int newState, int reason) { 345 DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device); 346 int oldState = BluetoothDevice.BOND_NONE; 347 if (newState != BluetoothDevice.BOND_NONE 348 && newState != BluetoothDevice.BOND_BONDING 349 && newState != BluetoothDevice.BOND_BONDED) { 350 infoLog("Invalid bond state " + newState); 351 return; 352 } 353 if (devProp != null) { 354 oldState = devProp.getBondState(); 355 } 356 if (mPendingBondedDevices.contains(device)) { 357 mPendingBondedDevices.remove(device); 358 if (oldState == BluetoothDevice.BOND_BONDED) { 359 if (newState == BluetoothDevice.BOND_BONDING) { 360 mAdapterProperties.onBondStateChanged(device, newState); 361 } 362 oldState = BluetoothDevice.BOND_BONDING; 363 } else { 364 // Should not enter here. 365 throw new IllegalArgumentException("Invalid old state " + oldState); 366 } 367 } 368 if (oldState == newState) { 369 return; 370 } 371 372 mAdapterProperties.onBondStateChanged(device, newState); 373 374 if (devProp != null && ((devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_CLASSIC 375 || devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_DUAL) 376 && newState == BluetoothDevice.BOND_BONDED && devProp.getUuids() == null)) { 377 infoLog(device + " is bonded, wait for SDP complete to broadcast bonded intent"); 378 if (!mPendingBondedDevices.contains(device)) { 379 mPendingBondedDevices.add(device); 380 } 381 if (oldState == BluetoothDevice.BOND_NONE) { 382 // Broadcast NONE->BONDING for NONE->BONDED case. 383 newState = BluetoothDevice.BOND_BONDING; 384 } else { 385 return; 386 } 387 } 388 389 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 390 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 391 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState); 392 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); 393 if (newState == BluetoothDevice.BOND_NONE) { 394 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); 395 } 396 mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM); 397 infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => " 398 + state2str(newState)); 399 } 400 bondStateChangeCallback(int status, byte[] address, int newState)401 void bondStateChangeCallback(int status, byte[] address, int newState) { 402 BluetoothDevice device = mRemoteDevices.getDevice(address); 403 404 if (device == null) { 405 infoLog("No record of the device:" + device); 406 // This device will be added as part of the BONDING_STATE_CHANGE intent processing 407 // in sendIntent above 408 device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 409 } 410 411 infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device + " newState: " 412 + newState); 413 414 Message msg = obtainMessage(BONDING_STATE_CHANGE); 415 msg.obj = device; 416 417 if (newState == BOND_STATE_BONDED) { 418 msg.arg1 = BluetoothDevice.BOND_BONDED; 419 } else if (newState == BOND_STATE_BONDING) { 420 msg.arg1 = BluetoothDevice.BOND_BONDING; 421 } else { 422 msg.arg1 = BluetoothDevice.BOND_NONE; 423 } 424 msg.arg2 = status; 425 426 sendMessage(msg); 427 } 428 sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey)429 void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey) { 430 //TODO(BT): Get wakelock and update name and cod 431 BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); 432 if (bdDevice == null) { 433 mRemoteDevices.addDeviceProperties(address); 434 } 435 infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " + cod 436 + " pairingVariant " + pairingVariant + " passkey: " + passkey); 437 int variant; 438 boolean displayPasskey = false; 439 switch (pairingVariant) { 440 441 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION: 442 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION; 443 displayPasskey = true; 444 break; 445 446 case AbstractionLayer.BT_SSP_VARIANT_CONSENT: 447 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT; 448 break; 449 450 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY: 451 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY; 452 break; 453 454 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION: 455 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY; 456 displayPasskey = true; 457 break; 458 459 default: 460 errorLog("SSP Pairing variant not present"); 461 return; 462 } 463 BluetoothDevice device = mRemoteDevices.getDevice(address); 464 if (device == null) { 465 warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address)); 466 mRemoteDevices.addDeviceProperties(address); 467 device = mRemoteDevices.getDevice(address); 468 } 469 470 Message msg = obtainMessage(SSP_REQUEST); 471 msg.obj = device; 472 if (displayPasskey) { 473 msg.arg1 = passkey; 474 } 475 msg.arg2 = variant; 476 sendMessage(msg); 477 } 478 pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits)479 void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) { 480 //TODO(BT): Get wakelock and update name and cod 481 482 BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); 483 if (bdDevice == null) { 484 mRemoteDevices.addDeviceProperties(address); 485 } 486 infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod); 487 488 Message msg = obtainMessage(PIN_REQUEST); 489 msg.obj = bdDevice; 490 msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean 491 492 sendMessage(msg); 493 } 494 clearProfilePriority(BluetoothDevice device)495 private void clearProfilePriority(BluetoothDevice device) { 496 HidHostService hidService = HidHostService.getHidHostService(); 497 A2dpService a2dpService = A2dpService.getA2dpService(); 498 HeadsetService headsetService = HeadsetService.getHeadsetService(); 499 HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService(); 500 A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); 501 PbapClientService pbapClientService = PbapClientService.getPbapClientService(); 502 503 if (hidService != null) { 504 hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 505 } 506 if (a2dpService != null) { 507 a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 508 } 509 if (headsetService != null) { 510 headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 511 } 512 if (headsetClientService != null) { 513 headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 514 } 515 if (a2dpSinkService != null) { 516 a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 517 } 518 if (pbapClientService != null) { 519 pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED); 520 } 521 522 // Clear Absolute Volume black list 523 if (a2dpService != null) { 524 a2dpService.resetAvrcpBlacklist(device); 525 } 526 } 527 state2str(int state)528 private String state2str(int state) { 529 if (state == BluetoothDevice.BOND_NONE) { 530 return "BOND_NONE"; 531 } else if (state == BluetoothDevice.BOND_BONDING) { 532 return "BOND_BONDING"; 533 } else if (state == BluetoothDevice.BOND_BONDED) { 534 return "BOND_BONDED"; 535 } else return "UNKNOWN(" + state + ")"; 536 } 537 infoLog(String msg)538 private void infoLog(String msg) { 539 Log.i(TAG, msg); 540 } 541 errorLog(String msg)542 private void errorLog(String msg) { 543 Log.e(TAG, msg); 544 } 545 warnLog(String msg)546 private void warnLog(String msg) { 547 Log.w(TAG, msg); 548 } 549 getUnbondReasonFromHALCode(int reason)550 private int getUnbondReasonFromHALCode(int reason) { 551 if (reason == AbstractionLayer.BT_STATUS_SUCCESS) { 552 return BluetoothDevice.BOND_SUCCESS; 553 } else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) { 554 return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; 555 } else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) { 556 return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; 557 } else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) { 558 return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; 559 } else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) { 560 return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; 561 } 562 563 /* default */ 564 return BluetoothDevice.UNBOND_REASON_REMOVED; 565 } 566 } 567