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