RemoteDevices.java revision ef1279dc436adaf9681b88acf7cf5d1239ba1574
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 17package com.android.bluetooth.btservice; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.BluetoothAssignedNumbers; 21import android.bluetooth.BluetoothClass; 22import android.bluetooth.BluetoothDevice; 23import android.bluetooth.BluetoothHeadset; 24import android.bluetooth.BluetoothProfile; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.Intent; 28import android.content.IntentFilter; 29import android.os.Handler; 30import android.os.Looper; 31import android.os.Message; 32import android.os.ParcelUuid; 33import android.support.annotation.VisibleForTesting; 34import android.util.Log; 35 36import com.android.bluetooth.R; 37import com.android.bluetooth.Utils; 38import com.android.bluetooth.hfp.HeadsetHalConstants; 39 40import java.util.ArrayList; 41import java.util.HashMap; 42import java.util.HashSet; 43import java.util.LinkedList; 44import java.util.Queue; 45import java.util.Set; 46 47final 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 if (device.mAlias != null) { 518 System.arraycopy(val, 0, device.mAlias, 0, val.length); 519 } else { 520 device.mAlias = new String(val); 521 } 522 break; 523 case AbstractionLayer.BT_PROPERTY_BDADDR: 524 device.mAddress = val; 525 debugLog("Remote Address is:" + Utils.getAddressStringFromByte(val)); 526 break; 527 case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: 528 final int newClass = Utils.byteArrayToInt(val); 529 if (newClass == device.mBluetoothClass) { 530 Log.w(TAG, "Skip class update for " + bdDevice); 531 break; 532 } 533 device.mBluetoothClass = Utils.byteArrayToInt(val); 534 intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); 535 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); 536 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 537 new BluetoothClass(device.mBluetoothClass)); 538 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 539 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 540 debugLog("Remote class is:" + device.mBluetoothClass); 541 break; 542 case AbstractionLayer.BT_PROPERTY_UUIDS: 543 int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE; 544 final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val); 545 if (areUuidsEqual(newUuids, device.mUuids)) { 546 Log.w(TAG, "Skip uuids update for " + bdDevice.getAddress()); 547 break; 548 } 549 device.mUuids = newUuids; 550 if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) { 551 sendUuidIntent(bdDevice); 552 } 553 break; 554 case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: 555 // The device type from hal layer, defined in bluetooth.h, 556 // matches the type defined in BluetoothDevice.java 557 device.mDeviceType = Utils.byteArrayToInt(val); 558 break; 559 case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: 560 // RSSI from hal is in one byte 561 device.mRssi = val[0]; 562 break; 563 } 564 } 565 } 566 } 567 } 568 569 void deviceFoundCallback(byte[] address) { 570 // The device properties are already registered - we can send the intent 571 // now 572 BluetoothDevice device = getDevice(address); 573 debugLog("deviceFoundCallback: Remote Address is:" + device); 574 DeviceProperties deviceProp = getDeviceProperties(device); 575 if (deviceProp == null) { 576 errorLog("Device Properties is null for Device:" + device); 577 return; 578 } 579 580 Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); 581 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 582 intent.putExtra(BluetoothDevice.EXTRA_CLASS, 583 new BluetoothClass(deviceProp.mBluetoothClass)); 584 intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi); 585 intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName); 586 587 sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{ 588 AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION 589 }); 590 } 591 592 void aclStateChangeCallback(int status, byte[] address, int newState) { 593 BluetoothDevice device = getDevice(address); 594 595 if (device == null) { 596 errorLog("aclStateChangeCallback: device is NULL, address=" 597 + Utils.getAddressStringFromByte(address) + ", newState=" + newState); 598 return; 599 } 600 int state = sAdapterService.getState(); 601 602 Intent intent = null; 603 if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) { 604 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_ON) { 605 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); 606 } else if (state == BluetoothAdapter.STATE_BLE_ON 607 || state == BluetoothAdapter.STATE_BLE_TURNING_ON) { 608 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_CONNECTED); 609 } 610 debugLog( 611 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 612 + " Connected: " + device); 613 } else { 614 if (device.getBondState() == BluetoothDevice.BOND_BONDING) { 615 // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding. 616 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); 617 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 618 intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package)); 619 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 620 } 621 if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) { 622 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); 623 } else if (state == BluetoothAdapter.STATE_BLE_ON 624 || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) { 625 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED); 626 } 627 // Reset battery level on complete disconnection 628 if (sAdapterService.getConnectionState(device) == 0) { 629 resetBatteryLevel(device); 630 } 631 debugLog( 632 "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state) 633 + " Disconnected: " + device); 634 } 635 636 if (intent != null) { 637 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 638 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 639 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 640 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 641 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM); 642 } else { 643 Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: " 644 + device.getBondState()); 645 } 646 } 647 648 649 void fetchUuids(BluetoothDevice device) { 650 if (sSdpTracker.contains(device)) { 651 return; 652 } 653 sSdpTracker.add(device); 654 655 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 656 message.obj = device; 657 mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY); 658 659 sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress())); 660 } 661 662 void updateUuids(BluetoothDevice device) { 663 Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT); 664 message.obj = device; 665 mHandler.sendMessage(message); 666 } 667 668 /** 669 * Handles headset connection state change event 670 * @param intent must be {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent 671 */ 672 @VisibleForTesting 673 void onHeadsetConnectionStateChanged(Intent intent) { 674 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 675 if (device == null) { 676 Log.e(TAG, "onHeadsetConnectionStateChanged() remote device is null"); 677 return; 678 } 679 if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED) 680 == BluetoothProfile.STATE_DISCONNECTED) { 681 // TODO: Rework this when non-HFP sources of battery level indication is added 682 resetBatteryLevel(device); 683 } 684 } 685 686 @VisibleForTesting 687 void onHfIndicatorValueChanged(Intent intent) { 688 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 689 if (device == null) { 690 Log.e(TAG, "onHfIndicatorValueChanged() remote device is null"); 691 return; 692 } 693 int indicatorId = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1); 694 int indicatorValue = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -1); 695 if (indicatorId == HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS) { 696 updateBatteryLevel(device, indicatorValue); 697 } 698 } 699 700 /** 701 * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 702 * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent 703 */ 704 @VisibleForTesting 705 void onVendorSpecificHeadsetEvent(Intent intent) { 706 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 707 if (device == null) { 708 Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null"); 709 return; 710 } 711 String cmd = 712 intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD); 713 if (cmd == null) { 714 Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null"); 715 return; 716 } 717 int cmdType = 718 intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, 719 -1); 720 // Only process set command 721 if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) { 722 debugLog("onVendorSpecificHeadsetEvent() only SET command is processed"); 723 return; 724 } 725 Object[] args = (Object[]) intent.getExtras() 726 .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS); 727 if (args == null) { 728 Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null"); 729 return; 730 } 731 int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 732 switch (cmd) { 733 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT: 734 batteryPercent = getBatteryLevelFromXEventVsc(args); 735 break; 736 case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV: 737 batteryPercent = getBatteryLevelFromAppleBatteryVsc(args); 738 break; 739 } 740 if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { 741 updateBatteryLevel(device, batteryPercent); 742 infoLog("Updated device " + device + " battery level to " + String.valueOf( 743 batteryPercent) + "%"); 744 } 745 } 746 747 /** 748 * Parse 749 * AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue] 750 * vendor specific event 751 * @param args Array of arguments on the right side of assignment 752 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 753 * when there is an error parsing the arguments 754 */ 755 @VisibleForTesting 756 static int getBatteryLevelFromAppleBatteryVsc(Object[] args) { 757 if (args.length == 0) { 758 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments"); 759 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 760 } 761 int numKvPair; 762 if (args[0] instanceof Integer) { 763 numKvPair = (Integer) args[0]; 764 } else { 765 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments"); 766 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 767 } 768 if (args.length != (numKvPair * 2 + 1)) { 769 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match"); 770 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 771 } 772 int indicatorType; 773 int indicatorValue = -1; 774 for (int i = 0; i < numKvPair; ++i) { 775 Object indicatorTypeObj = args[2 * i + 1]; 776 if (indicatorTypeObj instanceof Integer) { 777 indicatorType = (Integer) indicatorTypeObj; 778 } else { 779 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type"); 780 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 781 } 782 if (indicatorType 783 != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) { 784 continue; 785 } 786 Object indicatorValueObj = args[2 * i + 2]; 787 if (indicatorValueObj instanceof Integer) { 788 indicatorValue = (Integer) indicatorValueObj; 789 } else { 790 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value"); 791 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 792 } 793 break; 794 } 795 return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN 796 : (indicatorValue + 1) * 10; 797 } 798 799 /** 800 * Parse 801 * AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging] 802 * vendor specific event 803 * @param args Array of arguments on the right side of SET command 804 * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN} 805 * when there is an error parsing the arguments 806 */ 807 @VisibleForTesting 808 static int getBatteryLevelFromXEventVsc(Object[] args) { 809 if (args.length == 0) { 810 Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments"); 811 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 812 } 813 Object eventNameObj = args[0]; 814 if (!(eventNameObj instanceof String)) { 815 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name"); 816 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 817 } 818 String eventName = (String) eventNameObj; 819 if (!eventName.equals( 820 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) { 821 infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName); 822 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 823 } 824 if (args.length != 5) { 825 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: " 826 + String.valueOf(args.length)); 827 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 828 } 829 if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) { 830 Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values"); 831 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 832 } 833 int batteryLevel = (Integer) args[1]; 834 int numberOfLevels = (Integer) args[2]; 835 if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) { 836 Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel=" 837 + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf( 838 numberOfLevels)); 839 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN; 840 } 841 return batteryLevel * 100 / numberOfLevels; 842 } 843 844 private static void errorLog(String msg) { 845 Log.e(TAG, msg); 846 } 847 848 private static void debugLog(String msg) { 849 if (DBG) { 850 Log.d(TAG, msg); 851 } 852 } 853 854 private static void infoLog(String msg) { 855 if (DBG) { 856 Log.i(TAG, msg); 857 } 858 } 859 860 private static void warnLog(String msg) { 861 Log.w(TAG, msg); 862 } 863 864} 865