1 /* 2 * Copyright (C) 2012-2014 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 com.android.bluetooth.btservice; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothAssignedNumbers; 21 import android.bluetooth.BluetoothClass; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadset; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.ParcelUuid; 33 import android.support.annotation.VisibleForTesting; 34 import android.util.Log; 35 36 import com.android.bluetooth.R; 37 import com.android.bluetooth.Utils; 38 import com.android.bluetooth.hfp.HeadsetHalConstants; 39 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.HashSet; 43 import java.util.LinkedList; 44 import java.util.Queue; 45 import java.util.Set; 46 47 final class RemoteDevices { 48 private static final boolean DBG = false; 49 private static final String TAG = "BluetoothRemoteDevices"; 50 51 // Maximum number of device properties to remember 52 private static final int MAX_DEVICE_QUEUE_SIZE = 200; 53 54 private static BluetoothAdapter sAdapter; 55 private static AdapterService sAdapterService; 56 private static ArrayList<BluetoothDevice> sSdpTracker; 57 private final Object mObject = new Object(); 58 59 private static final int UUID_INTENT_DELAY = 6000; 60 private static final int MESSAGE_UUID_INTENT = 1; 61 62 private final HashMap<String, DeviceProperties> mDevices; 63 private Queue<String> mDeviceQueue; 64 65 private final Handler mHandler; 66 private class RemoteDevicesHandler extends Handler { 67 68 /** 69 * Handler must be created from an explicit looper to avoid threading ambiguity 70 * @param looper The looper that this handler should be executed on 71 */ RemoteDevicesHandler(Looper looper)72 RemoteDevicesHandler(Looper looper) { 73 super(looper); 74 } 75 76 @Override handleMessage(Message msg)77 public void handleMessage(Message msg) { 78 switch (msg.what) { 79 case MESSAGE_UUID_INTENT: 80 BluetoothDevice device = (BluetoothDevice) msg.obj; 81 if (device != null) { 82 sendUuidIntent(device); 83 } 84 break; 85 } 86 } 87 } 88 89 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 90 @Override 91 public void onReceive(Context context, Intent intent) { 92 String action = intent.getAction(); 93 switch (action) { 94 case BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED: 95 onHfIndicatorValueChanged(intent); 96 break; 97 case BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT: 98 onVendorSpecificHeadsetEvent(intent); 99 break; 100 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 101 onHeadsetConnectionStateChanged(intent); 102 break; 103 default: 104 Log.w(TAG, "Unhandled intent: " + intent); 105 break; 106 } 107 } 108 }; 109 RemoteDevices(AdapterService service, Looper looper)110 RemoteDevices(AdapterService service, Looper looper) { 111 sAdapter = BluetoothAdapter.getDefaultAdapter(); 112 sAdapterService = service; 113 sSdpTracker = new ArrayList<BluetoothDevice>(); 114 mDevices = new HashMap<String, DeviceProperties>(); 115 mDeviceQueue = new LinkedList<String>(); 116 mHandler = new RemoteDevicesHandler(looper); 117 } 118 119 /** 120 * Init should be called before using this RemoteDevices object 121 */ init()122 void init() { 123 IntentFilter filter = new IntentFilter(); 124 filter.addAction(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED); 125 filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); 126 filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." 127 + BluetoothAssignedNumbers.PLANTRONICS); 128 filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." 129 + BluetoothAssignedNumbers.APPLE); 130 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 131 sAdapterService.registerReceiver(mReceiver, filter); 132 } 133 134 /** 135 * Clean up should be called when this object is no longer needed, must be called after init() 136 */ cleanup()137 void cleanup() { 138 // Unregister receiver first, mAdapterService is never null 139 sAdapterService.unregisterReceiver(mReceiver); 140 reset(); 141 } 142 143 /** 144 * Reset should be called when the state of this object needs to be cleared 145 * RemoteDevices is still usable after reset 146 */ reset()147 void reset() { 148 if (sSdpTracker != null) { 149 sSdpTracker.clear(); 150 } 151 152 if (mDevices != null) { 153 mDevices.clear(); 154 } 155 156 if (mDeviceQueue != null) { 157 mDeviceQueue.clear(); 158 } 159 } 160 161 @Override clone()162 public Object clone() throws CloneNotSupportedException { 163 throw new CloneNotSupportedException(); 164 } 165 getDeviceProperties(BluetoothDevice device)166 DeviceProperties getDeviceProperties(BluetoothDevice device) { 167 synchronized (mDevices) { 168 return mDevices.get(device.getAddress()); 169 } 170 } 171 getDevice(byte[] address)172 BluetoothDevice getDevice(byte[] address) { 173 DeviceProperties prop = mDevices.get(Utils.getAddressStringFromByte(address)); 174 if (prop == null) { 175 return null; 176 } 177 return prop.getDevice(); 178 } 179 180 @VisibleForTesting addDeviceProperties(byte[] address)181 DeviceProperties addDeviceProperties(byte[] address) { 182 synchronized (mDevices) { 183 DeviceProperties prop = new DeviceProperties(); 184 prop.mDevice = sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 185 prop.mAddress = address; 186 String key = Utils.getAddressStringFromByte(address); 187 DeviceProperties pv = mDevices.put(key, prop); 188 189 if (pv == null) { 190 mDeviceQueue.offer(key); 191 if (mDeviceQueue.size() > MAX_DEVICE_QUEUE_SIZE) { 192 String deleteKey = mDeviceQueue.poll(); 193 for (BluetoothDevice device : sAdapterService.getBondedDevices()) { 194 if (device.getAddress().equals(deleteKey)) { 195 return prop; 196 } 197 } 198 debugLog("Removing device " + deleteKey + " from property map"); 199 mDevices.remove(deleteKey); 200 } 201 } 202 return prop; 203 } 204 } 205 206 class DeviceProperties { 207 private String mName; 208 private byte[] mAddress; 209 private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED; 210 private short mRssi; 211 private String mAlias; 212 private BluetoothDevice mDevice; 213 private boolean mIsBondingInitiatedLocally; 214 private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 215 @VisibleForTesting int mBondState; 216 @VisibleForTesting int mDeviceType; 217 @VisibleForTesting ParcelUuid[] mUuids; 218 DeviceProperties()219 DeviceProperties() { 220 mBondState = BluetoothDevice.BOND_NONE; 221 } 222 223 /** 224 * @return the mName 225 */ getName()226 String getName() { 227 synchronized (mObject) { 228 return mName; 229 } 230 } 231 232 /** 233 * @return the mClass 234 */ getBluetoothClass()235 int getBluetoothClass() { 236 synchronized (mObject) { 237 return mBluetoothClass; 238 } 239 } 240 241 /** 242 * @return the mUuids 243 */ getUuids()244 ParcelUuid[] getUuids() { 245 synchronized (mObject) { 246 return mUuids; 247 } 248 } 249 250 /** 251 * @return the mAddress 252 */ getAddress()253 byte[] getAddress() { 254 synchronized (mObject) { 255 return mAddress; 256 } 257 } 258 259 /** 260 * @return the mDevice 261 */ getDevice()262 BluetoothDevice getDevice() { 263 synchronized (mObject) { 264 return mDevice; 265 } 266 } 267 268 /** 269 * @return mRssi 270 */ getRssi()271 short getRssi() { 272 synchronized (mObject) { 273 return mRssi; 274 } 275 } 276 277 /** 278 * @return mDeviceType 279 */ getDeviceType()280 int getDeviceType() { 281 synchronized (mObject) { 282 return mDeviceType; 283 } 284 } 285 286 /** 287 * @return the mAlias 288 */ getAlias()289 String getAlias() { 290 synchronized (mObject) { 291 return mAlias; 292 } 293 } 294 295 /** 296 * @param mAlias the mAlias to set 297 */ setAlias(BluetoothDevice device, String mAlias)298 void setAlias(BluetoothDevice device, String mAlias) { 299 synchronized (mObject) { 300 this.mAlias = mAlias; 301 sAdapterService.setDevicePropertyNative(mAddress, 302 AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes()); 303 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED); 304 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 305 intent.putExtra(BluetoothDevice.EXTRA_NAME, mAlias); 306 sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM); 307 } 308 } 309 310 /** 311 * @param mBondState the mBondState to set 312 */ setBondState(int mBondState)313 void setBondState(int mBondState) { 314 synchronized (mObject) { 315 this.mBondState = mBondState; 316 if (mBondState == BluetoothDevice.BOND_NONE) { 317 /* Clearing the Uuids local copy when the device is unpaired. If not cleared, 318 cachedBluetoothDevice issued a connect using the local cached copy of uuids, 319 without waiting for the ACTION_UUID intent. 320 This was resulting in multiple calls to connect().*/ 321 mUuids = null; 322 } 323 } 324 } 325 326 /** 327 * @return the mBondState 328 */ getBondState()329 int getBondState() { 330 synchronized (mObject) { 331 return mBondState; 332 } 333 } 334 335 /** 336 * @param isBondingInitiatedLocally wether bonding is initiated locally 337 */ setBondingInitiatedLocally(boolean isBondingInitiatedLocally)338 void setBondingInitiatedLocally(boolean isBondingInitiatedLocally) { 339 synchronized (mObject) { 340 this.mIsBondingInitiatedLocally = isBondingInitiatedLocally; 341 } 342 } 343 344 /** 345 * @return the isBondingInitiatedLocally 346 */ isBondingInitiatedLocally()347 boolean isBondingInitiatedLocally() { 348 synchronized (mObject) { 349 return mIsBondingInitiatedLocally; 350 } 351 } 352 getBatteryLevel()353 int getBatteryLevel() { 354 synchronized (mObject) { 355 return mBatteryLevel; 356 } 357 } 358 359 /** 360 * @param batteryLevel the mBatteryLevel to set 361 */ setBatteryLevel(int batteryLevel)362 void setBatteryLevel(int batteryLevel) { 363 synchronized (mObject) { 364 this.mBatteryLevel = batteryLevel; 365 } 366 } 367 } 368 sendUuidIntent(BluetoothDevice device)369 private void sendUuidIntent(BluetoothDevice device) { 370 DeviceProperties prop = getDeviceProperties(device); 371 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 372 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 373 intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids); 374 sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM); 375 376 //Remove the outstanding UUID request 377 sSdpTracker.remove(device); 378 } 379 380 /** 381 * When bonding is initiated to remote device that we have never seen, i.e Out Of Band pairing, 382 * we must add device first before setting it's properties. This is a helper method for doing 383 * that. 384 */ setBondingInitiatedLocally(byte[] address)385 void setBondingInitiatedLocally(byte[] address) { 386 DeviceProperties properties; 387 388 BluetoothDevice device = getDevice(address); 389 if (device == null) { 390 properties = addDeviceProperties(address); 391 } else { 392 properties = getDeviceProperties(device); 393 } 394 395 properties.setBondingInitiatedLocally(true); 396 } 397 398 /** 399 * Update battery level in device properties 400 * @param device The remote device to be updated 401 * @param batteryLevel Battery level Indicator between 0-100, 402 * {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} is error 403 */ 404 @VisibleForTesting updateBatteryLevel(BluetoothDevice device, int batteryLevel)405 void updateBatteryLevel(BluetoothDevice device, int batteryLevel) { 406 if (device == null || batteryLevel < 0 || batteryLevel > 100) { 407 warnLog("Invalid parameters device=" + String.valueOf(device == null) 408 + ", batteryLevel=" + String.valueOf(batteryLevel)); 409 return; 410 } 411 DeviceProperties deviceProperties = getDeviceProperties(device); 412 if (deviceProperties == null) { 413 deviceProperties = addDeviceProperties(Utils.getByteAddress(device)); 414 } 415 synchronized (mObject) { 416 int currentBatteryLevel = deviceProperties.getBatteryLevel(); 417 if (batteryLevel == currentBatteryLevel) { 418 debugLog("Same battery level for device " + device + " received " + String.valueOf( 419 batteryLevel) + "%"); 420 return; 421 } 422 deviceProperties.setBatteryLevel(batteryLevel); 423 } 424 sendBatteryLevelChangedBroadcast(device, batteryLevel); 425 Log.d(TAG, "Updated device " + device + " battery level to " + batteryLevel + "%"); 426 } 427 428 /** 429 * Reset battery level property to {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} for a device 430 * @param device device whose battery level property needs to be reset 431 */ 432 @VisibleForTesting resetBatteryLevel(BluetoothDevice device)433 void resetBatteryLevel(BluetoothDevice device) { 434 if (device == null) { 435 warnLog("Device is null"); 436 return; 437 } 438 DeviceProperties deviceProperties = getDeviceProperties(device); 439 if (deviceProperties == null) { 440 return; 441 } 442 synchronized (mObject) { 443 if (deviceProperties.getBatteryLevel() == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { 444 debugLog("Battery level was never set or is already reset, device=" + device); 445 return; 446 } 447 deviceProperties.setBatteryLevel(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); 448 } 449 sendBatteryLevelChangedBroadcast(device, BluetoothDevice.BATTERY_LEVEL_UNKNOWN); 450 Log.d(TAG, "Reset battery level, device=" + device); 451 } 452 sendBatteryLevelChangedBroadcast(BluetoothDevice device, int batteryLevel)453 private void sendBatteryLevelChangedBroadcast(BluetoothDevice device, int batteryLevel) { 454 Intent intent = new Intent(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED); 455 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 456 intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel); 457 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 458 sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM); 459 } 460 areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2)461 private static boolean areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2) { 462 final int length1 = uuids1 == null ? 0 : uuids1.length; 463 final int length2 = uuids2 == null ? 0 : uuids2.length; 464 if (length1 != length2) { 465 return false; 466 } 467 Set<ParcelUuid> set = new HashSet<>(); 468 for (int i = 0; i < length1; ++i) { 469 set.add(uuids1[i]); 470 } 471 for (int i = 0; i < length2; ++i) { 472 set.remove(uuids2[i]); 473 } 474 return set.isEmpty(); 475 } 476 devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values)477 void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) { 478 Intent intent; 479 byte[] val; 480 int type; 481 BluetoothDevice bdDevice = getDevice(address); 482 DeviceProperties device; 483 if (bdDevice == null) { 484 debugLog("Added new device property"); 485 device = addDeviceProperties(address); 486 bdDevice = getDevice(address); 487 } else { 488 device = getDeviceProperties(bdDevice); 489 } 490 491 if (types.length <= 0) { 492 errorLog("No properties to update"); 493 return; 494 } 495 496 for (int j = 0; j < types.length; j++) { 497 type = types[j]; 498 val = values[j]; 499 if (val.length > 0) { 500 synchronized (mObject) { 501 debugLog("Property type: " + type); 502 switch (type) { 503 case AbstractionLayer.BT_PROPERTY_BDNAME: 504 final String newName = new String(val); 505 if (newName.equals(device.mName)) { 506 Log.w(TAG, "Skip name update for " + bdDevice); 507 break; 508 } 509 device.mName = newName; 510 intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); 511 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); 512 intent.putExtra(BluetoothDevice.EXTRA_NAME, device.mName); 513 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 514 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 515 debugLog("Remote Device name is: " + device.mName); 516 break; 517 case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: 518 device.mAlias = new String(val); 519 debugLog("Remote device alias is: " + device.mAlias); 520 break; 521 case AbstractionLayer.BT_PROPERTY_BDADDR: 522 device.mAddress = val; 523 debugLog("Remote Address is:" + Utils.getAddressStringFromByte(val)); 524 break; 525 case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: 526 final int newClass = Utils.byteArrayToInt(val); 527 if (newClass == device.mBluetoothClass) { 528 Log.w(TAG, "Skip class update for " + bdDevice); 529 break; 530 } 531 device.mBluetoothClass = Utils.byteArrayToInt(val); 532 intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); 533 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); 534 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 535 new BluetoothClass(device.mBluetoothClass)); 536 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 537 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 538 debugLog("Remote class is:" + device.mBluetoothClass); 539 break; 540 case AbstractionLayer.BT_PROPERTY_UUIDS: 541 int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE; 542 final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val); 543 if (areUuidsEqual(newUuids, device.mUuids)) { 544 Log.w(TAG, "Skip uuids update for " + bdDevice.getAddress()); 545 break; 546 } 547 device.mUuids = newUuids; 548 if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) { 549 sAdapterService.deviceUuidUpdated(bdDevice); 550 sendUuidIntent(bdDevice); 551 } 552 break; 553 case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: 554 // The device type from hal layer, defined in bluetooth.h, 555 // matches the type defined in BluetoothDevice.java 556 device.mDeviceType = Utils.byteArrayToInt(val); 557 break; 558 case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: 559 // RSSI from hal is in one byte 560 device.mRssi = val[0]; 561 break; 562 } 563 } 564 } 565 } 566 } 567 deviceFoundCallback(byte[] address)568 void deviceFoundCallback(byte[] address) { 569 // The device properties are already registered - we can send the intent 570 // now 571 BluetoothDevice device = getDevice(address); 572 debugLog("deviceFoundCallback: Remote Address is:" + device); 573 DeviceProperties deviceProp = getDeviceProperties(device); 574 if (deviceProp == null) { 575 errorLog("Device Properties is null for Device:" + device); 576 return; 577 } 578 579 Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); 580 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 581 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 582 new BluetoothClass(deviceProp.mBluetoothClass)); 583 intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi); 584 intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName); 585 586 sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{ 587 AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION 588 }); 589 } 590 aclStateChangeCallback(int status, byte[] address, int newState)591 void aclStateChangeCallback(int status, byte[] address, int newState) { 592 BluetoothDevice device = getDevice(address); 593 594 if (device == null) { 595 errorLog("aclStateChangeCallback: device is NULL, address=" 596 + Utils.getAddressStringFromByte(address) + ", newState=" + newState); 597 return; 598 } 599 int state = sAdapterService.getState(); 600 601 Intent intent = null; 602 if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) { 603 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_ON) { 604 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); 605 } else if (state == BluetoothAdapter.STATE_BLE_ON 606 || state == BluetoothAdapter.STATE_BLE_TURNING_ON) { 607 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_CONNECTED); 608 } 609 debugLog( 610 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 611 + " Connected: " + device); 612 } else { 613 if (device.getBondState() == BluetoothDevice.BOND_BONDING) { 614 // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding. 615 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); 616 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 617 intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package)); 618 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 619 } 620 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) { 621 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 622 } else if (state == BluetoothAdapter.STATE_BLE_ON 623 || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { 624 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED); 625 } 626 // Reset battery level on complete disconnection 627 if (sAdapterService.getConnectionState(device) == 0) { 628 resetBatteryLevel(device); 629 } 630 debugLog( 631 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 632 + " Disconnected: " + device); 633 } 634 635 if (intent != null) { 636 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 637 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 638 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 639 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 640 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 641 } else { 642 Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: " 643 + device.getBondState()); 644 } 645 } 646 647 fetchUuids(BluetoothDevice device)648 void fetchUuids(BluetoothDevice device) { 649 if (sSdpTracker.contains(device)) { 650 return; 651 } 652 sSdpTracker.add(device); 653 654 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 655 message.obj = device; 656 mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY); 657 658 sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress())); 659 } 660 updateUuids(BluetoothDevice device)661 void updateUuids(BluetoothDevice device) { 662 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 663 message.obj = device; 664 mHandler.sendMessage(message); 665 } 666 667 /** 668 * Handles headset connection state change event 669 * @param intent must be {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent 670 */ 671 @VisibleForTesting onHeadsetConnectionStateChanged(Intent intent)672 void onHeadsetConnectionStateChanged(Intent intent) { 673 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 674 if (device == null) { 675 Log.e(TAG, "onHeadsetConnectionStateChanged() remote device is null"); 676 return; 677 } 678 if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) 679 == BluetoothProfile.STATE_DISCONNECTED) { 680 // TODO: Rework this when non-HFP sources of battery level indication is added 681 resetBatteryLevel(device); 682 } 683 } 684 685 @VisibleForTesting onHfIndicatorValueChanged(Intent intent)686 void onHfIndicatorValueChanged(Intent intent) { 687 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 688 if (device == null) { 689 Log.e(TAG, "onHfIndicatorValueChanged() remote device is null"); 690 return; 691 } 692 int indicatorId = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1); 693 int indicatorValue = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -1); 694 if (indicatorId == HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS) { 695 updateBatteryLevel(device, indicatorValue); 696 } 697 } 698 699 /** 700 * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 701 * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 702 */ 703 @VisibleForTesting onVendorSpecificHeadsetEvent(Intent intent)704 void onVendorSpecificHeadsetEvent(Intent intent) { 705 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 706 if (device == null) { 707 Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null"); 708 return; 709 } 710 String cmd = 711 intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); 712 if (cmd == null) { 713 Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null"); 714 return; 715 } 716 int cmdType = 717 intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, 718 -1); 719 // Only process set command 720 if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) { 721 debugLog("onVendorSpecificHeadsetEvent() only SET command is processed"); 722 return; 723 } 724 Object[] args = (Object[]) intent.getExtras() 725 .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); 726 if (args == null) { 727 Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null"); 728 return; 729 } 730 int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 731 switch (cmd) { 732 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: 733 batteryPercent = getBatteryLevelFromXEventVsc(args); 734 break; 735 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV: 736 batteryPercent = getBatteryLevelFromAppleBatteryVsc(args); 737 break; 738 } 739 if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { 740 updateBatteryLevel(device, batteryPercent); 741 infoLog("Updated device " + device + " battery level to " + String.valueOf( 742 batteryPercent) + "%"); 743 } 744 } 745 746 /** 747 * Parse 748 * AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue] 749 * vendor specific event 750 * @param args Array of arguments on the right side of assignment 751 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 752 * when there is an error parsing the arguments 753 */ 754 @VisibleForTesting getBatteryLevelFromAppleBatteryVsc(Object[] args)755 static int getBatteryLevelFromAppleBatteryVsc(Object[] args) { 756 if (args.length == 0) { 757 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments"); 758 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 759 } 760 int numKvPair; 761 if (args[0] instanceof Integer) { 762 numKvPair = (Integer) args[0]; 763 } else { 764 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments"); 765 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 766 } 767 if (args.length != (numKvPair * 2 + 1)) { 768 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match"); 769 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 770 } 771 int indicatorType; 772 int indicatorValue = -1; 773 for (int i = 0; i < numKvPair; ++i) { 774 Object indicatorTypeObj = args[2 * i + 1]; 775 if (indicatorTypeObj instanceof Integer) { 776 indicatorType = (Integer) indicatorTypeObj; 777 } else { 778 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type"); 779 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 780 } 781 if (indicatorType 782 != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) { 783 continue; 784 } 785 Object indicatorValueObj = args[2 * i + 2]; 786 if (indicatorValueObj instanceof Integer) { 787 indicatorValue = (Integer) indicatorValueObj; 788 } else { 789 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value"); 790 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 791 } 792 break; 793 } 794 return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN 795 : (indicatorValue + 1) * 10; 796 } 797 798 /** 799 * Parse 800 * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] 801 * vendor specific event 802 * @param args Array of arguments on the right side of SET command 803 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 804 * when there is an error parsing the arguments 805 */ 806 @VisibleForTesting getBatteryLevelFromXEventVsc(Object[] args)807 static int getBatteryLevelFromXEventVsc(Object[] args) { 808 if (args.length == 0) { 809 Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments"); 810 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 811 } 812 Object eventNameObj = args[0]; 813 if (!(eventNameObj instanceof String)) { 814 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name"); 815 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 816 } 817 String eventName = (String) eventNameObj; 818 if (!eventName.equals( 819 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) { 820 infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName); 821 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 822 } 823 if (args.length != 5) { 824 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: " 825 + String.valueOf(args.length)); 826 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 827 } 828 if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) { 829 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values"); 830 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 831 } 832 int batteryLevel = (Integer) args[1]; 833 int numberOfLevels = (Integer) args[2]; 834 if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) { 835 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel=" 836 + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf( 837 numberOfLevels)); 838 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 839 } 840 return batteryLevel * 100 / numberOfLevels; 841 } 842 errorLog(String msg)843 private static void errorLog(String msg) { 844 Log.e(TAG, msg); 845 } 846 debugLog(String msg)847 private static void debugLog(String msg) { 848 if (DBG) { 849 Log.d(TAG, msg); 850 } 851 } 852 infoLog(String msg)853 private static void infoLog(String msg) { 854 if (DBG) { 855 Log.i(TAG, msg); 856 } 857 } 858 warnLog(String msg)859 private static void warnLog(String msg) { 860 Log.w(TAG, msg); 861 } 862 863 } 864