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