Home | History | Annotate | Download | only in btservice
      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          */
     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                             device.mAlias = new String(val);
    518                             debugLog("Remote device alias is: " + device.mAlias);
    519                             break;
    520                         case AbstractionLayer.BT_PROPERTY_BDADDR:
    521                             device.mAddress = val;
    522                             debugLog("Remote Address is:" + Utils.getAddressStringFromByte(val));
    523                             break;
    524                         case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE:
    525                             final int newClass = Utils.byteArrayToInt(val);
    526                             if (newClass == device.mBluetoothClass) {
    527                                 Log.w(TAG, "Skip class update for " + bdDevice);
    528                                 break;
    529                             }
    530                             device.mBluetoothClass = Utils.byteArrayToInt(val);
    531                             intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
    532                             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice);
    533                             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    534                                     new BluetoothClass(device.mBluetoothClass));
    535                             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    536                             sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
    537                             debugLog("Remote class is:" + device.mBluetoothClass);
    538                             break;
    539                         case AbstractionLayer.BT_PROPERTY_UUIDS:
    540                             int numUuids = val.length / AbstractionLayer.BT_UUID_SIZE;
    541                             final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val);
    542                             if (areUuidsEqual(newUuids, device.mUuids)) {
    543                                 Log.w(TAG, "Skip uuids update for " + bdDevice.getAddress());
    544                                 break;
    545                             }
    546                             device.mUuids = newUuids;
    547                             if (sAdapterService.getState() == BluetoothAdapter.STATE_ON) {
    548                                 sendUuidIntent(bdDevice);
    549                             }
    550                             break;
    551                         case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE:
    552                             // The device type from hal layer, defined in bluetooth.h,
    553                             // matches the type defined in BluetoothDevice.java
    554                             device.mDeviceType = Utils.byteArrayToInt(val);
    555                             break;
    556                         case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI:
    557                             // RSSI from hal is in one byte
    558                             device.mRssi = val[0];
    559                             break;
    560                     }
    561                 }
    562             }
    563         }
    564     }
    565 
    566     void deviceFoundCallback(byte[] address) {
    567         // The device properties are already registered - we can send the intent
    568         // now
    569         BluetoothDevice device = getDevice(address);
    570         debugLog("deviceFoundCallback: Remote Address is:" + device);
    571         DeviceProperties deviceProp = getDeviceProperties(device);
    572         if (deviceProp == null) {
    573             errorLog("Device Properties is null for Device:" + device);
    574             return;
    575         }
    576 
    577         Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
    578         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    579         intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    580                 new BluetoothClass(deviceProp.mBluetoothClass));
    581         intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
    582         intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
    583 
    584         sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{
    585                 AdapterService.BLUETOOTH_PERM, android.Manifest.permission.ACCESS_COARSE_LOCATION
    586         });
    587     }
    588 
    589     void aclStateChangeCallback(int status, byte[] address, int newState) {
    590         BluetoothDevice device = getDevice(address);
    591 
    592         if (device == null) {
    593             errorLog("aclStateChangeCallback: device is NULL, address="
    594                     + Utils.getAddressStringFromByte(address) + ", newState=" + newState);
    595             return;
    596         }
    597         int state = sAdapterService.getState();
    598 
    599         Intent intent = null;
    600         if (newState == AbstractionLayer.BT_ACL_STATE_CONNECTED) {
    601             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_ON) {
    602                 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
    603             } else if (state == BluetoothAdapter.STATE_BLE_ON
    604                     || state == BluetoothAdapter.STATE_BLE_TURNING_ON) {
    605                 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_CONNECTED);
    606             }
    607             debugLog(
    608                     "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
    609                             + " Connected: " + device);
    610         } else {
    611             if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
    612                 // Send PAIRING_CANCEL intent to dismiss any dialog requesting bonding.
    613                 intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
    614                 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    615                 intent.setPackage(sAdapterService.getString(R.string.pairing_ui_package));
    616                 sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
    617             }
    618             if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_TURNING_OFF) {
    619                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    620             } else if (state == BluetoothAdapter.STATE_BLE_ON
    621                     || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
    622                 intent = new Intent(BluetoothAdapter.ACTION_BLE_ACL_DISCONNECTED);
    623             }
    624             // Reset battery level on complete disconnection
    625             if (sAdapterService.getConnectionState(device) == 0) {
    626                 resetBatteryLevel(device);
    627             }
    628             debugLog(
    629                     "aclStateChangeCallback: Adapter State: " + BluetoothAdapter.nameForState(state)
    630                             + " Disconnected: " + device);
    631         }
    632 
    633         if (intent != null) {
    634             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    635             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    636                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
    637             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    638             sAdapterService.sendBroadcast(intent, sAdapterService.BLUETOOTH_PERM);
    639         } else {
    640             Log.e(TAG, "aclStateChangeCallback intent is null. deviceBondState: "
    641                     + device.getBondState());
    642         }
    643     }
    644 
    645 
    646     void fetchUuids(BluetoothDevice device) {
    647         if (sSdpTracker.contains(device)) {
    648             return;
    649         }
    650         sSdpTracker.add(device);
    651 
    652         Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
    653         message.obj = device;
    654         mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
    655 
    656         sAdapterService.getRemoteServicesNative(Utils.getBytesFromAddress(device.getAddress()));
    657     }
    658 
    659     void updateUuids(BluetoothDevice device) {
    660         Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
    661         message.obj = device;
    662         mHandler.sendMessage(message);
    663     }
    664 
    665     /**
    666      * Handles headset connection state change event
    667      * @param intent must be {@link BluetoothHeadset#ACTION_CONNECTION_STATE_CHANGED} intent
    668      */
    669     @VisibleForTesting
    670     void onHeadsetConnectionStateChanged(Intent intent) {
    671         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    672         if (device == null) {
    673             Log.e(TAG, "onHeadsetConnectionStateChanged() remote device is null");
    674             return;
    675         }
    676         if (intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED)
    677                 == BluetoothProfile.STATE_DISCONNECTED) {
    678             // TODO: Rework this when non-HFP sources of battery level indication is added
    679             resetBatteryLevel(device);
    680         }
    681     }
    682 
    683     @VisibleForTesting
    684     void onHfIndicatorValueChanged(Intent intent) {
    685         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    686         if (device == null) {
    687             Log.e(TAG, "onHfIndicatorValueChanged() remote device is null");
    688             return;
    689         }
    690         int indicatorId = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_ID, -1);
    691         int indicatorValue = intent.getIntExtra(BluetoothHeadset.EXTRA_HF_INDICATORS_IND_VALUE, -1);
    692         if (indicatorId == HeadsetHalConstants.HF_INDICATOR_BATTERY_LEVEL_STATUS) {
    693             updateBatteryLevel(device, indicatorValue);
    694         }
    695     }
    696 
    697     /**
    698      * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent
    699      * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent
    700      */
    701     @VisibleForTesting
    702     void onVendorSpecificHeadsetEvent(Intent intent) {
    703         BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    704         if (device == null) {
    705             Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null");
    706             return;
    707         }
    708         String cmd =
    709                 intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);
    710         if (cmd == null) {
    711             Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null");
    712             return;
    713         }
    714         int cmdType =
    715                 intent.getIntExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE,
    716                         -1);
    717         // Only process set command
    718         if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) {
    719             debugLog("onVendorSpecificHeadsetEvent() only SET command is processed");
    720             return;
    721         }
    722         Object[] args = (Object[]) intent.getExtras()
    723                 .get(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
    724         if (args == null) {
    725             Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null");
    726             return;
    727         }
    728         int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    729         switch (cmd) {
    730             case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT:
    731                 batteryPercent = getBatteryLevelFromXEventVsc(args);
    732                 break;
    733             case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV:
    734                 batteryPercent = getBatteryLevelFromAppleBatteryVsc(args);
    735                 break;
    736         }
    737         if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
    738             updateBatteryLevel(device, batteryPercent);
    739             infoLog("Updated device " + device + " battery level to " + String.valueOf(
    740                     batteryPercent) + "%");
    741         }
    742     }
    743 
    744     /**
    745      * Parse
    746      *      AT+IPHONEACCEV=[NumberOfIndicators],[IndicatorType],[IndicatorValue]
    747      * vendor specific event
    748      * @param args Array of arguments on the right side of assignment
    749      * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
    750      *         when there is an error parsing the arguments
    751      */
    752     @VisibleForTesting
    753     static int getBatteryLevelFromAppleBatteryVsc(Object[] args) {
    754         if (args.length == 0) {
    755             Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() empty arguments");
    756             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    757         }
    758         int numKvPair;
    759         if (args[0] instanceof Integer) {
    760             numKvPair = (Integer) args[0];
    761         } else {
    762             Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing number of arguments");
    763             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    764         }
    765         if (args.length != (numKvPair * 2 + 1)) {
    766             Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() number of arguments does not match");
    767             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    768         }
    769         int indicatorType;
    770         int indicatorValue = -1;
    771         for (int i = 0; i < numKvPair; ++i) {
    772             Object indicatorTypeObj = args[2 * i + 1];
    773             if (indicatorTypeObj instanceof Integer) {
    774                 indicatorType = (Integer) indicatorTypeObj;
    775             } else {
    776                 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator type");
    777                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    778             }
    779             if (indicatorType
    780                     != BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL) {
    781                 continue;
    782             }
    783             Object indicatorValueObj = args[2 * i + 2];
    784             if (indicatorValueObj instanceof Integer) {
    785                 indicatorValue = (Integer) indicatorValueObj;
    786             } else {
    787                 Log.w(TAG, "getBatteryLevelFromAppleBatteryVsc() error parsing indicator value");
    788                 return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    789             }
    790             break;
    791         }
    792         return (indicatorValue < 0 || indicatorValue > 9) ? BluetoothDevice.BATTERY_LEVEL_UNKNOWN
    793                 : (indicatorValue + 1) * 10;
    794     }
    795 
    796     /**
    797      * Parse
    798      *      AT+XEVENT=BATTERY,[Level],[NumberOfLevel],[MinutesOfTalk],[IsCharging]
    799      * vendor specific event
    800      * @param args Array of arguments on the right side of SET command
    801      * @return Battery level in percents, [0-100], {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
    802      *         when there is an error parsing the arguments
    803      */
    804     @VisibleForTesting
    805     static int getBatteryLevelFromXEventVsc(Object[] args) {
    806         if (args.length == 0) {
    807             Log.w(TAG, "getBatteryLevelFromXEventVsc() empty arguments");
    808             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    809         }
    810         Object eventNameObj = args[0];
    811         if (!(eventNameObj instanceof String)) {
    812             Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event name");
    813             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    814         }
    815         String eventName = (String) eventNameObj;
    816         if (!eventName.equals(
    817                 BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL)) {
    818             infoLog("getBatteryLevelFromXEventVsc() skip none BATTERY event: " + eventName);
    819             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    820         }
    821         if (args.length != 5) {
    822             Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong battery level event length: "
    823                     + String.valueOf(args.length));
    824             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    825         }
    826         if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)) {
    827             Log.w(TAG, "getBatteryLevelFromXEventVsc() error parsing event values");
    828             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    829         }
    830         int batteryLevel = (Integer) args[1];
    831         int numberOfLevels = (Integer) args[2];
    832         if (batteryLevel < 0 || numberOfLevels < 0 || batteryLevel > numberOfLevels) {
    833             Log.w(TAG, "getBatteryLevelFromXEventVsc() wrong event value, batteryLevel="
    834                     + String.valueOf(batteryLevel) + ", numberOfLevels=" + String.valueOf(
    835                     numberOfLevels));
    836             return BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
    837         }
    838         return batteryLevel * 100 / numberOfLevels;
    839     }
    840 
    841     private static void errorLog(String msg) {
    842         Log.e(TAG, msg);
    843     }
    844 
    845     private static void debugLog(String msg) {
    846         if (DBG) {
    847             Log.d(TAG, msg);
    848         }
    849     }
    850 
    851     private static void infoLog(String msg) {
    852         if (DBG) {
    853             Log.i(TAG, msg);
    854         }
    855     }
    856 
    857     private static void warnLog(String msg) {
    858         Log.w(TAG, msg);
    859     }
    860 
    861 }
    862