BondStateMachine.java revision 11b41d8123241ba8e693e21c4db308bacff23f57
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 // Set the profile Priorities to undefined 196 clearProfilePriorty(dev); 197 } 198 else if (newState == BluetoothDevice.BOND_BONDED) 199 { 200 // Do not set profile priority 201 // Profile priority should be set after SDP completion 202 203 // Restore the profile priorty settings 204 //setProfilePriorty(dev); 205 } 206 } 207 else if(!mDevices.contains(dev)) 208 result=true; 209 break; 210 case SSP_REQUEST: 211 int passkey = msg.arg1; 212 int variant = msg.arg2; 213 sendDisplayPinIntent(devProp.getAddress(), passkey, variant); 214 break; 215 case PIN_REQUEST: 216 BluetoothClass btClass = dev.getBluetoothClass(); 217 int btDeviceClass = btClass.getDeviceClass(); 218 if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || 219 btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) { 220 // Its a keyboard. Follow the HID spec recommendation of creating the 221 // passkey and displaying it to the user. If the keyboard doesn't follow 222 // the spec recommendation, check if the keyboard has a fixed PIN zero 223 // and pair. 224 //TODO: Maintain list of devices that have fixed pin 225 // Generate a variable 6-digit PIN in range of 100000-999999 226 // This is not truly random but good enough. 227 int pin = 100000 + (int)Math.floor((Math.random() * (999999 - 100000))); 228 sendDisplayPinIntent(devProp.getAddress(), pin, 229 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); 230 break; 231 } 232 //In PIN_REQUEST, there is no passkey to display.So do not send the 233 //EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() ) 234 sendDisplayPinIntent(devProp.getAddress(), 0, 235 BluetoothDevice.PAIRING_VARIANT_PIN); 236 237 break; 238 default: 239 Log.e(TAG, "Received unhandled event:" + msg.what); 240 return false; 241 } 242 if (result) mDevices.add(dev); 243 244 return true; 245 } 246 } 247 248 private boolean cancelBond(BluetoothDevice dev) { 249 if (dev.getBondState() == BluetoothDevice.BOND_BONDING) { 250 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 251 if (!mAdapterService.cancelBondNative(addr)) { 252 Log.e(TAG, "Unexpected error while cancelling bond:"); 253 } else { 254 return true; 255 } 256 } 257 return false; 258 } 259 260 private boolean removeBond(BluetoothDevice dev, boolean transition) { 261 if (dev.getBondState() == BluetoothDevice.BOND_BONDED) { 262 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 263 if (!mAdapterService.removeBondNative(addr)) { 264 Log.e(TAG, "Unexpected error while removing bond:"); 265 } else { 266 if (transition) transitionTo(mPendingCommandState); 267 return true; 268 } 269 270 } 271 return false; 272 } 273 274 private boolean createBond(BluetoothDevice dev, int transport, boolean transition) { 275 if (dev.getBondState() == BluetoothDevice.BOND_NONE) { 276 infoLog("Bond address is:" + dev); 277 byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); 278 if (!mAdapterService.createBondNative(addr, transport)) { 279 sendIntent(dev, BluetoothDevice.BOND_NONE, 280 BluetoothDevice.UNBOND_REASON_REMOVED); 281 return false; 282 } else if (transition) { 283 transitionTo(mPendingCommandState); 284 } 285 return true; 286 } 287 return false; 288 } 289 290 private void sendDisplayPinIntent(byte[] address, int pin, int variant) { 291 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 292 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address)); 293 if (pin != 0) { 294 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin); 295 } 296 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant); 297 mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM); 298 } 299 300 private void sendIntent(BluetoothDevice device, int newState, int reason) { 301 DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device); 302 int oldState = BluetoothDevice.BOND_NONE; 303 if (devProp != null) { 304 oldState = devProp.getBondState(); 305 } 306 if (oldState == newState) return; 307 mAdapterProperties.onBondStateChanged(device, newState); 308 309 Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 310 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 311 intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState); 312 intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); 313 if (newState == BluetoothDevice.BOND_NONE) 314 intent.putExtra(BluetoothDevice.EXTRA_REASON, reason); 315 mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, 316 AdapterService.BLUETOOTH_PERM); 317 infoLog("Bond State Change Intent:" + device + " OldState: " + oldState 318 + " NewState: " + newState); 319 } 320 321 void bondStateChangeCallback(int status, byte[] address, int newState) { 322 BluetoothDevice device = mRemoteDevices.getDevice(address); 323 324 if (device == null) { 325 infoLog("No record of the device:" + device); 326 // This device will be added as part of the BONDING_STATE_CHANGE intent processing 327 // in sendIntent above 328 device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 329 } 330 331 infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device 332 + " newState: " + newState); 333 334 Message msg = obtainMessage(BONDING_STATE_CHANGE); 335 msg.obj = device; 336 337 if (newState == BOND_STATE_BONDED) 338 msg.arg1 = BluetoothDevice.BOND_BONDED; 339 else if (newState == BOND_STATE_BONDING) 340 msg.arg1 = BluetoothDevice.BOND_BONDING; 341 else 342 msg.arg1 = BluetoothDevice.BOND_NONE; 343 msg.arg2 = status; 344 345 sendMessage(msg); 346 } 347 348 void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, 349 int passkey) { 350 //TODO(BT): Get wakelock and update name and cod 351 BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); 352 if (bdDevice == null) { 353 mRemoteDevices.addDeviceProperties(address); 354 } 355 infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " + 356 cod + " pairingVariant " + pairingVariant + " passkey: " + passkey); 357 int variant; 358 boolean displayPasskey = false; 359 switch(pairingVariant) { 360 361 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION : 362 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION; 363 displayPasskey = true; 364 break; 365 366 case AbstractionLayer.BT_SSP_VARIANT_CONSENT : 367 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT; 368 break; 369 370 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY : 371 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY; 372 break; 373 374 case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION : 375 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY; 376 displayPasskey = true; 377 break; 378 379 default: 380 errorLog("SSP Pairing variant not present"); 381 return; 382 } 383 BluetoothDevice device = mRemoteDevices.getDevice(address); 384 if (device == null) { 385 warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address)); 386 mRemoteDevices.addDeviceProperties(address); 387 device = mRemoteDevices.getDevice(address); 388 } 389 390 Message msg = obtainMessage(SSP_REQUEST); 391 msg.obj = device; 392 if(displayPasskey) 393 msg.arg1 = passkey; 394 msg.arg2 = variant; 395 sendMessage(msg); 396 } 397 398 void pinRequestCallback(byte[] address, byte[] name, int cod) { 399 //TODO(BT): Get wakelock and update name and cod 400 BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); 401 if (bdDevice == null) { 402 mRemoteDevices.addDeviceProperties(address); 403 } 404 infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + 405 cod); 406 407 Message msg = obtainMessage(PIN_REQUEST); 408 msg.obj = bdDevice; 409 410 sendMessage(msg); 411 } 412 413 private void setProfilePriorty (BluetoothDevice device){ 414 HidService hidService = HidService.getHidService(); 415 A2dpService a2dpService = A2dpService.getA2dpService(); 416 HeadsetService headsetService = HeadsetService.getHeadsetService(); 417 418 if ((hidService != null) && 419 (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 420 hidService.setPriority(device,BluetoothProfile.PRIORITY_ON); 421 } 422 423 if ((a2dpService != null) && 424 (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 425 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON); 426 } 427 428 if ((headsetService != null) && 429 (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){ 430 headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON); 431 } 432 } 433 434 private void clearProfilePriorty (BluetoothDevice device){ 435 HidService hidService = HidService.getHidService(); 436 A2dpService a2dpService = A2dpService.getA2dpService(); 437 HeadsetService headsetService = HeadsetService.getHeadsetService(); 438 439 if (hidService != null) 440 hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 441 if(a2dpService != null) 442 a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 443 if(headsetService != null) 444 headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED); 445 } 446 447 private void infoLog(String msg) { 448 Log.i(TAG, msg); 449 } 450 451 private void errorLog(String msg) { 452 Log.e(TAG, msg); 453 } 454 455 private void warnLog(String msg) { 456 Log.w(TAG, msg); 457 } 458 459 private int getUnbondReasonFromHALCode (int reason) { 460 if (reason == AbstractionLayer.BT_STATUS_SUCCESS) 461 return BluetoothDevice.BOND_SUCCESS; 462 else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) 463 return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; 464 else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) 465 return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; 466 else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) 467 return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; 468 else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) 469 return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; 470 471 /* default */ 472 return BluetoothDevice.UNBOND_REASON_REMOVED; 473 } 474} 475