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