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