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