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