BluetoothEventLoop.java revision 9519ce75f15ba287a641166c1b7ed10f2aa73f74
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 17package android.server; 18 19import android.bluetooth.BluetoothA2dp; 20import android.bluetooth.BluetoothClass; 21import android.bluetooth.BluetoothAdapter; 22import android.bluetooth.BluetoothDevice; 23import android.bluetooth.BluetoothError; 24import android.bluetooth.BluetoothIntent; 25import android.bluetooth.BluetoothUuid; 26import android.content.Context; 27import android.content.Intent; 28import android.os.Handler; 29import android.os.Message; 30import android.util.Log; 31 32import java.util.HashMap; 33import java.util.UUID; 34 35/** 36 * TODO: Move this to 37 * java/services/com/android/server/BluetoothEventLoop.java 38 * and make the contructor package private again. 39 * 40 * @hide 41 */ 42class BluetoothEventLoop { 43 private static final String TAG = "BluetoothEventLoop"; 44 private static final boolean DBG = false; 45 46 private int mNativeData; 47 private Thread mThread; 48 private boolean mStarted; 49 private boolean mInterrupted; 50 51 private final HashMap<String, Integer> mPasskeyAgentRequestData; 52 private final BluetoothService mBluetoothService; 53 private final BluetoothAdapter mAdapter; 54 private final Context mContext; 55 56 private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; 57 private static final int EVENT_RESTART_BLUETOOTH = 2; 58 59 // The time (in millisecs) to delay the pairing attempt after the first 60 // auto pairing attempt fails. We use an exponential delay with 61 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and 62 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. 63 private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; 64 private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; 65 66 private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; 67 private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; 68 69 private final Handler mHandler = new Handler() { 70 @Override 71 public void handleMessage(Message msg) { 72 switch (msg.what) { 73 case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: 74 String address = (String)msg.obj; 75 if (address != null) { 76 mBluetoothService.createBond(address); 77 return; 78 } 79 break; 80 case EVENT_RESTART_BLUETOOTH: 81 mBluetoothService.restart(); 82 break; 83 } 84 } 85 }; 86 87 static { classInitNative(); } 88 private static native void classInitNative(); 89 90 /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, 91 BluetoothService bluetoothService) { 92 mBluetoothService = bluetoothService; 93 mContext = context; 94 mPasskeyAgentRequestData = new HashMap(); 95 mAdapter = adapter; 96 initializeNativeDataNative(); 97 } 98 99 protected void finalize() throws Throwable { 100 try { 101 cleanupNativeDataNative(); 102 } finally { 103 super.finalize(); 104 } 105 } 106 107 /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() { 108 return mPasskeyAgentRequestData; 109 } 110 111 /* package */ void start() { 112 113 if (!isEventLoopRunningNative()) { 114 if (DBG) log("Starting Event Loop thread"); 115 startEventLoopNative(); 116 } 117 } 118 119 public void stop() { 120 if (isEventLoopRunningNative()) { 121 if (DBG) log("Stopping Event Loop thread"); 122 stopEventLoopNative(); 123 } 124 } 125 126 public boolean isEventLoopRunning() { 127 return isEventLoopRunningNative(); 128 } 129 130 private void addDevice(String address, String[] properties) { 131 mBluetoothService.addRemoteDeviceProperties(address, properties); 132 String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI"); 133 String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class"); 134 String name = mBluetoothService.getRemoteDeviceProperty(address, "Name"); 135 short rssiValue; 136 // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE. 137 // If we accept the pairing, we will automatically show it at the top of the list. 138 if (rssi != null) { 139 rssiValue = (short)Integer.valueOf(rssi).intValue(); 140 } else { 141 rssiValue = Short.MIN_VALUE; 142 } 143 if (classValue != null) { 144 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); 145 intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address)); 146 intent.putExtra(BluetoothIntent.CLASS, Integer.valueOf(classValue)); 147 intent.putExtra(BluetoothIntent.RSSI, rssiValue); 148 intent.putExtra(BluetoothIntent.NAME, name); 149 150 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 151 } else { 152 log ("ClassValue: " + classValue + " for remote device: " + address + " is null"); 153 } 154 } 155 156 private void onDeviceFound(String address, String[] properties) { 157 if (properties == null) { 158 Log.e(TAG, "ERROR: Remote device properties are null"); 159 return; 160 } 161 addDevice(address, properties); 162 } 163 164 private void onDeviceDisappeared(String address) { 165 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION); 166 intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address)); 167 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 168 } 169 170 private void onCreatePairedDeviceResult(String address, int result) { 171 address = address.toUpperCase(); 172 if (result == BluetoothError.SUCCESS) { 173 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); 174 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 175 mBluetoothService.getBondState().clearPinAttempts(address); 176 } 177 } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && 178 mBluetoothService.getBondState().getAttempt(address) == 1) { 179 mBluetoothService.getBondState().addAutoPairingFailure(address); 180 pairingAttempt(address, result); 181 } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && 182 mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 183 pairingAttempt(address, result); 184 } else { 185 mBluetoothService.getBondState().setBondState(address, 186 BluetoothDevice.BOND_NOT_BONDED, result); 187 if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { 188 mBluetoothService.getBondState().clearPinAttempts(address); 189 } 190 } 191 } 192 193 private void pairingAttempt(String address, int result) { 194 // This happens when our initial guess of "0000" as the pass key 195 // fails. Try to create the bond again and display the pin dialog 196 // to the user. Use back-off while posting the delayed 197 // message. The initial value is 198 // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is 199 // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is 200 // reached, display an error to the user. 201 int attempt = mBluetoothService.getBondState().getAttempt(address); 202 if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > 203 MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { 204 mBluetoothService.getBondState().clearPinAttempts(address); 205 mBluetoothService.getBondState().setBondState(address, 206 BluetoothDevice.BOND_NOT_BONDED, result); 207 return; 208 } 209 210 Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 211 message.obj = address; 212 boolean postResult = mHandler.sendMessageDelayed(message, 213 attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); 214 if (!postResult) { 215 mBluetoothService.getBondState().clearPinAttempts(address); 216 mBluetoothService.getBondState().setBondState(address, 217 BluetoothDevice.BOND_NOT_BONDED, result); 218 return; 219 } 220 mBluetoothService.getBondState().attempt(address); 221 } 222 223 private void onDeviceCreated(String deviceObjectPath) { 224 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 225 if (!mBluetoothService.isRemoteDeviceInCache(address)) { 226 // Incoming connection, we haven't seen this device, add to cache. 227 String[] properties = mBluetoothService.getRemoteDeviceProperties(address); 228 if (properties != null) { 229 addDevice(address, properties); 230 } 231 } 232 return; 233 } 234 235 private void onDeviceRemoved(String deviceObjectPath) { 236 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 237 if (address != null) 238 mBluetoothService.getBondState().setBondState(address.toUpperCase(), 239 BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED); 240 } 241 242 /*package*/ void onPropertyChanged(String[] propValues) { 243 if (mBluetoothService.isAdapterPropertiesEmpty()) { 244 // We have got a property change before 245 // we filled up our cache. 246 mBluetoothService.getAllProperties(); 247 } 248 String name = propValues[0]; 249 if (name.equals("Name")) { 250 Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION); 251 intent.putExtra(BluetoothIntent.NAME, propValues[1]); 252 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 253 mBluetoothService.setProperty(name, propValues[1]); 254 } else if (name.equals("Pairable") || name.equals("Discoverable")) { 255 String pairable = name.equals("Pairable") ? propValues[1] : 256 mBluetoothService.getProperty("Pairable"); 257 String discoverable = name.equals("Discoverable") ? propValues[1] : 258 mBluetoothService.getProperty("Discoverable"); 259 260 // This shouldn't happen, unless Adapter Properties are null. 261 if (pairable == null || discoverable == null) 262 return; 263 264 int mode = BluetoothService.bluezStringToScanMode( 265 pairable.equals("true"), 266 discoverable.equals("true")); 267 if (mode >= 0) { 268 Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION); 269 intent.putExtra(BluetoothIntent.SCAN_MODE, mode); 270 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 271 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 272 } 273 mBluetoothService.setProperty(name, propValues[1]); 274 } else if (name.equals("Discovering")) { 275 Intent intent; 276 if (propValues[1].equals("true")) { 277 mBluetoothService.setIsDiscovering(true); 278 intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION); 279 } else { 280 // Stop the discovery. 281 mBluetoothService.cancelDiscovery(); 282 mBluetoothService.setIsDiscovering(false); 283 intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION); 284 } 285 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 286 mBluetoothService.setProperty(name, propValues[1]); 287 } else if (name.equals("Devices")) { 288 String value = null; 289 int len = Integer.valueOf(propValues[1]); 290 if (len > 0) { 291 StringBuilder str = new StringBuilder(); 292 for (int i = 2; i < propValues.length; i++) { 293 str.append(propValues[i]); 294 str.append(","); 295 } 296 value = str.toString(); 297 } 298 mBluetoothService.setProperty(name, value); 299 } else if (name.equals("Powered")) { 300 // bluetoothd has restarted, re-read all our properties. 301 // Note: bluez only sends this property change when it restarts. 302 if (propValues[1].equals("true")) 303 onRestartRequired(); 304 } 305 } 306 307 private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) { 308 String name = propValues[0]; 309 String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); 310 if (address == null) { 311 Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); 312 return; 313 } 314 BluetoothDevice device = mAdapter.getRemoteDevice(address); 315 if (name.equals("Name")) { 316 Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); 317 intent.putExtra(BluetoothIntent.DEVICE, device); 318 intent.putExtra(BluetoothIntent.NAME, propValues[1]); 319 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 320 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 321 } else if (name.equals("Class")) { 322 Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION); 323 intent.putExtra(BluetoothIntent.DEVICE, device); 324 intent.putExtra(BluetoothIntent.CLASS, propValues[1]); 325 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 326 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 327 } else if (name.equals("Connected")) { 328 Intent intent = null; 329 if (propValues[1].equals("true")) { 330 intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); 331 } else { 332 intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION); 333 } 334 intent.putExtra(BluetoothIntent.DEVICE, device); 335 mContext.sendBroadcast(intent, BLUETOOTH_PERM); 336 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 337 } else if (name.equals("UUIDs")) { 338 String uuid = null; 339 int len = Integer.valueOf(propValues[1]); 340 if (len > 0) { 341 StringBuilder str = new StringBuilder(); 342 for (int i = 2; i < propValues.length; i++) { 343 str.append(propValues[i]); 344 str.append(","); 345 } 346 uuid = str.toString(); 347 } 348 mBluetoothService.setRemoteDeviceProperty(address, name, uuid); 349 } else if (name.equals("Paired")) { 350 if (propValues[1].equals("true")) { 351 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); 352 } else { 353 mBluetoothService.getBondState().setBondState(address, 354 BluetoothDevice.BOND_NOT_BONDED); 355 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); 356 } 357 } else if (name.equals("Trusted")) { 358 if (DBG) 359 log("set trust state succeded, value is " + propValues[1]); 360 mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); 361 } 362 } 363 364 private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { 365 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 366 if (address == null) { 367 Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + 368 "returning null"); 369 return null; 370 } 371 address = address.toUpperCase(); 372 mPasskeyAgentRequestData.put(address, new Integer(nativeData)); 373 374 if (mBluetoothService.getBluetoothState() == BluetoothAdapter.BLUETOOTH_STATE_TURNING_OFF) { 375 // shutdown path 376 mBluetoothService.cancelPairingUserInput(address); 377 return null; 378 } 379 return address; 380 } 381 382 private void onRequestConfirmation(String objectPath, int passkey, int nativeData) { 383 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 384 if (address == null) return; 385 386 Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); 387 intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address)); 388 intent.putExtra(BluetoothIntent.PASSKEY, passkey); 389 intent.putExtra(BluetoothIntent.PAIRING_VARIANT, 390 BluetoothDevice.PAIRING_VARIANT_CONFIRMATION); 391 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 392 return; 393 } 394 395 private void onRequestPasskey(String objectPath, int nativeData) { 396 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 397 if (address == null) return; 398 399 Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); 400 intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address)); 401 intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PASSKEY); 402 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 403 return; 404 } 405 406 private void onRequestPinCode(String objectPath, int nativeData) { 407 String address = checkPairingRequestAndGetAddress(objectPath, nativeData); 408 if (address == null) return; 409 410 if (mBluetoothService.getBondState().getBondState(address) == 411 BluetoothDevice.BOND_BONDING) { 412 // we initiated the bonding 413 int btClass = mBluetoothService.getRemoteClass(address); 414 415 // try 0000 once if the device looks dumb 416 switch (BluetoothClass.Device.getDevice(btClass)) { 417 case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: 418 case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: 419 case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: 420 case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: 421 case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: 422 case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: 423 if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && 424 !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { 425 mBluetoothService.getBondState().attempt(address); 426 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); 427 return; 428 } 429 } 430 } 431 Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); 432 intent.putExtra(BluetoothIntent.DEVICE, mAdapter.getRemoteDevice(address)); 433 intent.putExtra(BluetoothIntent.PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); 434 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 435 return; 436 } 437 438 private boolean onAgentAuthorize(String objectPath, String deviceUuid) { 439 String address = mBluetoothService.getAddressFromObjectPath(objectPath); 440 if (address == null) { 441 Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); 442 return false; 443 } 444 445 boolean authorized = false; 446 UUID uuid = UUID.fromString(deviceUuid); 447 // Bluez sends the UUID of the local service being accessed, _not_ the 448 // remote service 449 if (mBluetoothService.isEnabled() && 450 (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) 451 || BluetoothUuid.isAdvAudioDist(uuid))) { 452 BluetoothA2dp a2dp = new BluetoothA2dp(mContext); 453 BluetoothDevice device = mAdapter.getRemoteDevice(address); 454 authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; 455 if (authorized) { 456 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); 457 } else { 458 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); 459 } 460 } else { 461 Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); 462 } 463 log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); 464 return authorized; 465 } 466 467 private void onAgentCancel() { 468 Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); 469 mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); 470 return; 471 } 472 473 private void onRestartRequired() { 474 if (mBluetoothService.isEnabled()) { 475 Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " + 476 "restarting Bluetooth ***"); 477 mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH); 478 } 479 } 480 481 private static void log(String msg) { 482 Log.d(TAG, msg); 483 } 484 485 private native void initializeNativeDataNative(); 486 private native void startEventLoopNative(); 487 private native void stopEventLoopNative(); 488 private native boolean isEventLoopRunningNative(); 489 private native void cleanupNativeDataNative(); 490} 491