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