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