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