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 static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_SCAN; 21 22 import android.app.admin.SecurityLog; 23 import android.bluetooth.BluetoothAdapter; 24 import android.bluetooth.BluetoothAssignedNumbers; 25 import android.bluetooth.BluetoothClass; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.BluetoothHeadset; 28 import android.bluetooth.BluetoothHeadsetClient; 29 import android.bluetooth.BluetoothProfile; 30 import android.bluetooth.BluetoothSinkAudioPolicy; 31 import android.bluetooth.IBluetoothConnectionCallback; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.net.MacAddress; 37 import android.os.Handler; 38 import android.os.Looper; 39 import android.os.Message; 40 import android.os.ParcelUuid; 41 import android.os.RemoteException; 42 import android.os.SystemProperties; 43 import android.util.Log; 44 45 import com.android.bluetooth.BluetoothStatsLog; 46 import com.android.bluetooth.R; 47 import com.android.bluetooth.Utils; 48 import com.android.bluetooth.bas.BatteryService; 49 import com.android.bluetooth.hfp.HeadsetHalConstants; 50 import com.android.internal.annotations.VisibleForTesting; 51 52 import java.util.ArrayList; 53 import java.util.HashMap; 54 import java.util.HashSet; 55 import java.util.LinkedList; 56 import java.util.Queue; 57 import java.util.Set; 58 import java.util.function.Predicate; 59 60 final class RemoteDevices { 61 private static final boolean DBG = false; 62 private static final String TAG = "BluetoothRemoteDevices"; 63 64 // Maximum number of device properties to remember 65 private static final int MAX_DEVICE_QUEUE_SIZE = 200; 66 67 private static BluetoothAdapter sAdapter; 68 private static AdapterService sAdapterService; 69 private static ArrayList<BluetoothDevice> sSdpTracker; 70 private final Object mObject = new Object(); 71 72 private static final int UUID_INTENT_DELAY = 6000; 73 private static final int MESSAGE_UUID_INTENT = 1; 74 75 private final HashMap<String, DeviceProperties> mDevices; 76 private final HashMap<String, String> mDualDevicesMap; 77 private Queue<String> mDeviceQueue; 78 79 /** 80 * Bluetooth HFP v1.8 specifies the Battery Charge indicator of AG can take values from 81 * {@code 0} to {@code 5}, but it does not specify how to map the values back to percentages. 82 * The following mapping is used: 83 * - Level 0: 0% 84 * - Level 1: midpoint of 1-25% 85 * - Level 2: midpoint of 26-50% 86 * - Level 3: midpoint of 51-75% 87 * - Level 4: midpoint of 76-99% 88 * - Level 5: 100% 89 */ 90 private static final int HFP_BATTERY_CHARGE_INDICATOR_0 = 0; 91 private static final int HFP_BATTERY_CHARGE_INDICATOR_1 = 13; 92 private static final int HFP_BATTERY_CHARGE_INDICATOR_2 = 38; 93 private static final int HFP_BATTERY_CHARGE_INDICATOR_3 = 63; 94 private static final int HFP_BATTERY_CHARGE_INDICATOR_4 = 88; 95 private static final int HFP_BATTERY_CHARGE_INDICATOR_5 = 100; 96 97 private final Handler mHandler; 98 private class RemoteDevicesHandler extends Handler { 99 100 /** 101 * Handler must be created from an explicit looper to avoid threading ambiguity 102 * @param looper The looper that this handler should be executed on 103 */ RemoteDevicesHandler(Looper looper)104 RemoteDevicesHandler(Looper looper) { 105 super(looper); 106 } 107 108 @Override handleMessage(Message msg)109 public void handleMessage(Message msg) { 110 switch (msg.what) { 111 case MESSAGE_UUID_INTENT: 112 BluetoothDevice device = (BluetoothDevice) msg.obj; 113 if (device != null) { 114 DeviceProperties prop = getDeviceProperties(device); 115 sendUuidIntent(device, prop); 116 } 117 break; 118 } 119 } 120 } 121 122 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 123 @Override 124 public void onReceive(Context context, Intent intent) { 125 String action = intent.getAction(); 126 switch (action) { 127 case BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED: 128 onHfIndicatorValueChanged(intent); 129 break; 130 case BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT: 131 onVendorSpecificHeadsetEvent(intent); 132 break; 133 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED: 134 onHeadsetConnectionStateChanged(intent); 135 break; 136 case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED: 137 onHeadsetClientConnectionStateChanged(intent); 138 break; 139 case BluetoothHeadsetClient.ACTION_AG_EVENT: 140 onAgIndicatorValueChanged(intent); 141 break; 142 default: 143 Log.w(TAG, "Unhandled intent: " + intent); 144 break; 145 } 146 } 147 }; 148 149 /** 150 * Predicate that tests if the given {@link BluetoothDevice} is well-known 151 * to be used for physical location. 152 */ 153 private final Predicate<BluetoothDevice> mLocationDenylistPredicate = (device) -> { 154 final MacAddress parsedAddress = MacAddress.fromString(device.getAddress()); 155 if (sAdapterService.getLocationDenylistMac().test(parsedAddress.toByteArray())) { 156 Log.v(TAG, "Skipping device matching denylist: " + parsedAddress); 157 return true; 158 } 159 final String name = Utils.getName(device); 160 if (sAdapterService.getLocationDenylistName().test(name)) { 161 Log.v(TAG, "Skipping name matching denylist: " + name); 162 return true; 163 } 164 return false; 165 }; 166 RemoteDevices(AdapterService service, Looper looper)167 RemoteDevices(AdapterService service, Looper looper) { 168 sAdapter = BluetoothAdapter.getDefaultAdapter(); 169 sAdapterService = service; 170 sSdpTracker = new ArrayList<BluetoothDevice>(); 171 mDevices = new HashMap<String, DeviceProperties>(); 172 mDualDevicesMap = new HashMap<String, String>(); 173 mDeviceQueue = new LinkedList<String>(); 174 mHandler = new RemoteDevicesHandler(looper); 175 } 176 177 /** 178 * Init should be called before using this RemoteDevices object 179 */ init()180 void init() { 181 IntentFilter filter = new IntentFilter(); 182 filter.addAction(BluetoothHeadset.ACTION_HF_INDICATORS_VALUE_CHANGED); 183 filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT); 184 filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." 185 + BluetoothAssignedNumbers.PLANTRONICS); 186 filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY + "." 187 + BluetoothAssignedNumbers.APPLE); 188 filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); 189 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 190 filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT); 191 sAdapterService.registerReceiver(mReceiver, filter); 192 } 193 194 /** 195 * Clean up should be called when this object is no longer needed, must be called after init() 196 */ cleanup()197 void cleanup() { 198 // Unregister receiver first, mAdapterService is never null 199 sAdapterService.unregisterReceiver(mReceiver); 200 reset(); 201 } 202 203 /** 204 * Reset should be called when the state of this object needs to be cleared 205 * RemoteDevices is still usable after reset 206 */ reset()207 void reset() { 208 if (sSdpTracker != null) { 209 sSdpTracker.clear(); 210 } 211 212 synchronized (mDevices) { 213 if (mDevices != null) { 214 debugLog("reset(): Broadcasting ACL_DISCONNECTED"); 215 216 mDevices.forEach((address, deviceProperties) -> { 217 BluetoothDevice bluetoothDevice = deviceProperties.getDevice(); 218 219 debugLog("reset(): address=" + address + ", connected=" 220 + bluetoothDevice.isConnected()); 221 222 if (bluetoothDevice.isConnected()) { 223 Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 224 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bluetoothDevice); 225 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 226 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 227 sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM); 228 } 229 }); 230 mDevices.clear(); 231 } 232 } 233 234 if (mDualDevicesMap != null) { 235 mDualDevicesMap.clear(); 236 } 237 238 if (mDeviceQueue != null) { 239 mDeviceQueue.clear(); 240 } 241 } 242 243 @Override clone()244 public Object clone() throws CloneNotSupportedException { 245 throw new CloneNotSupportedException(); 246 } 247 getDeviceProperties(BluetoothDevice device)248 DeviceProperties getDeviceProperties(BluetoothDevice device) { 249 synchronized (mDevices) { 250 String address = mDualDevicesMap.get(device.getAddress()); 251 // If the device is not in the dual map, use its original address 252 if (address == null || mDevices.get(address) == null) { 253 address = device.getAddress(); 254 } 255 return mDevices.get(address); 256 } 257 } 258 getDevice(byte[] address)259 BluetoothDevice getDevice(byte[] address) { 260 String addressString = Utils.getAddressStringFromByte(address); 261 String deviceAddress = mDualDevicesMap.get(addressString); 262 // If the device is not in the dual map, use its original address 263 if (deviceAddress == null || mDevices.get(deviceAddress) == null) { 264 deviceAddress = addressString; 265 } 266 267 DeviceProperties prop = mDevices.get(deviceAddress); 268 if (prop != null) { 269 return prop.getDevice(); 270 } 271 return null; 272 } 273 274 @VisibleForTesting addDeviceProperties(byte[] address)275 DeviceProperties addDeviceProperties(byte[] address) { 276 synchronized (mDevices) { 277 DeviceProperties prop = new DeviceProperties(); 278 prop.mDevice = sAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); 279 prop.mAddress = address; 280 String key = Utils.getAddressStringFromByte(address); 281 DeviceProperties pv = mDevices.put(key, prop); 282 283 if (pv == null) { 284 mDeviceQueue.offer(key); 285 if (mDeviceQueue.size() > MAX_DEVICE_QUEUE_SIZE) { 286 String deleteKey = mDeviceQueue.poll(); 287 for (BluetoothDevice device : sAdapterService.getBondedDevices()) { 288 if (device.getAddress().equals(deleteKey)) { 289 return prop; 290 } 291 } 292 debugLog("Removing device " + deleteKey + " from property map"); 293 mDevices.remove(deleteKey); 294 } 295 } 296 return prop; 297 } 298 } 299 300 class DeviceProperties { 301 private String mName; 302 private byte[] mAddress; 303 private String mIdentityAddress; 304 private boolean mIsConsolidated = false; 305 private int mBluetoothClass = BluetoothClass.Device.Major.UNCATEGORIZED; 306 private short mRssi; 307 private String mAlias; 308 private BluetoothDevice mDevice; 309 private boolean mIsBondingInitiatedLocally; 310 private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 311 private boolean mIsCoordinatedSetMember; 312 @VisibleForTesting int mBondState; 313 @VisibleForTesting int mDeviceType; 314 @VisibleForTesting ParcelUuid[] mUuids; 315 private BluetoothSinkAudioPolicy mAudioPolicy; 316 DeviceProperties()317 DeviceProperties() { 318 mBondState = BluetoothDevice.BOND_NONE; 319 } 320 321 /** 322 * @return the mName 323 */ getName()324 String getName() { 325 synchronized (mObject) { 326 return mName; 327 } 328 } 329 330 /** 331 * @return the mIdentityAddress 332 */ getIdentityAddress()333 String getIdentityAddress() { 334 synchronized (mObject) { 335 return mIdentityAddress; 336 } 337 } 338 339 /** 340 * @return mIsConsolidated 341 */ isConsolidated()342 boolean isConsolidated() { 343 synchronized (mObject) { 344 return mIsConsolidated; 345 } 346 } 347 348 /** 349 * @return the mClass 350 */ getBluetoothClass()351 int getBluetoothClass() { 352 synchronized (mObject) { 353 return mBluetoothClass; 354 } 355 } 356 357 /** 358 * @return the mUuids 359 */ getUuids()360 ParcelUuid[] getUuids() { 361 synchronized (mObject) { 362 return mUuids; 363 } 364 } 365 366 /** 367 * @return the mAddress 368 */ getAddress()369 byte[] getAddress() { 370 synchronized (mObject) { 371 return mAddress; 372 } 373 } 374 375 /** 376 * @return the mDevice 377 */ getDevice()378 BluetoothDevice getDevice() { 379 synchronized (mObject) { 380 return mDevice; 381 } 382 } 383 384 /** 385 * @return mRssi 386 */ getRssi()387 short getRssi() { 388 synchronized (mObject) { 389 return mRssi; 390 } 391 } 392 /** 393 * @return mDeviceType 394 */ getDeviceType()395 int getDeviceType() { 396 synchronized (mObject) { 397 return mDeviceType; 398 } 399 } 400 401 /** 402 * @return the mAlias 403 */ getAlias()404 String getAlias() { 405 synchronized (mObject) { 406 return mAlias; 407 } 408 } 409 410 /** 411 * @param mAlias the mAlias to set 412 */ setAlias(BluetoothDevice device, String mAlias)413 void setAlias(BluetoothDevice device, String mAlias) { 414 synchronized (mObject) { 415 this.mAlias = mAlias; 416 sAdapterService.setDevicePropertyNative(mAddress, 417 AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME, mAlias.getBytes()); 418 Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED); 419 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 420 intent.putExtra(BluetoothDevice.EXTRA_NAME, mAlias); 421 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 422 Utils.getTempAllowlistBroadcastOptions()); 423 } 424 } 425 426 /** 427 * @param mBondState the mBondState to set 428 */ setBondState(int newBondState)429 void setBondState(int newBondState) { 430 synchronized (mObject) { 431 if ((mBondState == BluetoothDevice.BOND_BONDED 432 && newBondState == BluetoothDevice.BOND_BONDING) 433 || newBondState == BluetoothDevice.BOND_NONE) { 434 /* Clearing the Uuids local copy when the device is unpaired. If not cleared, 435 cachedBluetoothDevice issued a connect using the local cached copy of uuids, 436 without waiting for the ACTION_UUID intent. 437 This was resulting in multiple calls to connect().*/ 438 mUuids = null; 439 mAlias = null; 440 } 441 mBondState = newBondState; 442 } 443 } 444 445 /** 446 * @return the mBondState 447 */ getBondState()448 int getBondState() { 449 synchronized (mObject) { 450 return mBondState; 451 } 452 } 453 isBonding()454 boolean isBonding() { 455 return getBondState() == BluetoothDevice.BOND_BONDING; 456 } 457 isBondingOrBonded()458 boolean isBondingOrBonded() { 459 return isBonding() || getBondState() == BluetoothDevice.BOND_BONDED; 460 } 461 462 /** 463 * @param isBondingInitiatedLocally wether bonding is initiated locally 464 */ setBondingInitiatedLocally(boolean isBondingInitiatedLocally)465 void setBondingInitiatedLocally(boolean isBondingInitiatedLocally) { 466 synchronized (mObject) { 467 this.mIsBondingInitiatedLocally = isBondingInitiatedLocally; 468 } 469 } 470 471 /** 472 * @return the isBondingInitiatedLocally 473 */ isBondingInitiatedLocally()474 boolean isBondingInitiatedLocally() { 475 synchronized (mObject) { 476 return mIsBondingInitiatedLocally; 477 } 478 } 479 getBatteryLevel()480 int getBatteryLevel() { 481 synchronized (mObject) { 482 return mBatteryLevel; 483 } 484 } 485 486 /** 487 * @param batteryLevel the mBatteryLevel to set 488 */ setBatteryLevel(int batteryLevel)489 void setBatteryLevel(int batteryLevel) { 490 synchronized (mObject) { 491 this.mBatteryLevel = batteryLevel; 492 } 493 } 494 495 /** 496 * @return the mIsCoordinatedSetMember 497 */ isCoordinatedSetMember()498 private boolean isCoordinatedSetMember() { 499 synchronized (mObject) { 500 return mIsCoordinatedSetMember; 501 } 502 } 503 setHfAudioPolicyForRemoteAg(BluetoothSinkAudioPolicy policies)504 public void setHfAudioPolicyForRemoteAg(BluetoothSinkAudioPolicy policies) { 505 mAudioPolicy = policies; 506 } 507 getHfAudioPolicyForRemoteAg()508 public BluetoothSinkAudioPolicy getHfAudioPolicyForRemoteAg() { 509 return mAudioPolicy; 510 } 511 } 512 sendUuidIntent(BluetoothDevice device, DeviceProperties prop)513 private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) { 514 Intent intent = new Intent(BluetoothDevice.ACTION_UUID); 515 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 516 intent.putExtra(BluetoothDevice.EXTRA_UUID, prop == null ? null : prop.mUuids); 517 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 518 Utils.getTempAllowlistBroadcastOptions()); 519 520 //Remove the outstanding UUID request 521 sSdpTracker.remove(device); 522 } 523 524 /** 525 * When bonding is initiated to remote device that we have never seen, i.e Out Of Band pairing, 526 * we must add device first before setting it's properties. This is a helper method for doing 527 * that. 528 */ setBondingInitiatedLocally(byte[] address)529 void setBondingInitiatedLocally(byte[] address) { 530 DeviceProperties properties; 531 532 BluetoothDevice device = getDevice(address); 533 if (device == null) { 534 properties = addDeviceProperties(address); 535 } else { 536 properties = getDeviceProperties(device); 537 } 538 539 properties.setBondingInitiatedLocally(true); 540 } 541 542 /** 543 * Update battery level in device properties 544 * @param device The remote device to be updated 545 * @param batteryLevel Battery level Indicator between 0-100, 546 * {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} is error 547 */ 548 @VisibleForTesting updateBatteryLevel(BluetoothDevice device, int batteryLevel)549 void updateBatteryLevel(BluetoothDevice device, int batteryLevel) { 550 if (device == null || batteryLevel < 0 || batteryLevel > 100) { 551 warnLog("Invalid parameters device=" + String.valueOf(device == null) 552 + ", batteryLevel=" + String.valueOf(batteryLevel)); 553 return; 554 } 555 DeviceProperties deviceProperties = getDeviceProperties(device); 556 if (deviceProperties == null) { 557 deviceProperties = addDeviceProperties(Utils.getByteAddress(device)); 558 } 559 synchronized (mObject) { 560 int currentBatteryLevel = deviceProperties.getBatteryLevel(); 561 if (batteryLevel == currentBatteryLevel) { 562 debugLog("Same battery level for device " + device + " received " + String.valueOf( 563 batteryLevel) + "%"); 564 return; 565 } 566 deviceProperties.setBatteryLevel(batteryLevel); 567 } 568 sendBatteryLevelChangedBroadcast(device, batteryLevel); 569 Log.d(TAG, "Updated device " + device + " battery level to " + batteryLevel + "%"); 570 } 571 572 /** 573 * Reset battery level property to {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} for a device 574 * @param device device whose battery level property needs to be reset 575 */ 576 @VisibleForTesting resetBatteryLevel(BluetoothDevice device)577 void resetBatteryLevel(BluetoothDevice device) { 578 if (device == null) { 579 warnLog("Device is null"); 580 return; 581 } 582 DeviceProperties deviceProperties = getDeviceProperties(device); 583 if (deviceProperties == null) { 584 return; 585 } 586 synchronized (mObject) { 587 if (deviceProperties.getBatteryLevel() == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { 588 debugLog("Battery level was never set or is already reset, device=" + device); 589 return; 590 } 591 deviceProperties.setBatteryLevel(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); 592 } 593 sendBatteryLevelChangedBroadcast(device, BluetoothDevice.BATTERY_LEVEL_UNKNOWN); 594 Log.d(TAG, "Reset battery level, device=" + device); 595 } 596 sendBatteryLevelChangedBroadcast(BluetoothDevice device, int batteryLevel)597 private void sendBatteryLevelChangedBroadcast(BluetoothDevice device, int batteryLevel) { 598 Intent intent = new Intent(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED); 599 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 600 intent.putExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, batteryLevel); 601 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 602 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 603 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 604 Utils.getTempAllowlistBroadcastOptions()); 605 } 606 607 /** 608 * Converts HFP's Battery Charge indicator values of {@code 0 -- 5} to an integer percentage. 609 */ 610 @VisibleForTesting batteryChargeIndicatorToPercentge(int indicator)611 static int batteryChargeIndicatorToPercentge(int indicator) { 612 int percent; 613 switch (indicator) { 614 case 5: 615 percent = HFP_BATTERY_CHARGE_INDICATOR_5; 616 break; 617 case 4: 618 percent = HFP_BATTERY_CHARGE_INDICATOR_4; 619 break; 620 case 3: 621 percent = HFP_BATTERY_CHARGE_INDICATOR_3; 622 break; 623 case 2: 624 percent = HFP_BATTERY_CHARGE_INDICATOR_2; 625 break; 626 case 1: 627 percent = HFP_BATTERY_CHARGE_INDICATOR_1; 628 break; 629 case 0: 630 percent = HFP_BATTERY_CHARGE_INDICATOR_0; 631 break; 632 default: 633 percent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 634 } 635 Log.d(TAG, "Battery charge indicator: " + indicator + "; converted to: " + percent + "%"); 636 return percent; 637 } 638 areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2)639 private static boolean areUuidsEqual(ParcelUuid[] uuids1, ParcelUuid[] uuids2) { 640 final int length1 = uuids1 == null ? 0 : uuids1.length; 641 final int length2 = uuids2 == null ? 0 : uuids2.length; 642 if (length1 != length2) { 643 return false; 644 } 645 Set<ParcelUuid> set = new HashSet<>(); 646 for (int i = 0; i < length1; ++i) { 647 set.add(uuids1[i]); 648 } 649 for (int i = 0; i < length2; ++i) { 650 set.remove(uuids2[i]); 651 } 652 return set.isEmpty(); 653 } 654 devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values)655 void devicePropertyChangedCallback(byte[] address, int[] types, byte[][] values) { 656 Intent intent; 657 byte[] val; 658 int type; 659 BluetoothDevice bdDevice = getDevice(address); 660 DeviceProperties device; 661 if (bdDevice == null) { 662 debugLog("Added new device property"); 663 device = addDeviceProperties(address); 664 bdDevice = getDevice(address); 665 } else { 666 device = getDeviceProperties(bdDevice); 667 } 668 669 if (types.length <= 0) { 670 errorLog("No properties to update"); 671 return; 672 } 673 674 for (int j = 0; j < types.length; j++) { 675 type = types[j]; 676 val = values[j]; 677 if (val.length > 0) { 678 synchronized (mObject) { 679 debugLog("Property type: " + type); 680 switch (type) { 681 case AbstractionLayer.BT_PROPERTY_BDNAME: 682 final String newName = new String(val); 683 if (newName.equals(device.mName)) { 684 debugLog("Skip name update for " + bdDevice); 685 break; 686 } 687 device.mName = newName; 688 intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); 689 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); 690 intent.putExtra(BluetoothDevice.EXTRA_NAME, device.mName); 691 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 692 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 693 Utils.getTempAllowlistBroadcastOptions()); 694 debugLog("Remote Device name is: " + device.mName); 695 break; 696 case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: 697 device.mAlias = new String(val); 698 debugLog("Remote device alias is: " + device.mAlias); 699 break; 700 case AbstractionLayer.BT_PROPERTY_BDADDR: 701 device.mAddress = val; 702 debugLog("Remote Address is:" + Utils.getAddressStringFromByte(val)); 703 break; 704 case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: 705 final int newClass = Utils.byteArrayToInt(val); 706 if (newClass == device.mBluetoothClass) { 707 debugLog("Skip class update for " + bdDevice); 708 break; 709 } 710 device.mBluetoothClass = Utils.byteArrayToInt(val); 711 intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); 712 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); 713 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 714 new BluetoothClass(device.mBluetoothClass)); 715 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 716 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 717 Utils.getTempAllowlistBroadcastOptions()); 718 debugLog("Remote class is:" + device.mBluetoothClass); 719 break; 720 case AbstractionLayer.BT_PROPERTY_UUIDS: 721 int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE; 722 final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val); 723 if (areUuidsEqual(newUuids, device.mUuids)) { 724 debugLog( "Skip uuids update for " + bdDevice.getAddress()); 725 break; 726 } 727 device.mUuids = newUuids; 728 if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) { 729 sAdapterService.deviceUuidUpdated(bdDevice); 730 sendUuidIntent(bdDevice, device); 731 } else if (sAdapterService.getState() 732 == BluetoothAdapter.STATE_BLE_ON) { 733 sAdapterService.deviceUuidUpdated(bdDevice); 734 } 735 break; 736 case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: 737 if (device.isConsolidated()) { 738 break; 739 } 740 // The device type from hal layer, defined in bluetooth.h, 741 // matches the type defined in BluetoothDevice.java 742 device.mDeviceType = Utils.byteArrayToInt(val); 743 break; 744 case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: 745 // RSSI from hal is in one byte 746 device.mRssi = val[0]; 747 break; 748 case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER: 749 device.mIsCoordinatedSetMember = (boolean) (val[0] != 0); 750 break; 751 } 752 } 753 } 754 } 755 } 756 deviceFoundCallback(byte[] address)757 void deviceFoundCallback(byte[] address) { 758 // The device properties are already registered - we can send the intent 759 // now 760 BluetoothDevice device = getDevice(address); 761 debugLog("deviceFoundCallback: Remote Address is:" + device); 762 DeviceProperties deviceProp = getDeviceProperties(device); 763 if (deviceProp == null) { 764 errorLog("Device Properties is null for Device:" + device); 765 return; 766 } 767 boolean restrict_device_found = 768 SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false); 769 if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) { 770 debugLog("Device name is null or empty: " + device); 771 return; 772 } 773 774 Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); 775 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 776 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 777 new BluetoothClass(deviceProp.mBluetoothClass)); 778 intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi); 779 intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName); 780 intent.putExtra(BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER, 781 deviceProp.mIsCoordinatedSetMember); 782 783 final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages(); 784 synchronized (packages) { 785 for (DiscoveringPackage pkg : packages) { 786 if (pkg.hasDisavowedLocation()) { 787 if (mLocationDenylistPredicate.test(device)) { 788 continue; 789 } 790 } 791 792 intent.setPackage(pkg.getPackageName()); 793 794 if (pkg.getPermission() != null) { 795 sAdapterService.sendBroadcastMultiplePermissions(intent, 796 new String[] { BLUETOOTH_SCAN, pkg.getPermission() }, 797 Utils.getTempBroadcastOptions()); 798 } else { 799 sAdapterService.sendBroadcastMultiplePermissions(intent, 800 new String[] { BLUETOOTH_SCAN }, 801 Utils.getTempBroadcastOptions()); 802 } 803 } 804 } 805 } 806 addressConsolidateCallback(byte[] mainAddress, byte[] secondaryAddress)807 void addressConsolidateCallback(byte[] mainAddress, byte[] secondaryAddress) { 808 BluetoothDevice device = getDevice(mainAddress); 809 if (device == null) { 810 errorLog("addressConsolidateCallback: device is NULL, address=" 811 + Utils.getAddressStringFromByte(mainAddress) + ", secondaryAddress=" 812 + Utils.getAddressStringFromByte(secondaryAddress)); 813 return; 814 } 815 Log.d(TAG, "addressConsolidateCallback device: " + device + ", secondaryAddress:" 816 + Utils.getAddressStringFromByte(secondaryAddress)); 817 818 DeviceProperties deviceProperties = getDeviceProperties(device); 819 deviceProperties.mIsConsolidated = true; 820 deviceProperties.mDeviceType = BluetoothDevice.DEVICE_TYPE_DUAL; 821 deviceProperties.mIdentityAddress = Utils.getAddressStringFromByte(secondaryAddress); 822 mDualDevicesMap.put(deviceProperties.getIdentityAddress(), Utils.getAddressStringFromByte(mainAddress)); 823 } 824 825 /** 826 * Callback to associate an LE-only device's RPA with its identity address 827 * 828 * @param mainAddress the device's RPA 829 * @param secondaryAddress the device's identity address 830 */ leAddressAssociateCallback(byte[] mainAddress, byte[] secondaryAddress)831 void leAddressAssociateCallback(byte[] mainAddress, byte[] secondaryAddress) { 832 BluetoothDevice device = getDevice(mainAddress); 833 if (device == null) { 834 errorLog("leAddressAssociateCallback: device is NULL, address=" 835 + Utils.getAddressStringFromByte(mainAddress) + ", secondaryAddress=" 836 + Utils.getAddressStringFromByte(secondaryAddress)); 837 return; 838 } 839 Log.d(TAG, "leAddressAssociateCallback device: " + device + ", secondaryAddress:" 840 + Utils.getAddressStringFromByte(secondaryAddress)); 841 842 DeviceProperties deviceProperties = getDeviceProperties(device); 843 deviceProperties.mIdentityAddress = Utils.getAddressStringFromByte(secondaryAddress); 844 } 845 aclStateChangeCallback(int status, byte[] address, int newState, int transportLinkType, int hciReason)846 void aclStateChangeCallback(int status, byte[] address, int newState, 847 int transportLinkType, int hciReason) { 848 BluetoothDevice device = getDevice(address); 849 850 if (device == null) { 851 errorLog("aclStateChangeCallback: device is NULL, address=" 852 + Utils.getAddressStringFromByte(address) + ", newState=" + newState); 853 return; 854 } 855 int state = sAdapterService.getState(); 856 857 Intent intent = null; 858 if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) { 859 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_ON) { 860 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); 861 intent.putExtra(BluetoothDevice.EXTRA_TRANSPORT, transportLinkType); 862 } else if (state == BluetoothAdapter.STATE_BLE_ON 863 || state == BluetoothAdapter.STATE_BLE_TURNING_ON) { 864 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_CONNECTED); 865 } 866 BatteryService batteryService = BatteryService.getBatteryService(); 867 if (batteryService != null) { 868 batteryService.connect(device); 869 } 870 SecurityLog.writeEvent(SecurityLog.TAG_BLUETOOTH_CONNECTION, 871 Utils.getLoggableAddress(device), /* success */ 1, /* reason */ ""); 872 debugLog( 873 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 874 + " Connected: " + device); 875 } else { 876 if (device.getBondState() == BluetoothDevice.BOND_BONDING) { 877 // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding. 878 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); 879 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 880 intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package)); 881 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 882 Utils.getTempAllowlistBroadcastOptions()); 883 } 884 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) { 885 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 886 intent.putExtra(BluetoothDevice.EXTRA_TRANSPORT, transportLinkType); 887 } else if (state == BluetoothAdapter.STATE_BLE_ON 888 || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { 889 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED); 890 } 891 // Reset battery level on complete disconnection 892 if (sAdapterService.getConnectionState(device) == 0) { 893 BatteryService batteryService = BatteryService.getBatteryService(); 894 if (batteryService != null) { 895 batteryService.disconnect(device); 896 } 897 resetBatteryLevel(device); 898 } 899 if (!sAdapterService.isAnyProfileEnabled(device)) { 900 DeviceProperties deviceProp = getDeviceProperties(device); 901 if (deviceProp != null) { 902 deviceProp.setBondingInitiatedLocally(false); 903 } 904 } 905 SecurityLog.writeEvent(SecurityLog.TAG_BLUETOOTH_DISCONNECTION, 906 Utils.getLoggableAddress(device), 907 BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonToString( 908 AdapterService.hciToAndroidDisconnectReason(hciReason))); 909 debugLog( 910 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 911 + " Disconnected: " + device 912 + " transportLinkType: " + transportLinkType 913 + " hciReason: " + hciReason); 914 } 915 916 int connectionState = newState == AbstractionLayer.BT_ACL_STATE_CONNECTED 917 ? BluetoothAdapter.STATE_CONNECTED : BluetoothAdapter.STATE_DISCONNECTED; 918 int metricId = sAdapterService.getMetricId(device); 919 BluetoothStatsLog.write( 920 BluetoothStatsLog.BLUETOOTH_ACL_CONNECTION_STATE_CHANGED, 921 sAdapterService.obfuscateAddress(device), 922 connectionState, 923 metricId, 924 transportLinkType); 925 926 BluetoothClass deviceClass = device.getBluetoothClass(); 927 int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice(); 928 BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED, 929 sAdapterService.obfuscateAddress(device), classOfDevice, metricId); 930 931 if (intent != null) { 932 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device) 933 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) 934 .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 935 sAdapterService.sendBroadcast(intent, BLUETOOTH_CONNECT, 936 Utils.getTempAllowlistBroadcastOptions()); 937 938 synchronized (sAdapterService.getBluetoothConnectionCallbacks()) { 939 Set<IBluetoothConnectionCallback> bluetoothConnectionCallbacks = 940 sAdapterService.getBluetoothConnectionCallbacks(); 941 for (IBluetoothConnectionCallback callback : bluetoothConnectionCallbacks) { 942 try { 943 if (connectionState == BluetoothAdapter.STATE_CONNECTED) { 944 callback.onDeviceConnected(device); 945 } else { 946 callback.onDeviceDisconnected(device, 947 AdapterService.hciToAndroidDisconnectReason(hciReason)); 948 } 949 } catch (RemoteException ex) { 950 Log.e(TAG, "RemoteException in calling IBluetoothConnectionCallback"); 951 } 952 } 953 } 954 } else { 955 Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: " 956 + device.getBondState()); 957 } 958 } 959 960 fetchUuids(BluetoothDevice device, int transport)961 void fetchUuids(BluetoothDevice device, int transport) { 962 if (sSdpTracker.contains(device)) { 963 return; 964 } 965 966 // If no UUIDs are cached and the device is bonding, wait for SDP after the device is bonded 967 DeviceProperties deviceProperties = getDeviceProperties(device); 968 if (deviceProperties != null && deviceProperties.isBonding() 969 && getDeviceProperties(device).getUuids() == null) { 970 return; 971 } 972 973 sSdpTracker.add(device); 974 975 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 976 message.obj = device; 977 mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY); 978 979 // Uses cached UUIDs if we are bonding. If not, we fetch the UUIDs with SDP. 980 if (deviceProperties == null || !deviceProperties.isBonding()) { 981 sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()), 982 transport); 983 } 984 } 985 updateUuids(BluetoothDevice device)986 void updateUuids(BluetoothDevice device) { 987 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 988 message.obj = device; 989 mHandler.sendMessage(message); 990 } 991 992 /** 993 * Handles headset connection state change event 994 * @param intent must be {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent 995 */ 996 @VisibleForTesting onHeadsetConnectionStateChanged(Intent intent)997 void onHeadsetConnectionStateChanged(Intent intent) { 998 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 999 if (device == null) { 1000 Log.e(TAG, "onHeadsetConnectionStateChanged() remote device is null"); 1001 return; 1002 } 1003 if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) 1004 == BluetoothProfile.STATE_DISCONNECTED) { 1005 // TODO: Rework this when non-HFP sources of battery level indication is added 1006 resetBatteryLevel(device); 1007 } 1008 } 1009 1010 @VisibleForTesting onHfIndicatorValueChanged(Intent intent)1011 void onHfIndicatorValueChanged(Intent intent) { 1012 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1013 if (device == null) { 1014 Log.e(TAG, "onHfIndicatorValueChanged() remote device is null"); 1015 return; 1016 } 1017 int indicatorId = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1); 1018 int indicatorValue = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -1); 1019 if (indicatorId == HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS) { 1020 updateBatteryLevel(device, indicatorValue); 1021 } 1022 } 1023 1024 /** 1025 * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 1026 * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 1027 */ 1028 @VisibleForTesting onVendorSpecificHeadsetEvent(Intent intent)1029 void onVendorSpecificHeadsetEvent(Intent intent) { 1030 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1031 if (device == null) { 1032 Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null"); 1033 return; 1034 } 1035 String cmd = 1036 intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); 1037 if (cmd == null) { 1038 Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null"); 1039 return; 1040 } 1041 int cmdType = 1042 intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, 1043 -1); 1044 // Only process set command 1045 if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) { 1046 debugLog("onVendorSpecificHeadsetEvent() only SET command is processed"); 1047 return; 1048 } 1049 Object[] args = (Object[]) intent.getExtras() 1050 .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); 1051 if (args == null) { 1052 Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null"); 1053 return; 1054 } 1055 int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1056 switch (cmd) { 1057 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: 1058 batteryPercent = getBatteryLevelFromXEventVsc(args); 1059 break; 1060 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV: 1061 batteryPercent = getBatteryLevelFromAppleBatteryVsc(args); 1062 break; 1063 } 1064 if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { 1065 updateBatteryLevel(device, batteryPercent); 1066 infoLog("Updated device " + device + " battery level to " + String.valueOf( 1067 batteryPercent) + "%"); 1068 } 1069 } 1070 1071 /** 1072 * Parse 1073 * AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue] 1074 * vendor specific event 1075 * @param args Array of arguments on the right side of assignment 1076 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 1077 * when there is an error parsing the arguments 1078 */ 1079 @VisibleForTesting getBatteryLevelFromAppleBatteryVsc(Object[] args)1080 static int getBatteryLevelFromAppleBatteryVsc(Object[] args) { 1081 if (args.length == 0) { 1082 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments"); 1083 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1084 } 1085 int numKvPair; 1086 if (args[0] instanceof Integer) { 1087 numKvPair = (Integer) args[0]; 1088 } else { 1089 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments"); 1090 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1091 } 1092 if (args.length != (numKvPair * 2 + 1)) { 1093 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match"); 1094 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1095 } 1096 int indicatorType; 1097 int indicatorValue = -1; 1098 for (int i = 0; i < numKvPair; ++i) { 1099 Object indicatorTypeObj = args[2 * i + 1]; 1100 if (indicatorTypeObj instanceof Integer) { 1101 indicatorType = (Integer) indicatorTypeObj; 1102 } else { 1103 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type"); 1104 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1105 } 1106 if (indicatorType 1107 != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) { 1108 continue; 1109 } 1110 Object indicatorValueObj = args[2 * i + 2]; 1111 if (indicatorValueObj instanceof Integer) { 1112 indicatorValue = (Integer) indicatorValueObj; 1113 } else { 1114 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value"); 1115 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1116 } 1117 break; 1118 } 1119 return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN 1120 : (indicatorValue + 1) * 10; 1121 } 1122 1123 /** 1124 * Parse 1125 * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] 1126 * vendor specific event 1127 * @param args Array of arguments on the right side of SET command 1128 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 1129 * when there is an error parsing the arguments 1130 */ 1131 @VisibleForTesting getBatteryLevelFromXEventVsc(Object[] args)1132 static int getBatteryLevelFromXEventVsc(Object[] args) { 1133 if (args.length == 0) { 1134 Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments"); 1135 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1136 } 1137 Object eventNameObj = args[0]; 1138 if (!(eventNameObj instanceof String)) { 1139 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name"); 1140 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1141 } 1142 String eventName = (String) eventNameObj; 1143 if (!eventName.equals( 1144 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) { 1145 infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName); 1146 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1147 } 1148 if (args.length != 5) { 1149 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: " 1150 + String.valueOf(args.length)); 1151 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1152 } 1153 if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) { 1154 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values"); 1155 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1156 } 1157 int batteryLevel = (Integer) args[1]; 1158 int numberOfLevels = (Integer) args[2]; 1159 if (batteryLevel < 0 || numberOfLevels <= 1 || batteryLevel > numberOfLevels) { 1160 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel=" 1161 + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf( 1162 numberOfLevels)); 1163 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 1164 } 1165 return batteryLevel * 100 / (numberOfLevels - 1); 1166 } 1167 1168 /** 1169 * Handles headset client connection state change event 1170 * @param intent must be {@link BluetoothHeadsetClient#ACTION_CONNECTION_STATE_CHANGED} intent 1171 */ 1172 @VisibleForTesting onHeadsetClientConnectionStateChanged(Intent intent)1173 void onHeadsetClientConnectionStateChanged(Intent intent) { 1174 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1175 if (device == null) { 1176 Log.e(TAG, "onHeadsetClientConnectionStateChanged() remote device is null"); 1177 return; 1178 } 1179 if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) 1180 == BluetoothProfile.STATE_DISCONNECTED) { 1181 // TODO: Rework this when non-HFP sources of battery level indication is added 1182 resetBatteryLevel(device); 1183 } 1184 } 1185 1186 @VisibleForTesting onAgIndicatorValueChanged(Intent intent)1187 void onAgIndicatorValueChanged(Intent intent) { 1188 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 1189 if (device == null) { 1190 Log.e(TAG, "onAgIndicatorValueChanged() remote device is null"); 1191 return; 1192 } 1193 1194 if (intent.hasExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL)) { 1195 int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL, -1); 1196 updateBatteryLevel(device, batteryChargeIndicatorToPercentge(batteryLevel)); 1197 } 1198 } 1199 errorLog(String msg)1200 private static void errorLog(String msg) { 1201 Log.e(TAG, msg); 1202 } 1203 debugLog(String msg)1204 private static void debugLog(String msg) { 1205 if (DBG) { 1206 Log.d(TAG, msg); 1207 } 1208 } 1209 infoLog(String msg)1210 private static void infoLog(String msg) { 1211 if (DBG) { 1212 Log.i(TAG, msg); 1213 } 1214 } 1215 warnLog(String msg)1216 private static void warnLog(String msg) { 1217 Log.w(TAG, msg); 1218 } 1219 1220 } 1221