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