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