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.BluetoothUuid; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.ParcelUuid; 29 import android.util.Log; 30 31 import java.util.HashMap; 32 import java.util.Set; 33 34 /** 35 * TODO: Move this to 36 * java/services/com/android/server/BluetoothEventLoop.java 37 * and make the contructor package private again. 38 * 39 * @hide 40 */ 41 class BluetoothEventLoop { 42 private static final String TAG = "BluetoothEventLoop"; 43 private static final boolean DBG = false; 44 45 private int mNativeData; 46 private Thread mThread; 47 private boolean mStarted; 48 private boolean mInterrupted; 49 50 private final HashMap<String, Integer> mPasskeyAgentRequestData; 51 private final BluetoothService mBluetoothService; 52 private final BluetoothAdapter mAdapter; 53 private final Context mContext; 54 55 private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; 56 private static final int EVENT_RESTART_BLUETOOTH = 2; 57 private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3; 58 private static final int EVENT_AGENT_CANCEL = 4; 59 60 private static final int CREATE_DEVICE_ALREADY_EXISTS = 1; 61 private static final int CREATE_DEVICE_SUCCESS = 0; 62 private static final int CREATE_DEVICE_FAILED = -1; 63 64 // The time (in millisecs) to delay the pairing attempt after the first 65 // auto pairing attempt fails. We use an exponential delay with 66 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and 67 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. 68 private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; 69 private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; 70 71 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 72 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 73 74 private final Handler mHandler = new Handler() { 75 @Override 76 public void handleMessage(Message msg) { 77 String address = null; 78 switch (msg.what) { 79 case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: 80 address = (String)msg.obj; 81 if (address != null) { 82 mBluetoothService.createBond(address); 83 return; 84 } 85 break; 86 case EVENT_RESTART_BLUETOOTH: 87 mBluetoothService.restart(); 88 break; 89 case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT: 90 address = (String)msg.obj; 91 if (address != null) { 92 mBluetoothService.setPairingConfirmation(address, true); 93 } 94 break; 95 case EVENT_AGENT_CANCEL: 96 // Set the Bond State to BOND_NONE. 97 // We always have only 1 device in BONDING state. 98 String[] devices = 99 mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING); 100 if (devices.length == 0) { 101 break; 102 } else if (devices.length > 1) { 103 Log.e(TAG, " There is more than one device in the Bonding State"); 104 break; 105 } 106 address = devices[0]; 107 mBluetoothService.getBondState().setBondState(address, 108 BluetoothDevice.BOND_NONE, 109 BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED); 110 break; 111 } 112 } 113 }; 114 115 static { classInitNative(); } 116 private static native void classInitNative(); 117 118 /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, 119 BluetoothService bluetoothService) { 120 mBluetoothService = bluetoothService; 121 mContext = context; 122 mPasskeyAgentRequestData = new HashMap(); 123 mAdapter = adapter; 124 initializeNativeDataNative(); 125 } 126 127 protected void finalize() throws Throwable { 128 try { 129 cleanupNativeDataNative(); 130 } finally { 131 super.finalize(); 132 } 133 } 134 135 /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() { 136 return mPasskeyAgentRequestData; 137 } 138 139 /* package */ void start() { 140 141 if (!isEventLoopRunningNative()) { 142 if (DBG) log("Starting Event Loop thread"); 143 startEventLoopNative(); 144 } 145 } 146 147 public void stop() { 148 if (isEventLoopRunningNative()) { 149 if (DBG) log("Stopping Event Loop thread"); 150 stopEventLoopNative(); 151 } 152 } 153 154 public boolean isEventLoopRunning() { 155 return isEventLoopRunningNative(); 156 } 157 158 private void addDevice(String address, String[] properties) { 159 mBluetoothService.addRemoteDeviceProperties(address, properties); 160 String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI"); 161 String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class"); 162 String name = mBluetoothService.getRemoteDeviceProperty(address, "Name"); 163 short rssiValue; 164 // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE. 165 // If we accept the pairing, we will automatically show it at the top of the list. 166 if (rssi != null) { 167 rssiValue = (short)Integer.valueOf(rssi).intValue(); 168 } else { 169 rssiValue = Short.MIN_VALUE; 170 } 171 if (classValue != null) { 172 Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); 173 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 174 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 175 new BluetoothClass(Integer.valueOf(classValue))); 176 intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue); 177 intent.putExtra(BluetoothDevice.EXTRA_NAME, name); 178 179 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 180 } else { 181 log ("ClassValue: " + classValue + " for remote device: " + address + " is null"); 182 } 183 } 184 185 private void onDeviceFound(String address, String[] properties) { 186 if (properties == null) { 187 Log.e(TAG, "ERROR: Remote device properties are null"); 188 return; 189 } 190 addDevice(address, properties); 191 } 192 193 private void onDeviceDisappeared(String address) { 194 Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED); 195 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 196 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 197 } 198 199 private void onDeviceDisconnectRequested(String deviceObjectPath) { 200 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 201 if (address == null) { 202 Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null"); 203 return; 204 } 205 Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); 206 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 207 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 208 } 209 210 private void onCreatePairedDeviceResult(String address, int result) { 211 address = address.toUpperCase(); 212 if (result == BluetoothDevice.BOND_SUCCESS) { 213 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); 214 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 215 mBluetoothService.getBondState().clearPinAttempts(address); 216 } 217 } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && 218 mBluetoothService.getBondState().getAttempt(address) == 1) { 219 mBluetoothService.getBondState().addAutoPairingFailure(address); 220 pairingAttempt(address, result); 221 } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && 222 mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 223 pairingAttempt(address, result); 224 } else { 225 mBluetoothService.getBondState().setBondState(address, 226 BluetoothDevice.BOND_NONE, result); 227 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 228 mBluetoothService.getBondState().clearPinAttempts(address); 229 } 230 } 231 } 232 233 private void pairingAttempt(String address, int result) { 234 // This happens when our initial guess of "0000" as the pass key 235 // fails. Try to create the bond again and display the pin dialog 236 // to the user. Use back-off while posting the delayed 237 // message. The initial value is 238 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is 239 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is 240 // reached, display an error to the user. 241 int attempt = mBluetoothService.getBondState().getAttempt(address); 242 if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > 243 MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { 244 mBluetoothService.getBondState().clearPinAttempts(address); 245 mBluetoothService.getBondState().setBondState(address, 246 BluetoothDevice.BOND_NONE, result); 247 return; 248 } 249 250 Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 251 message.obj = address; 252 boolean postResult = mHandler.sendMessageDelayed(message, 253 attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 254 if (!postResult) { 255 mBluetoothService.getBondState().clearPinAttempts(address); 256 mBluetoothService.getBondState().setBondState(address, 257 BluetoothDevice.BOND_NONE, result); 258 return; 259 } 260 mBluetoothService.getBondState().attempt(address); 261 } 262 263 private void onDeviceCreated(String deviceObjectPath) { 264 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 265 if (!mBluetoothService.isRemoteDeviceInCache(address)) { 266 // Incoming connection, we haven't seen this device, add to cache. 267 String[] properties = mBluetoothService.getRemoteDeviceProperties(address); 268 if (properties != null) { 269 addDevice(address, properties); 270 } 271 } 272 return; 273 } 274 275 private void onDeviceRemoved(String deviceObjectPath) { 276 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 277 if (address != null) { 278 mBluetoothService.getBondState().setBondState(address.toUpperCase(), 279 BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED); 280 mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null); 281 } 282 } 283 284 /*package*/ void onPropertyChanged(String[] propValues) { 285 if (mBluetoothService.isAdapterPropertiesEmpty()) { 286 // We have got a property change before 287 // we filled up our cache. 288 mBluetoothService.getAllProperties(); 289 } 290 String name = propValues[0]; 291 if (name.equals("Name")) { 292 Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); 293 intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]); 294 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 295 mBluetoothService.setProperty(name, propValues[1]); 296 } else if (name.equals("Pairable") || name.equals("Discoverable")) { 297 String pairable = name.equals("Pairable") ? propValues[1] : 298 mBluetoothService.getPropertyInternal("Pairable"); 299 String discoverable = name.equals("Discoverable") ? propValues[1] : 300 mBluetoothService.getPropertyInternal("Discoverable"); 301 302 // This shouldn't happen, unless Adapter Properties are null. 303 if (pairable == null || discoverable == null) 304 return; 305 306 int mode = BluetoothService.bluezStringToScanMode( 307 pairable.equals("true"), 308 discoverable.equals("true")); 309 if (mode >= 0) { 310 Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); 311 intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode); 312 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 313 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 314 } 315 mBluetoothService.setProperty(name, propValues[1]); 316 } else if (name.equals("Discovering")) { 317 Intent intent; 318 if (propValues[1].equals("true")) { 319 mBluetoothService.setIsDiscovering(true); 320 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 321 } else { 322 // Stop the discovery. 323 mBluetoothService.cancelDiscovery(); 324 mBluetoothService.setIsDiscovering(false); 325 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 326 } 327 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 328 mBluetoothService.setProperty(name, propValues[1]); 329 } else if (name.equals("Devices")) { 330 String value = null; 331 int len = Integer.valueOf(propValues[1]); 332 if (len > 0) { 333 StringBuilder str = new StringBuilder(); 334 for (int i = 2; i < propValues.length; i++) { 335 str.append(propValues[i]); 336 str.append(","); 337 } 338 value = str.toString(); 339 } 340 mBluetoothService.setProperty(name, value); 341 } else if (name.equals("Powered")) { 342 // bluetoothd has restarted, re-read all our properties. 343 // Note: bluez only sends this property change when it restarts. 344 if (propValues[1].equals("true")) 345 onRestartRequired(); 346 } 347 } 348 349 private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) { 350 String name = propValues[0]; 351 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 352 if (address == null) { 353 Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); 354 return; 355 } 356 if (DBG) { 357 log("Device property changed:" + address + "property:" + name); 358 } 359 BluetoothDevice device = mAdapter.getRemoteDevice(address); 360 if (name.equals("Name")) { 361 Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); 362 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 363 intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]); 364 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 365 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 366 } else if (name.equals("Class")) { 367 Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); 368 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 369 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 370 new BluetoothClass(Integer.valueOf(propValues[1]))); 371 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 372 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 373 } else if (name.equals("Connected")) { 374 Intent intent = null; 375 if (propValues[1].equals("true")) { 376 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); 377 // Set the link timeout to 8000 slots (5 sec timeout) 378 // for bluetooth docks. 379 if (mBluetoothService.isBluetoothDock(address)) { 380 mBluetoothService.setLinkTimeout(address, 8000); 381 } 382 } else { 383 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 384 } 385 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 386 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 387 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 388 } else if (name.equals("UUIDs")) { 389 String uuid = null; 390 int len = Integer.valueOf(propValues[1]); 391 if (len > 0) { 392 StringBuilder str = new StringBuilder(); 393 for (int i = 2; i < propValues.length; i++) { 394 str.append(propValues[i]); 395 str.append(","); 396 } 397 uuid = str.toString(); 398 } 399 mBluetoothService.setRemoteDeviceProperty(address, name, uuid); 400 401 // UUIDs have changed, query remote service channel and update cache. 402 mBluetoothService.updateDeviceServiceChannelCache(address); 403 404 mBluetoothService.sendUuidIntent(address); 405 } else if (name.equals("Paired")) { 406 if (propValues[1].equals("true")) { 407 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); 408 } else { 409 mBluetoothService.getBondState().setBondState(address, 410 BluetoothDevice.BOND_NONE); 411 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); 412 } 413 } else if (name.equals("Trusted")) { 414 if (DBG) 415 log("set trust state succeded, value is " + propValues[1]); 416 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 417 } 418 } 419 420 private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { 421 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 422 if (address == null) { 423 Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + 424 "returning null"); 425 return null; 426 } 427 address = address.toUpperCase(); 428 mPasskeyAgentRequestData.put(address, new Integer(nativeData)); 429 430 if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) { 431 // shutdown path 432 mBluetoothService.cancelPairingUserInput(address); 433 return null; 434 } 435 // Set state to BONDING. For incoming connections it will be set here. 436 // For outgoing connections, it gets set when we call createBond. 437 // Also set it only when the state is not already Bonded, we can sometimes 438 // get an authorization request from the remote end if it doesn't have the link key 439 // while we still have it. 440 if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED) 441 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING); 442 return address; 443 } 444 445 private void onRequestPairingConsent(String objectPath, int nativeData) { 446 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 447 if (address == null) return; 448 449 /* The link key will not be stored if the incoming request has MITM 450 * protection switched on. Unfortunately, some devices have MITM 451 * switched on even though their capabilities are NoInputNoOutput, 452 * so we may get this request many times. Also if we respond immediately, 453 * the other end is unable to handle it. Delay sending the message. 454 */ 455 if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) { 456 Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); 457 message.obj = address; 458 mHandler.sendMessageDelayed(message, 1500); 459 return; 460 } 461 462 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 463 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 464 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 465 BluetoothDevice.PAIRING_VARIANT_CONSENT); 466 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 467 return; 468 } 469 470 private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) { 471 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 472 if (address == null) return; 473 474 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 475 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 476 intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); 477 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 478 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION); 479 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 480 return; 481 } 482 483 private void onRequestPasskey(String objectPath, int nativeData) { 484 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 485 if (address == null) return; 486 487 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 488 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 489 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 490 BluetoothDevice.PAIRING_VARIANT_PASSKEY); 491 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 492 return; 493 } 494 495 private void onRequestPinCode(String objectPath, int nativeData) { 496 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 497 if (address == null) return; 498 499 String pendingOutgoingAddress = 500 mBluetoothService.getBondState().getPendingOutgoingBonding(); 501 if (address.equals(pendingOutgoingAddress)) { 502 // we initiated the bonding 503 504 // Check if its a dock 505 if (mBluetoothService.isBluetoothDock(address)) { 506 String pin = mBluetoothService.getDockPin(); 507 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin)); 508 return; 509 } 510 511 BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address)); 512 513 // try 0000 once if the device looks dumb 514 switch (btClass.getDeviceClass()) { 515 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 516 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 517 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 518 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 519 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 520 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 521 if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && 522 !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { 523 mBluetoothService.getBondState().attempt(address); 524 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); 525 return; 526 } 527 } 528 } 529 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 530 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 531 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); 532 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 533 return; 534 } 535 536 private void onDisplayPasskey(String objectPath, int passkey, int nativeData) { 537 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 538 if (address == null) return; 539 540 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); 541 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); 542 intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); 543 intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, 544 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY); 545 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 546 } 547 548 private boolean onAgentAuthorize(String objectPath, String deviceUuid) { 549 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 550 if (address == null) { 551 Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); 552 return false; 553 } 554 555 boolean authorized = false; 556 ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); 557 BluetoothA2dp a2dp = new BluetoothA2dp(mContext); 558 559 // Bluez sends the UUID of the local service being accessed, _not_ the 560 // remote service 561 if (mBluetoothService.isEnabled() && 562 (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) 563 || BluetoothUuid.isAdvAudioDist(uuid)) && 564 !isOtherSinkInNonDisconnectingState(address)) { 565 BluetoothDevice device = mAdapter.getRemoteDevice(address); 566 authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; 567 if (authorized) { 568 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); 569 } else { 570 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); 571 } 572 } else { 573 Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); 574 } 575 log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); 576 return authorized; 577 } 578 579 boolean isOtherSinkInNonDisconnectingState(String address) { 580 BluetoothA2dp a2dp = new BluetoothA2dp(mContext); 581 Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); 582 if (devices.size() == 0) return false; 583 for(BluetoothDevice dev: devices) { 584 if (!dev.getAddress().equals(address)) return true; 585 } 586 return false; 587 } 588 589 private void onAgentCancel() { 590 Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); 591 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 592 593 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL), 594 1500); 595 596 return; 597 } 598 599 private void onDiscoverServicesResult(String deviceObjectPath, boolean result) { 600 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 601 // We don't parse the xml here, instead just query Bluez for the properties. 602 if (result) { 603 mBluetoothService.updateRemoteDevicePropertiesCache(address); 604 } 605 mBluetoothService.sendUuidIntent(address); 606 mBluetoothService.makeServiceChannelCallbacks(address); 607 } 608 609 private void onCreateDeviceResult(String address, int result) { 610 if (DBG) log("Result of onCreateDeviceResult:" + result); 611 612 switch (result) { 613 case CREATE_DEVICE_ALREADY_EXISTS: 614 String path = mBluetoothService.getObjectPathFromAddress(address); 615 if (path != null) { 616 mBluetoothService.discoverServicesNative(path, ""); 617 break; 618 } 619 Log.w(TAG, "Device exists, but we dont have the bluez path, failing"); 620 // fall-through 621 case CREATE_DEVICE_FAILED: 622 mBluetoothService.sendUuidIntent(address); 623 mBluetoothService.makeServiceChannelCallbacks(address); 624 break; 625 case CREATE_DEVICE_SUCCESS: 626 // nothing to do, UUID intent's will be sent via property changed 627 } 628 } 629 630 private void onRestartRequired() { 631 if (mBluetoothService.isEnabled()) { 632 Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " + 633 "restarting Bluetooth ***"); 634 mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH); 635 } 636 } 637 638 private static void log(String msg) { 639 Log.d(TAG, msg); 640 } 641 642 private native void initializeNativeDataNative(); 643 private native void startEventLoopNative(); 644 private native void stopEventLoopNative(); 645 private native boolean isEventLoopRunningNative(); 646 private native void cleanupNativeDataNative(); 647 } 648