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