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