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