1 /* 2 * Copyright (C) 2008 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 android.server; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothClass; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHealth; 24 import android.bluetooth.BluetoothInputDevice; 25 import android.bluetooth.BluetoothPan; 26 import android.bluetooth.BluetoothProfile; 27 import android.bluetooth.BluetoothUuid; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.ParcelUuid; 33 import android.os.PowerManager; 34 import android.util.Log; 35 36 import java.util.HashMap; 37 import java.util.List; 38 39 40 /** 41 * @hide 42 */ 43 class BluetoothEventLoop { 44 private static final String TAG = "BluetoothEventLoop"; 45 private static final boolean DBG = false; 46 47 private int mNativeData; 48 private Thread mThread; 49 private boolean mStarted; 50 private boolean mInterrupted; 51 52 private final HashMap<String, Integer> mPasskeyAgentRequestData; 53 private final HashMap<String, Integer> mAuthorizationAgentRequestData; 54 private final BluetoothService mBluetoothService; 55 private final BluetoothAdapter mAdapter; 56 private final BluetoothAdapterStateMachine mBluetoothState; 57 private BluetoothA2dp mA2dp; 58 private final Context mContext; 59 // The WakeLock is used for bringing up the LCD during a pairing request 60 // from remote device when Android is in Suspend state. 61 private PowerManager.WakeLock mWakeLock; 62 63 private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 1; 64 private static final int EVENT_AGENT_CANCEL = 2; 65 66 private static final int CREATE_DEVICE_ALREADY_EXISTS = 1; 67 private static final int CREATE_DEVICE_SUCCESS = 0; 68 private static final int CREATE_DEVICE_FAILED = -1; 69 70 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 71 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 72 73 private final Handler mHandler = new Handler() { 74 @Override 75 public void handleMessage(Message msg) { 76 String address = null; 77 switch (msg.what) { 78 case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT: 79 address = (String)msg.obj; 80 if (address != null) { 81 mBluetoothService.setPairingConfirmation(address, true); 82 } 83 break; 84 case EVENT_AGENT_CANCEL: 85 // Set the Bond State to BOND_NONE. 86 // We always have only 1 device in BONDING state. 87 String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING); 88 if (devices.length == 0) { 89 break; 90 } else if (devices.length > 1) { 91 Log.e(TAG, " There is more than one device in the Bonding State"); 92 break; 93 } 94 address = devices[0]; 95 mBluetoothService.setBondState(address, 96 BluetoothDevice.BOND_NONE, 97 BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED); 98 break; 99 } 100 } 101 }; 102 classInitNative()103 static { classInitNative(); } classInitNative()104 private static native void classInitNative(); 105 BluetoothEventLoop(Context context, BluetoothAdapter adapter, BluetoothService bluetoothService, BluetoothAdapterStateMachine bluetoothState)106 /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, 107 BluetoothService bluetoothService, 108 BluetoothAdapterStateMachine bluetoothState) { 109 mBluetoothService = bluetoothService; 110 mContext = context; 111 mBluetoothState = bluetoothState; 112 mPasskeyAgentRequestData = new HashMap<String, Integer>(); 113 mAuthorizationAgentRequestData = new HashMap<String, Integer>(); 114 mAdapter = adapter; 115 //WakeLock instantiation in BluetoothEventLoop class 116 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); 117 mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP 118 | PowerManager.ON_AFTER_RELEASE, TAG); 119 mWakeLock.setReferenceCounted(false); 120 initializeNativeDataNative(); 121 } 122 getProfileProxy()123 /*package*/ void getProfileProxy() { 124 mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP); 125 mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.INPUT_DEVICE); 126 } 127 128 private BluetoothProfile.ServiceListener mProfileServiceListener = 129 new BluetoothProfile.ServiceListener() { 130 public void onServiceConnected(int profile, BluetoothProfile proxy) { 131 if (profile == BluetoothProfile.A2DP) { 132 mA2dp = (BluetoothA2dp) proxy; 133 } 134 } 135 public void onServiceDisconnected(int profile) { 136 if (profile == BluetoothProfile.A2DP) { 137 mA2dp = null; 138 } 139 } 140 }; 141 142 finalize()143 protected void finalize() throws Throwable { 144 try { 145 cleanupNativeDataNative(); 146 } finally { 147 super.finalize(); 148 } 149 } 150 getPasskeyAgentRequestData()151 /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() { 152 return mPasskeyAgentRequestData; 153 } 154 getAuthorizationAgentRequestData()155 /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() { 156 return mAuthorizationAgentRequestData; 157 } 158 start()159 /* package */ void start() { 160 161 if (!isEventLoopRunningNative()) { 162 if (DBG) log("Starting Event Loop thread"); 163 startEventLoopNative(); 164 } 165 } 166 stop()167 public void stop() { 168 if (isEventLoopRunningNative()) { 169 if (DBG) log("Stopping Event Loop thread"); 170 stopEventLoopNative(); 171 } 172 } 173 isEventLoopRunning()174 public boolean isEventLoopRunning() { 175 return isEventLoopRunningNative(); 176 } 177 addDevice(String address, String[] properties)178 private void addDevice(String address, String[] properties) { 179 BluetoothDeviceProperties deviceProperties = 180 mBluetoothService.getDeviceProperties(); 181 deviceProperties.addProperties(address, properties); 182 String rssi = deviceProperties.getProperty(address, "RSSI"); 183 String classValue = deviceProperties.getProperty(address, "Class"); 184 String name = deviceProperties.getProperty(address, "Name"); 185 short rssiValue; 186 // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE. 187 // If we accept the pairing, we will automatically show it at the top of the list. 188 if (rssi != null) { 189 rssiValue = (short)Integer.valueOf(rssi).intValue(); 190 } else { 191 rssiValue = Short.MIN_VALUE; 192 } 193 if (classValue != null) { 194 Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); 195 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 196 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 197 new BluetoothClass(Integer.valueOf(classValue))); 198 intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue); 199 intent.putExtra(BluetoothDevice.EXTRA_NAME, name); 200 201 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 202 } else { 203 log ("ClassValue: " + classValue + " for remote device: " + address + " is null"); 204 } 205 } 206 207 /** 208 * Called by native code on a DeviceFound signal from org.bluez.Adapter. 209 * 210 * @param address the MAC address of the new device 211 * @param properties an array of property keys and value strings 212 * 213 * @see BluetoothDeviceProperties#addProperties(String, String[]) 214 */ onDeviceFound(String address, String[] properties)215 private void onDeviceFound(String address, String[] properties) { 216 if (properties == null) { 217 Log.e(TAG, "ERROR: Remote device properties are null"); 218 return; 219 } 220 addDevice(address, properties); 221 } 222 223 /** 224 * Called by native code on a DeviceDisappeared signal from 225 * org.bluez.Adapter. 226 * 227 * @param address the MAC address of the disappeared device 228 */ onDeviceDisappeared(String address)229 private void onDeviceDisappeared(String address) { 230 Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED); 231 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 232 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 233 } 234 235 /** 236 * Called by native code on a DisconnectRequested signal from 237 * org.bluez.Device. 238 * 239 * @param deviceObjectPath the object path for the disconnecting device 240 */ onDeviceDisconnectRequested(String deviceObjectPath)241 private void onDeviceDisconnectRequested(String deviceObjectPath) { 242 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 243 if (address == null) { 244 Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null"); 245 return; 246 } 247 Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); 248 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 249 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 250 } 251 252 /** 253 * Called by native code for the async response to a CreatePairedDevice 254 * method call to org.bluez.Adapter. 255 * 256 * @param address the MAC address of the device to pair 257 * @param result success or error result for the pairing operation 258 */ onCreatePairedDeviceResult(String address, int result)259 private void onCreatePairedDeviceResult(String address, int result) { 260 address = address.toUpperCase(); 261 mBluetoothService.onCreatePairedDeviceResult(address, result); 262 } 263 264 /** 265 * Called by native code on a DeviceCreated signal from org.bluez.Adapter. 266 * 267 * @param deviceObjectPath the object path for the created device 268 */ onDeviceCreated(String deviceObjectPath)269 private void onDeviceCreated(String deviceObjectPath) { 270 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 271 if (address == null) { 272 Log.e(TAG, "onDeviceCreated: device address null!" + " deviceObjectPath: " + 273 deviceObjectPath); 274 return; 275 } 276 if (!mBluetoothService.isRemoteDeviceInCache(address)) { 277 // Incoming connection, we haven't seen this device, add to cache. 278 String[] properties = mBluetoothService.getRemoteDeviceProperties(address); 279 if (properties != null) { 280 addDevice(address, properties); 281 } 282 } 283 } 284 285 /** 286 * Called by native code on a DeviceRemoved signal from org.bluez.Adapter. 287 * 288 * @param deviceObjectPath the object path for the removed device 289 */ onDeviceRemoved(String deviceObjectPath)290 private void onDeviceRemoved(String deviceObjectPath) { 291 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 292 if (address != null) { 293 mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE, 294 BluetoothDevice.UNBOND_REASON_REMOVED); 295 mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null); 296 } 297 } 298 299 /** 300 * Called by native code on a PropertyChanged signal from 301 * org.bluez.Adapter. This method is also called from 302 * {@link BluetoothAdapterStateMachine} to set the "Pairable" 303 * property when Bluetooth is enabled. 304 * 305 * @param propValues a string array containing the key and one or more 306 * values. 307 */ onPropertyChanged(String[] propValues)308 /*package*/ void onPropertyChanged(String[] propValues) { 309 BluetoothAdapterProperties adapterProperties = 310 mBluetoothService.getAdapterProperties(); 311 312 if (adapterProperties.isEmpty()) { 313 // We have got a property change before 314 // we filled up our cache. 315 adapterProperties.getAllProperties(); 316 } 317 log("Property Changed: " + propValues[0] + " : " + propValues[1]); 318 String name = propValues[0]; 319 if (name.equals("Name")) { 320 adapterProperties.setProperty(name, propValues[1]); 321 Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); 322 intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]); 323 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 324 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 325 } else if (name.equals("Pairable") || name.equals("Discoverable")) { 326 adapterProperties.setProperty(name, propValues[1]); 327 328 if (name.equals("Discoverable")) { 329 mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED); 330 } 331 332 String pairable = name.equals("Pairable") ? propValues[1] : 333 adapterProperties.getProperty("Pairable"); 334 String discoverable = name.equals("Discoverable") ? propValues[1] : 335 adapterProperties.getProperty("Discoverable"); 336 337 // This shouldn't happen, unless Adapter Properties are null. 338 if (pairable == null || discoverable == null) 339 return; 340 341 int mode = BluetoothService.bluezStringToScanMode( 342 pairable.equals("true"), 343 discoverable.equals("true")); 344 if (mode >= 0) { 345 Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 346 intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode); 347 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 348 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 349 } 350 } else if (name.equals("Discovering")) { 351 Intent intent; 352 adapterProperties.setProperty(name, propValues[1]); 353 if (propValues[1].equals("true")) { 354 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 355 } else { 356 // Stop the discovery. 357 mBluetoothService.cancelDiscovery(); 358 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 359 } 360 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 361 } else if (name.equals("Devices") || name.equals("UUIDs")) { 362 String value = null; 363 int len = Integer.valueOf(propValues[1]); 364 if (len > 0) { 365 StringBuilder str = new StringBuilder(); 366 for (int i = 2; i < propValues.length; i++) { 367 str.append(propValues[i]); 368 str.append(","); 369 } 370 value = str.toString(); 371 } 372 adapterProperties.setProperty(name, value); 373 if (name.equals("UUIDs")) { 374 mBluetoothService.updateBluetoothState(value); 375 } 376 } else if (name.equals("Powered")) { 377 mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED, 378 propValues[1].equals("true") ? new Boolean(true) : new Boolean(false)); 379 } else if (name.equals("DiscoverableTimeout")) { 380 adapterProperties.setProperty(name, propValues[1]); 381 } 382 } 383 384 /** 385 * Called by native code on a PropertyChanged signal from 386 * org.bluez.Device. 387 * 388 * @param deviceObjectPath the object path for the changed device 389 * @param propValues a string array containing the key and one or more 390 * values. 391 */ onDevicePropertyChanged(String deviceObjectPath, String[] propValues)392 private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) { 393 String name = propValues[0]; 394 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 395 if (address == null) { 396 Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); 397 return; 398 } 399 log("Device property changed: " + address + " property: " 400 + name + " value: " + propValues[1]); 401 402 BluetoothDevice device = mAdapter.getRemoteDevice(address); 403 if (name.equals("Name")) { 404 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 405 Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); 406 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 407 intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]); 408 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 409 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 410 } else if (name.equals("Alias")) { 411 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 412 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED); 413 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 414 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 415 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 416 } else if (name.equals("Class")) { 417 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 418 Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); 419 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 420 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 421 new BluetoothClass(Integer.valueOf(propValues[1]))); 422 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 423 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 424 } else if (name.equals("Connected")) { 425 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 426 Intent intent = null; 427 if (propValues[1].equals("true")) { 428 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); 429 // Set the link timeout to 8000 slots (5 sec timeout) 430 // for bluetooth docks. 431 if (mBluetoothService.isBluetoothDock(address)) { 432 mBluetoothService.setLinkTimeout(address, 8000); 433 } 434 } else { 435 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 436 } 437 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 438 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 439 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 440 } else if (name.equals("UUIDs")) { 441 String uuid = null; 442 int len = Integer.valueOf(propValues[1]); 443 if (len > 0) { 444 StringBuilder str = new StringBuilder(); 445 for (int i = 2; i < propValues.length; i++) { 446 str.append(propValues[i]); 447 str.append(","); 448 } 449 uuid = str.toString(); 450 } 451 mBluetoothService.setRemoteDeviceProperty(address, name, uuid); 452 453 // UUIDs have changed, query remote service channel and update cache. 454 mBluetoothService.updateDeviceServiceChannelCache(address); 455 456 mBluetoothService.sendUuidIntent(address); 457 } else if (name.equals("Paired")) { 458 if (propValues[1].equals("true")) { 459 // If locally initiated pairing, we will 460 // not go to BOND_BONDED state until we have received a 461 // successful return value in onCreatePairedDeviceResult 462 if (null == mBluetoothService.getPendingOutgoingBonding()) { 463 mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED); 464 } 465 } else { 466 mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE); 467 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); 468 } 469 } else if (name.equals("Trusted")) { 470 if (DBG) 471 log("set trust state succeeded, value is: " + propValues[1]); 472 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 473 } 474 } 475 476 /** 477 * Called by native code on a PropertyChanged signal from 478 * org.bluez.Input. 479 * 480 * @param path the object path for the changed input device 481 * @param propValues a string array containing the key and one or more 482 * values. 483 */ onInputDevicePropertyChanged(String path, String[] propValues)484 private void onInputDevicePropertyChanged(String path, String[] propValues) { 485 String address = mBluetoothService.getAddressFromObjectPath(path); 486 if (address == null) { 487 Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null"); 488 return; 489 } 490 log("Input Device : Name of Property is: " + propValues[0]); 491 boolean state = false; 492 if (propValues[1].equals("true")) { 493 state = true; 494 } 495 mBluetoothService.handleInputDevicePropertyChange(address, state); 496 } 497 498 /** 499 * Called by native code on a PropertyChanged signal from 500 * org.bluez.Network. 501 * 502 * @param deviceObjectPath the object path for the changed PAN device 503 * @param propValues a string array containing the key and one or more 504 * values. 505 */ onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues)506 private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) { 507 String name = propValues[0]; 508 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 509 if (address == null) { 510 Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null"); 511 return; 512 } 513 if (DBG) { 514 log("Pan Device property changed: " + address + " property: " 515 + name + " value: "+ propValues[1]); 516 } 517 BluetoothDevice device = mAdapter.getRemoteDevice(address); 518 if (name.equals("Connected")) { 519 if (propValues[1].equals("false")) { 520 mBluetoothService.handlePanDeviceStateChange(device, 521 BluetoothPan.STATE_DISCONNECTED, 522 BluetoothPan.LOCAL_PANU_ROLE); 523 } 524 } else if (name.equals("Interface")) { 525 String iface = propValues[1]; 526 if (!iface.equals("")) { 527 mBluetoothService.handlePanDeviceStateChange(device, iface, 528 BluetoothPan.STATE_CONNECTED, 529 BluetoothPan.LOCAL_PANU_ROLE); 530 } 531 } 532 } 533 checkPairingRequestAndGetAddress(String objectPath, int nativeData)534 private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { 535 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 536 if (address == null) { 537 Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + 538 "returning null"); 539 return null; 540 } 541 address = address.toUpperCase(); 542 mPasskeyAgentRequestData.put(address, new Integer(nativeData)); 543 544 if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) { 545 // shutdown path 546 mBluetoothService.cancelPairingUserInput(address); 547 return null; 548 } 549 // Set state to BONDING. For incoming connections it will be set here. 550 // For outgoing connections, it gets set when we call createBond. 551 // Also set it only when the state is not already Bonded, we can sometimes 552 // get an authorization request from the remote end if it doesn't have the link key 553 // while we still have it. 554 if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED) 555 mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING); 556 return address; 557 } 558 559 /** 560 * Called by native code on a RequestPairingConsent method call to 561 * org.bluez.Agent. 562 * 563 * @param objectPath the path of the device to request pairing consent for 564 * @param nativeData a native pointer to the original D-Bus message 565 */ onRequestPairingConsent(String objectPath, int nativeData)566 private void onRequestPairingConsent(String objectPath, int nativeData) { 567 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 568 if (address == null) return; 569 570 /* The link key will not be stored if the incoming request has MITM 571 * protection switched on. Unfortunately, some devices have MITM 572 * switched on even though their capabilities are NoInputNoOutput, 573 * so we may get this request many times. Also if we respond immediately, 574 * the other end is unable to handle it. Delay sending the message. 575 */ 576 if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) { 577 Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); 578 message.obj = address; 579 mHandler.sendMessageDelayed(message, 1500); 580 return; 581 } 582 // Acquire wakelock during PIN code request to bring up LCD display 583 mWakeLock.acquire(); 584 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 585 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 586 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 587 BluetoothDevice.PAIRING_VARIANT_CONSENT); 588 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 589 // Release wakelock to allow the LCD to go off after the PIN popup notification. 590 mWakeLock.release(); 591 return; 592 } 593 594 /** 595 * Called by native code on a RequestConfirmation method call to 596 * org.bluez.Agent. 597 * 598 * @param objectPath the path of the device to confirm the passkey for 599 * @param passkey an integer containing the 6-digit passkey to confirm 600 * @param nativeData a native pointer to the original D-Bus message 601 */ onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData)602 private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) { 603 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 604 if (address == null) return; 605 // Acquire wakelock during PIN code request to bring up LCD display 606 mWakeLock.acquire(); 607 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 608 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 609 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey); 610 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 611 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION); 612 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 613 // Release wakelock to allow the LCD to go off after the PIN popup notification. 614 mWakeLock.release(); 615 return; 616 } 617 618 /** 619 * Called by native code on a RequestPasskey method call to 620 * org.bluez.Agent. 621 * 622 * @param objectPath the path of the device requesting a passkey 623 * @param nativeData a native pointer to the original D-Bus message 624 */ onRequestPasskey(String objectPath, int nativeData)625 private void onRequestPasskey(String objectPath, int nativeData) { 626 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 627 if (address == null) return; 628 // Acquire wakelock during PIN code request to bring up LCD display 629 mWakeLock.acquire(); 630 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 631 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 632 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 633 BluetoothDevice.PAIRING_VARIANT_PASSKEY); 634 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 635 // Release wakelock to allow the LCD to go off after the PIN popup notification. 636 mWakeLock.release(); 637 return; 638 } 639 640 /** 641 * Called by native code on a RequestPinCode method call to 642 * org.bluez.Agent. 643 * 644 * @param objectPath the path of the device requesting a PIN code 645 * @param nativeData a native pointer to the original D-Bus message 646 */ onRequestPinCode(String objectPath, int nativeData)647 private void onRequestPinCode(String objectPath, int nativeData) { 648 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 649 if (address == null) return; 650 651 String pendingOutgoingAddress = 652 mBluetoothService.getPendingOutgoingBonding(); 653 BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address)); 654 int btDeviceClass = btClass.getDeviceClass(); 655 656 if (address.equals(pendingOutgoingAddress)) { 657 // we initiated the bonding 658 659 // Check if its a dock 660 if (mBluetoothService.isBluetoothDock(address)) { 661 String pin = mBluetoothService.getDockPin(); 662 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin)); 663 return; 664 } 665 666 // try 0000 once if the device looks dumb 667 switch (btDeviceClass) { 668 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 669 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 670 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 671 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 672 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 673 if (mBluetoothService.attemptAutoPair(address)) return; 674 } 675 } 676 677 if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || 678 btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) { 679 // Its a keyboard. Follow the HID spec recommendation of creating the 680 // passkey and displaying it to the user. If the keyboard doesn't follow 681 // the spec recommendation, check if the keyboard has a fixed PIN zero 682 // and pair. 683 if (mBluetoothService.isFixedPinZerosAutoPairKeyboard(address)) { 684 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); 685 return; 686 } 687 688 // Generate a variable PIN. This is not truly random but good enough. 689 int pin = (int) Math.floor(Math.random() * 10000); 690 sendDisplayPinIntent(address, pin); 691 return; 692 } 693 // Acquire wakelock during PIN code request to bring up LCD display 694 mWakeLock.acquire(); 695 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 696 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 697 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); 698 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 699 // Release wakelock to allow the LCD to go off after the PIN popup notification. 700 mWakeLock.release(); 701 return; 702 } 703 704 /** 705 * Called by native code on a DisplayPasskey method call to 706 * org.bluez.Agent. 707 * 708 * @param objectPath the path of the device to display the passkey for 709 * @param passkey an integer containing the 6-digit passkey 710 * @param nativeData a native pointer to the original D-Bus message 711 */ onDisplayPasskey(String objectPath, int passkey, int nativeData)712 private void onDisplayPasskey(String objectPath, int passkey, int nativeData) { 713 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 714 if (address == null) return; 715 716 // Acquire wakelock during PIN code request to bring up LCD display 717 mWakeLock.acquire(); 718 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 719 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 720 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey); 721 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 722 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY); 723 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 724 //Release wakelock to allow the LCD to go off after the PIN popup notification. 725 mWakeLock.release(); 726 } 727 sendDisplayPinIntent(String address, int pin)728 private void sendDisplayPinIntent(String address, int pin) { 729 // Acquire wakelock during PIN code request to bring up LCD display 730 mWakeLock.acquire(); 731 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 732 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 733 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin); 734 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 735 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); 736 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 737 //Release wakelock to allow the LCD to go off after the PIN popup notifcation. 738 mWakeLock.release(); 739 } 740 741 /** 742 * Called by native code on a RequestOobData method call to 743 * org.bluez.Agent. 744 * 745 * @param objectPath the path of the device requesting OOB data 746 * @param nativeData a native pointer to the original D-Bus message 747 */ onRequestOobData(String objectPath, int nativeData)748 private void onRequestOobData(String objectPath, int nativeData) { 749 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 750 if (address == null) return; 751 752 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 753 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 754 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 755 BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT); 756 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 757 } 758 759 /** 760 * Called by native code on an Authorize method call to org.bluez.Agent. 761 * 762 * @param objectPath the path of the device requesting to be authorized 763 * @param deviceUuid the UUID of the requesting device 764 * @param nativeData reference for native data 765 */ onAgentAuthorize(String objectPath, String deviceUuid, int nativeData)766 private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) { 767 if (!mBluetoothService.isEnabled()) return; 768 769 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 770 if (address == null) { 771 Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); 772 return; 773 } 774 775 boolean authorized = false; 776 ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); 777 778 BluetoothDevice device = mAdapter.getRemoteDevice(address); 779 mAuthorizationAgentRequestData.put(address, new Integer(nativeData)); 780 781 // Bluez sends the UUID of the local service being accessed, _not_ the 782 // remote service 783 if (mA2dp != null && 784 (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) 785 || BluetoothUuid.isAdvAudioDist(uuid)) && 786 !isOtherSinkInNonDisconnectedState(address)) { 787 authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF; 788 if (authorized && !BluetoothUuid.isAvrcpTarget(uuid)) { 789 Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address); 790 // Some headsets try to connect AVCTP before AVDTP - against the recommendation 791 // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state 792 // machine. We don't handle AVCTP signals currently. We only send 793 // intents for AVDTP state changes. We need to handle both of them in 794 // some cases. For now, just don't move to incoming state in this case. 795 mBluetoothService.notifyIncomingA2dpConnection(address, false); 796 } else { 797 Log.i(TAG, "" + authorized + 798 "Incoming A2DP / AVRCP connection from " + address); 799 mA2dp.allowIncomingConnect(device, authorized); 800 mBluetoothService.notifyIncomingA2dpConnection(address, true); 801 } 802 } else if (BluetoothUuid.isInputDevice(uuid)) { 803 // We can have more than 1 input device connected. 804 authorized = mBluetoothService.getInputDevicePriority(device) > 805 BluetoothInputDevice.PRIORITY_OFF; 806 if (authorized) { 807 Log.i(TAG, "First check pass for incoming HID connection from " + address); 808 // notify profile state change 809 mBluetoothService.notifyIncomingHidConnection(address); 810 } else { 811 Log.i(TAG, "Rejecting incoming HID connection from " + address); 812 mBluetoothService.allowIncomingProfileConnect(device, authorized); 813 } 814 } else if (BluetoothUuid.isBnep(uuid)) { 815 // PAN doesn't go to the state machine, accept or reject from here 816 authorized = mBluetoothService.allowIncomingTethering(); 817 mBluetoothService.allowIncomingProfileConnect(device, authorized); 818 } else { 819 Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); 820 mBluetoothService.allowIncomingProfileConnect(device, authorized); 821 } 822 log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); 823 } 824 onAgentOutOfBandDataAvailable(String objectPath)825 private boolean onAgentOutOfBandDataAvailable(String objectPath) { 826 if (!mBluetoothService.isEnabled()) return false; 827 828 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 829 if (address == null) return false; 830 831 if (mBluetoothService.getDeviceOutOfBandData( 832 mAdapter.getRemoteDevice(address)) != null) { 833 return true; 834 } 835 return false; 836 } 837 isOtherSinkInNonDisconnectedState(String address)838 private boolean isOtherSinkInNonDisconnectedState(String address) { 839 List<BluetoothDevice> devices = 840 mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED, 841 BluetoothA2dp.STATE_CONNECTING, 842 BluetoothA2dp.STATE_DISCONNECTING}); 843 844 if (devices.size() == 0) return false; 845 for (BluetoothDevice dev: devices) { 846 if (!dev.getAddress().equals(address)) return true; 847 } 848 return false; 849 } 850 851 /** 852 * Called by native code on a Cancel method call to org.bluez.Agent. 853 */ onAgentCancel()854 private void onAgentCancel() { 855 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); 856 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 857 858 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL), 859 1500); 860 861 return; 862 } 863 864 /** 865 * Called by native code for the async response to a DiscoverServices 866 * method call to org.bluez.Adapter. 867 * 868 * @param deviceObjectPath the path for the specified device 869 * @param result true for success; false on error 870 */ onDiscoverServicesResult(String deviceObjectPath, boolean result)871 private void onDiscoverServicesResult(String deviceObjectPath, boolean result) { 872 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 873 if (address == null) return; 874 875 // We don't parse the xml here, instead just query Bluez for the properties. 876 if (result) { 877 mBluetoothService.updateRemoteDevicePropertiesCache(address); 878 } 879 mBluetoothService.sendUuidIntent(address); 880 mBluetoothService.makeServiceChannelCallbacks(address); 881 } 882 883 /** 884 * Called by native code for the async response to a CreateDevice 885 * method call to org.bluez.Adapter. 886 * 887 * @param address the MAC address of the device to create 888 * @param result {@link #CREATE_DEVICE_SUCCESS}, 889 * {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}} 890 */ onCreateDeviceResult(String address, int result)891 private void onCreateDeviceResult(String address, int result) { 892 if (DBG) log("Result of onCreateDeviceResult:" + result); 893 894 switch (result) { 895 case CREATE_DEVICE_ALREADY_EXISTS: 896 String path = mBluetoothService.getObjectPathFromAddress(address); 897 if (path != null) { 898 mBluetoothService.discoverServicesNative(path, ""); 899 break; 900 } 901 Log.w(TAG, "Device exists, but we don't have the bluez path, failing"); 902 // fall-through 903 case CREATE_DEVICE_FAILED: 904 mBluetoothService.sendUuidIntent(address); 905 mBluetoothService.makeServiceChannelCallbacks(address); 906 break; 907 case CREATE_DEVICE_SUCCESS: 908 // nothing to do, UUID intent's will be sent via property changed 909 } 910 } 911 912 /** 913 * Called by native code for the async response to a Connect 914 * method call to org.bluez.Input. 915 * 916 * @param path the path of the specified input device 917 * @param result Result code of the operation. 918 */ onInputDeviceConnectionResult(String path, int result)919 private void onInputDeviceConnectionResult(String path, int result) { 920 // Success case gets handled by Property Change signal 921 if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) { 922 String address = mBluetoothService.getAddressFromObjectPath(path); 923 if (address == null) return; 924 925 boolean connected = false; 926 BluetoothDevice device = mAdapter.getRemoteDevice(address); 927 int state = mBluetoothService.getInputDeviceConnectionState(device); 928 if (state == BluetoothInputDevice.STATE_CONNECTING) { 929 if (result == BluetoothInputDevice.INPUT_CONNECT_FAILED_ALREADY_CONNECTED) { 930 connected = true; 931 } else { 932 connected = false; 933 } 934 } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) { 935 if (result == BluetoothInputDevice.INPUT_DISCONNECT_FAILED_NOT_CONNECTED) { 936 connected = false; 937 } else { 938 // There is no better way to handle this, this shouldn't happen 939 connected = true; 940 } 941 } else { 942 Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state); 943 } 944 mBluetoothService.handleInputDevicePropertyChange(address, connected); 945 } 946 } 947 948 /** 949 * Called by native code for the async response to a Connect 950 * method call to org.bluez.Network. 951 * 952 * @param path the path of the specified PAN device 953 * @param result Result code of the operation. 954 */ onPanDeviceConnectionResult(String path, int result)955 private void onPanDeviceConnectionResult(String path, int result) { 956 log ("onPanDeviceConnectionResult " + path + " " + result); 957 // Success case gets handled by Property Change signal 958 if (result != BluetoothPan.PAN_OPERATION_SUCCESS) { 959 String address = mBluetoothService.getAddressFromObjectPath(path); 960 if (address == null) return; 961 962 boolean connected = false; 963 BluetoothDevice device = mAdapter.getRemoteDevice(address); 964 int state = mBluetoothService.getPanDeviceConnectionState(device); 965 if (state == BluetoothPan.STATE_CONNECTING) { 966 if (result == BluetoothPan.PAN_CONNECT_FAILED_ALREADY_CONNECTED) { 967 connected = true; 968 } else { 969 connected = false; 970 } 971 } else if (state == BluetoothPan.STATE_DISCONNECTING) { 972 if (result == BluetoothPan.PAN_DISCONNECT_FAILED_NOT_CONNECTED) { 973 connected = false; 974 } else { 975 // There is no better way to handle this, this shouldn't happen 976 connected = true; 977 } 978 } else { 979 Log.e(TAG, "Error onPanDeviceConnectionResult. State is: " 980 + state + " result: "+ result); 981 } 982 int newState = connected? BluetoothPan.STATE_CONNECTED : 983 BluetoothPan.STATE_DISCONNECTED; 984 mBluetoothService.handlePanDeviceStateChange(device, newState, 985 BluetoothPan.LOCAL_PANU_ROLE); 986 } 987 } 988 989 /** 990 * Called by native code for the async response to a Connect 991 * method call to org.bluez.Health 992 * 993 * @param chanCode The internal id of the channel 994 * @param result Result code of the operation. 995 */ onHealthDeviceConnectionResult(int chanCode, int result)996 private void onHealthDeviceConnectionResult(int chanCode, int result) { 997 log ("onHealthDeviceConnectionResult " + chanCode + " " + result); 998 // Success case gets handled by Property Change signal 999 if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) { 1000 mBluetoothService.onHealthDeviceChannelConnectionError(chanCode, 1001 BluetoothHealth.STATE_CHANNEL_DISCONNECTED); 1002 } 1003 } 1004 1005 /** 1006 * Called by native code on a DeviceDisconnected signal from 1007 * org.bluez.NetworkServer. 1008 * 1009 * @param address the MAC address of the disconnected device 1010 */ onNetworkDeviceDisconnected(String address)1011 private void onNetworkDeviceDisconnected(String address) { 1012 BluetoothDevice device = mAdapter.getRemoteDevice(address); 1013 mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED, 1014 BluetoothPan.LOCAL_NAP_ROLE); 1015 } 1016 1017 /** 1018 * Called by native code on a DeviceConnected signal from 1019 * org.bluez.NetworkServer. 1020 * 1021 * @param address the MAC address of the connected device 1022 * @param iface interface of remote network 1023 * @param destUuid unused UUID parameter 1024 */ onNetworkDeviceConnected(String address, String iface, int destUuid)1025 private void onNetworkDeviceConnected(String address, String iface, int destUuid) { 1026 BluetoothDevice device = mAdapter.getRemoteDevice(address); 1027 mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED, 1028 BluetoothPan.LOCAL_NAP_ROLE); 1029 } 1030 1031 /** 1032 * Called by native code on a PropertyChanged signal from 1033 * org.bluez.HealthDevice. 1034 * 1035 * @param devicePath the object path of the remote device 1036 * @param propValues Properties (Name-Value) of the Health Device. 1037 */ onHealthDevicePropertyChanged(String devicePath, String[] propValues)1038 private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) { 1039 log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]); 1040 mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]); 1041 } 1042 1043 /** 1044 * Called by native code on a ChannelCreated/Deleted signal from 1045 * org.bluez.HealthDevice. 1046 * 1047 * @param devicePath the object path of the remote device 1048 * @param channelPath the path of the health channel. 1049 * @param exists Boolean to indicate if the channel was created or deleted. 1050 */ onHealthDeviceChannelChanged(String devicePath, String channelPath, boolean exists)1051 private void onHealthDeviceChannelChanged(String devicePath, String channelPath, 1052 boolean exists) { 1053 log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath + 1054 ":exists" + exists); 1055 mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists); 1056 } 1057 log(String msg)1058 private static void log(String msg) { 1059 Log.d(TAG, msg); 1060 } 1061 initializeNativeDataNative()1062 private native void initializeNativeDataNative(); startEventLoopNative()1063 private native void startEventLoopNative(); stopEventLoopNative()1064 private native void stopEventLoopNative(); isEventLoopRunningNative()1065 private native boolean isEventLoopRunningNative(); cleanupNativeDataNative()1066 private native void cleanupNativeDataNative(); 1067 } 1068