Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth;
     18 
     19 import android.app.Service;
     20 import android.bluetooth.BluetoothA2dp;
     21 import android.bluetooth.BluetoothA2dpSink;
     22 import android.bluetooth.BluetoothAdapter;
     23 import android.bluetooth.BluetoothDevice;
     24 import android.bluetooth.BluetoothHeadset;
     25 import android.bluetooth.BluetoothHeadsetClient;
     26 import android.bluetooth.BluetoothHidDevice;
     27 import android.bluetooth.BluetoothHidHost;
     28 import android.bluetooth.BluetoothManager;
     29 import android.bluetooth.BluetoothMap;
     30 import android.bluetooth.BluetoothMapClient;
     31 import android.bluetooth.BluetoothPan;
     32 import android.bluetooth.BluetoothPbapClient;
     33 import android.bluetooth.BluetoothProfile;
     34 import android.bluetooth.BluetoothUuid;
     35 import android.content.BroadcastReceiver;
     36 import android.content.Context;
     37 import android.content.Intent;
     38 import android.content.IntentFilter;
     39 import android.os.Bundle;
     40 import android.os.ParcelUuid;
     41 
     42 import com.googlecode.android_scripting.Log;
     43 import com.googlecode.android_scripting.facade.EventFacade;
     44 import com.googlecode.android_scripting.facade.FacadeManager;
     45 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
     46 import com.googlecode.android_scripting.rpc.Rpc;
     47 import com.googlecode.android_scripting.rpc.RpcDefault;
     48 import com.googlecode.android_scripting.rpc.RpcOptional;
     49 import com.googlecode.android_scripting.rpc.RpcParameter;
     50 
     51 import org.json.JSONArray;
     52 import org.json.JSONException;
     53 
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.Collections;
     57 import java.util.HashMap;
     58 import java.util.List;
     59 import java.util.Map;
     60 
     61 public class BluetoothConnectionFacade extends RpcReceiver {
     62 
     63     private final Service mService;
     64     private final Context mContext;
     65     private final BluetoothAdapter mBluetoothAdapter;
     66     private final BluetoothManager mBluetoothManager;
     67     private final BluetoothPairingHelper mPairingHelper;
     68     private final Map<String, BroadcastReceiver> listeningDevices;
     69     private final EventFacade mEventFacade;
     70 
     71     private final IntentFilter mDiscoverConnectFilter;
     72     private final IntentFilter mPairingFilter;
     73     private final IntentFilter mBondFilter;
     74     private final IntentFilter mA2dpStateChangeFilter;
     75     private final IntentFilter mA2dpSinkStateChangeFilter;
     76     private final IntentFilter mHidStateChangeFilter;
     77     private final IntentFilter mHidDeviceStateChangeFilter;
     78     private final IntentFilter mHspStateChangeFilter;
     79     private final IntentFilter mHfpClientStateChangeFilter;
     80     private final IntentFilter mPbapClientStateChangeFilter;
     81     private final IntentFilter mPanStateChangeFilter;
     82     private final IntentFilter mMapClientStateChangeFilter;
     83     private final IntentFilter mMapStateChangeFilter;
     84 
     85     private final Bundle mGoodNews;
     86     private final Bundle mBadNews;
     87 
     88     private BluetoothA2dpFacade mA2dpProfile;
     89     private BluetoothA2dpSinkFacade mA2dpSinkProfile;
     90     private BluetoothHidFacade mHidProfile;
     91     private BluetoothHidDeviceFacade mHidDeviceProfile;
     92     private BluetoothHspFacade mHspProfile;
     93     private BluetoothHfpClientFacade mHfpClientProfile;
     94     private BluetoothPbapClientFacade mPbapClientProfile;
     95     private BluetoothPanFacade mPanProfile;
     96     private BluetoothMapClientFacade mMapClientProfile;
     97     private BluetoothMapFacade mMapProfile;
     98     private ArrayList<String> mDeviceMonitorList;
     99 
    100     public BluetoothConnectionFacade(FacadeManager manager) {
    101         super(manager);
    102         mService = manager.getService();
    103         mContext = mService.getApplicationContext();
    104         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    105         mBluetoothManager = (BluetoothManager) mContext.getSystemService(
    106                 Service.BLUETOOTH_SERVICE);
    107         mDeviceMonitorList = new ArrayList<String>();
    108         // Use a synchronized map to avoid racing problems
    109         listeningDevices = Collections.synchronizedMap(new HashMap<String, BroadcastReceiver>());
    110 
    111         mEventFacade = manager.getReceiver(EventFacade.class);
    112         mPairingHelper = new BluetoothPairingHelper(mEventFacade);
    113         mA2dpProfile = manager.getReceiver(BluetoothA2dpFacade.class);
    114         mA2dpSinkProfile = manager.getReceiver(BluetoothA2dpSinkFacade.class);
    115         mHidProfile = manager.getReceiver(BluetoothHidFacade.class);
    116         mHidDeviceProfile = manager.getReceiver(BluetoothHidDeviceFacade.class);
    117         mHspProfile = manager.getReceiver(BluetoothHspFacade.class);
    118         mHfpClientProfile = manager.getReceiver(BluetoothHfpClientFacade.class);
    119         mPbapClientProfile = manager.getReceiver(BluetoothPbapClientFacade.class);
    120         mPanProfile = manager.getReceiver(BluetoothPanFacade.class);
    121         mMapClientProfile = manager.getReceiver(BluetoothMapClientFacade.class);
    122         mMapProfile = manager.getReceiver(BluetoothMapFacade.class);
    123 
    124         mDiscoverConnectFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    125         mDiscoverConnectFilter.addAction(BluetoothDevice.ACTION_UUID);
    126         mDiscoverConnectFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    127 
    128         mPairingFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST);
    129         mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
    130         mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
    131         mPairingFilter.setPriority(999);
    132 
    133         mBondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    134         mBondFilter.addAction(BluetoothDevice.ACTION_FOUND);
    135         mBondFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    136 
    137         mA2dpStateChangeFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
    138         mA2dpSinkStateChangeFilter =
    139             new IntentFilter(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
    140         mHidStateChangeFilter =
    141             new IntentFilter(BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED);
    142         mHidDeviceStateChangeFilter =
    143                 new IntentFilter(BluetoothHidDevice.ACTION_CONNECTION_STATE_CHANGED);
    144         mHspStateChangeFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
    145         mHfpClientStateChangeFilter =
    146             new IntentFilter(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
    147         mPbapClientStateChangeFilter =
    148             new IntentFilter(BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
    149         mPanStateChangeFilter =
    150             new IntentFilter(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
    151         mMapClientStateChangeFilter =
    152             new IntentFilter(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
    153         mMapStateChangeFilter =
    154             new IntentFilter(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
    155 
    156         mGoodNews = new Bundle();
    157         mGoodNews.putBoolean("Status", true);
    158         mBadNews = new Bundle();
    159         mBadNews.putBoolean("Status", false);
    160     }
    161 
    162     private void unregisterCachedListener(String listenerId) {
    163         BroadcastReceiver listener = listeningDevices.remove(listenerId);
    164         if (listener != null) {
    165             mService.unregisterReceiver(listener);
    166         }
    167     }
    168 
    169     /**
    170      * Connect to a specific device upon its discovery
    171      */
    172     public class DiscoverConnectReceiver extends BroadcastReceiver {
    173         private final String mDeviceID;
    174         private BluetoothDevice mDevice;
    175 
    176         /**
    177          * Constructor
    178          *
    179          * @param deviceID Either the device alias name or mac address.
    180          * @param bond     If true, bond the device only.
    181          */
    182         public DiscoverConnectReceiver(String deviceID) {
    183             super();
    184             mDeviceID = deviceID;
    185         }
    186 
    187         @Override
    188         public void onReceive(Context context, Intent intent) {
    189             String action = intent.getAction();
    190             // The specified device is found.
    191             if (action.equals(BluetoothDevice.ACTION_FOUND)) {
    192                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    193                 if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
    194                     Log.d("Found device " + device.getAliasName() + " for connection.");
    195                     mBluetoothAdapter.cancelDiscovery();
    196                     mDevice = device;
    197                 }
    198                 // After discovery stops.
    199             } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
    200                 if (mDevice == null) {
    201                     Log.d("Device " + mDeviceID + " not discovered.");
    202                     mEventFacade.postEvent("Bond" + mDeviceID, mBadNews);
    203                     return;
    204                 }
    205                 boolean status = mDevice.fetchUuidsWithSdp();
    206                 Log.d("Initiated ACL connection: " + status);
    207             } else if (action.equals(BluetoothDevice.ACTION_UUID)) {
    208                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    209                 if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
    210                     Log.d("Initiating connections.");
    211                     connectProfile(device, mDeviceID);
    212                     mService.unregisterReceiver(listeningDevices.remove("Connect" + mDeviceID));
    213                 }
    214             }
    215         }
    216     }
    217 
    218     /**
    219      * Connect to a specific device upon its discovery
    220      */
    221     public class DiscoverBondReceiver extends BroadcastReceiver {
    222         private final String mDeviceID;
    223         private BluetoothDevice mDevice = null;
    224         private boolean started = false;
    225 
    226         /**
    227          * Constructor
    228          *
    229          * @param deviceID Either the device alias name or Mac address.
    230          */
    231         public DiscoverBondReceiver(String deviceID) {
    232             super();
    233             mDeviceID = deviceID;
    234         }
    235 
    236         @Override
    237         public void onReceive(Context context, Intent intent) {
    238             String action = intent.getAction();
    239             // The specified device is found.
    240             if (action.equals(BluetoothDevice.ACTION_FOUND)) {
    241                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    242                 if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
    243                     Log.d("Found device " + device.getAliasName() + " for connection.");
    244                     mBluetoothAdapter.cancelDiscovery();
    245                     mDevice = device;
    246                 }
    247                 // After discovery stops.
    248             } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
    249                 if (mDevice == null) {
    250                     Log.d("Device " + mDeviceID + " was not discovered.");
    251                     mEventFacade.postEvent("Bond", mBadNews);
    252                     return;
    253                 }
    254                 // Attempt to initiate bonding.
    255                 if (!started) {
    256                     Log.d("Bond with " + mDevice.getAliasName());
    257                     if (mDevice.createBond()) {
    258                         started = true;
    259                         Log.d("Bonding started.");
    260                     } else {
    261                         Log.e("Failed to bond with " + mDevice.getAliasName());
    262                         mEventFacade.postEvent("Bond", mBadNews);
    263                         mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID));
    264                     }
    265                 }
    266             } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
    267                 Log.d("Bond state changing.");
    268                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    269                 if (BluetoothFacade.deviceMatch(device, mDeviceID)) {
    270                     int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
    271                     Log.d("New state is " + state);
    272                     if (state == BluetoothDevice.BOND_BONDED) {
    273                         Log.d("Bonding with " + mDeviceID + " successful.");
    274                         mEventFacade.postEvent("Bond" + mDeviceID, mGoodNews);
    275                         mService.unregisterReceiver(listeningDevices.remove("Bond" + mDeviceID));
    276                     }
    277                 }
    278             }
    279         }
    280     }
    281 
    282     public class ConnectStateChangeReceiver extends BroadcastReceiver {
    283         private final String mDeviceID;
    284 
    285         public ConnectStateChangeReceiver(String deviceID) {
    286             mDeviceID = deviceID;
    287         }
    288 
    289         @Override
    290         public void onReceive(Context context, Intent intent) {
    291             // no matter what the action, just push it...
    292             String action = intent.getAction();
    293             Log.d("Action received: " + action);
    294 
    295             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    296             // Check if received the specified device
    297             if (!BluetoothFacade.deviceMatch(device, mDeviceID)) {
    298                 Log.e("Action devices does match act: " + device + " exp " + mDeviceID);
    299                 return;
    300             }
    301             // Find the state.
    302             int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
    303             if (state == -1) {
    304                 Log.e("Action does not have a state.");
    305                 return;
    306             }
    307 
    308             // Switch Only Necessary for Old implementation. Left in for backwards compatability.
    309             int profile = -1;
    310             switch (action) {
    311                 case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
    312                     profile = BluetoothProfile.A2DP;
    313                     break;
    314                 case BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED:
    315                     profile = BluetoothProfile.HID_HOST;
    316                     break;
    317                 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
    318                     profile = BluetoothProfile.HEADSET;
    319                     break;
    320                 case BluetoothPan.ACTION_CONNECTION_STATE_CHANGED:
    321                     profile = BluetoothProfile.PAN;
    322                     break;
    323                 case BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED:
    324                     profile = BluetoothProfile.HEADSET_CLIENT;
    325                     break;
    326                 case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
    327                     profile = BluetoothProfile.A2DP_SINK;
    328                     break;
    329                 case BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED:
    330                     profile = BluetoothProfile.PBAP_CLIENT;
    331                     break;
    332                 case BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED:
    333                     profile = BluetoothProfile.MAP_CLIENT;
    334                     break;
    335             }
    336 
    337             if (profile == -1) {
    338                 Log.e("Action does not match any given profiles " + action);
    339             }
    340 
    341             // The newer implementation will just post the Bundle with the literal event
    342             // intead of the old implemenatation of posting BluetoothProfileConnectionStateChanged
    343             // with the action inside of the Bundle. This makes for cleaner connection handling
    344             // from test frameworks. Left the old implemenation in for backwards compatability.
    345 
    346             // Post an event to Facade.
    347             Bundle news = new Bundle();
    348             news.putInt("state", state);
    349             news.putString("addr", device.getAddress());
    350             mEventFacade.postEvent(action, news);
    351 
    352             news.putInt("profile", profile);
    353             news.putString("action", action);
    354             mEventFacade.postEvent("BluetoothProfileConnectionStateChanged", news);
    355         }
    356     }
    357 
    358     /**
    359      * Converts a given JSONArray to an ArrayList of Integers
    360      *
    361      * @param jsonArray the JSONArray to be converted
    362      * @return <code>List<Integer></></code> the converted list of Integers
    363      */
    364     private List<Integer> jsonArrayToIntegerList(JSONArray jsonArray) throws JSONException {
    365         if (jsonArray == null) {
    366             return null;
    367         }
    368         List<Integer> intArray = new ArrayList<Integer>();
    369         for (int i = 0; i < jsonArray.length(); i++) {
    370             intArray.add(jsonArray.getInt(i));
    371         }
    372         return intArray;
    373 
    374     }
    375 
    376     @Rpc(description = "Start monitoring state changes for input device.")
    377     public void bluetoothStartConnectionStateChangeMonitor(
    378         @RpcParameter(name = "deviceID",
    379                     description = "Name or MAC address of a bluetooth device.")
    380                     String deviceID) {
    381         if (!mDeviceMonitorList.contains(deviceID)) {
    382             ConnectStateChangeReceiver receiver = new ConnectStateChangeReceiver(deviceID);
    383             mService.registerReceiver(receiver, mA2dpStateChangeFilter);
    384             mService.registerReceiver(receiver, mA2dpSinkStateChangeFilter);
    385             mService.registerReceiver(receiver, mHidStateChangeFilter);
    386             mService.registerReceiver(receiver, mHspStateChangeFilter);
    387             mService.registerReceiver(receiver, mHfpClientStateChangeFilter);
    388             mService.registerReceiver(receiver, mPbapClientStateChangeFilter);
    389             mService.registerReceiver(receiver, mPanStateChangeFilter);
    390             mService.registerReceiver(receiver, mMapClientStateChangeFilter);
    391             mService.registerReceiver(receiver, mMapStateChangeFilter);
    392             listeningDevices.put("StateChangeListener:" + deviceID, receiver);
    393         }
    394     }
    395 
    396     /**
    397      * Connect on all the profiles to the given Bluetooth device
    398      *
    399      * @param device   The <code>BluetoothDevice</code> to connect to
    400      * @param deviceID Name (String) of the device to connect to
    401      */
    402     private void connectProfile(BluetoothDevice device, String deviceID) {
    403         mService.registerReceiver(mPairingHelper, mPairingFilter);
    404         ParcelUuid[] deviceUuids = device.getUuids();
    405         Log.d("Device uuid is " + Arrays.toString(deviceUuids));
    406         if (deviceUuids == null) {
    407             mEventFacade.postEvent("BluetoothProfileConnectionEvent", mBadNews);
    408         }
    409         Log.d("Connecting to " + device.getAliasName());
    410         if (BluetoothUuid.containsAnyUuid(BluetoothA2dpFacade.SINK_UUIDS, deviceUuids)) {
    411             mA2dpProfile.a2dpConnect(device);
    412         }
    413         if (BluetoothUuid.containsAnyUuid(BluetoothA2dpSinkFacade.SOURCE_UUIDS, deviceUuids)) {
    414             mA2dpSinkProfile.a2dpSinkConnect(device);
    415         }
    416         if (BluetoothUuid.containsAnyUuid(BluetoothHidFacade.UUIDS, deviceUuids)) {
    417             mHidProfile.hidConnect(device);
    418         }
    419         if (BluetoothUuid.containsAnyUuid(BluetoothHspFacade.UUIDS, deviceUuids)) {
    420             mHspProfile.hspConnect(device);
    421         }
    422         if (BluetoothUuid.containsAnyUuid(BluetoothHfpClientFacade.UUIDS, deviceUuids)) {
    423             mHfpClientProfile.hfpClientConnect(device);
    424         }
    425         if (BluetoothUuid.containsAnyUuid(BluetoothMapClientFacade.MAP_UUIDS, deviceUuids)) {
    426             mMapClientProfile.mapClientConnect(device);
    427         }
    428         if (BluetoothUuid.containsAnyUuid(BluetoothPanFacade.UUIDS, deviceUuids)) {
    429             mPanProfile.panConnect(device);
    430         }
    431         if (BluetoothUuid.containsAnyUuid(BluetoothPbapClientFacade.UUIDS, deviceUuids)) {
    432             mPbapClientProfile.pbapClientConnect(device);
    433         }
    434         mService.unregisterReceiver(mPairingHelper);
    435     }
    436 
    437     /**
    438      * Disconnect on all available profiles from the given device
    439      *
    440      * @param device   The <code>BluetoothDevice</code> to disconnect from
    441      * @param deviceID Name (String) of the device to disconnect from
    442      */
    443     private void disconnectProfiles(BluetoothDevice device, String deviceID) {
    444         Log.d("Disconnecting device " + device);
    445         // Blindly disconnect all profiles. We may not have some of them connected so that will be a
    446         // null op.
    447         mA2dpProfile.a2dpDisconnect(device);
    448         mA2dpSinkProfile.a2dpSinkDisconnect(device);
    449         mHidProfile.hidDisconnect(device);
    450         mHidDeviceProfile.hidDeviceDisconnect(device);
    451         mHspProfile.hspDisconnect(device);
    452         mHfpClientProfile.hfpClientDisconnect(device);
    453         mPbapClientProfile.pbapClientDisconnect(device);
    454         mPanProfile.panDisconnect(device);
    455         mMapClientProfile.mapClientDisconnect(device);
    456     }
    457 
    458     /**
    459      * Disconnect from specific profiles provided in the given List of profiles.
    460      *
    461      * @param device     The {@link BluetoothDevice} to disconnect from
    462      * @param deviceID   Name/BDADDR (String) of the device to disconnect from
    463      * @param profileIds The list of profiles we want to disconnect on.
    464      */
    465     private void disconnectProfiles(BluetoothDevice device, String deviceID,
    466             List<Integer> profileIds) {
    467         boolean result;
    468         for (int profileId : profileIds) {
    469             switch (profileId) {
    470                 case BluetoothProfile.A2DP_SINK:
    471                     mA2dpSinkProfile.a2dpSinkDisconnect(device);
    472                     break;
    473                 case BluetoothProfile.A2DP:
    474                     mA2dpProfile.a2dpDisconnect(device);
    475                     break;
    476                 case BluetoothProfile.HID_HOST:
    477                     mHidProfile.hidDisconnect(device);
    478                     break;
    479                 case BluetoothProfile.HID_DEVICE:
    480                     mHidDeviceProfile.hidDeviceDisconnect(device);
    481                     break;
    482                 case BluetoothProfile.HEADSET:
    483                     mHspProfile.hspDisconnect(device);
    484                     break;
    485                 case BluetoothProfile.HEADSET_CLIENT:
    486                     mHfpClientProfile.hfpClientDisconnect(device);
    487                     break;
    488                 case BluetoothProfile.PAN:
    489                     mPanProfile.panDisconnect(device);
    490                     break;
    491                 case BluetoothProfile.PBAP_CLIENT:
    492                     mPbapClientProfile.pbapClientDisconnect(device);
    493                     break;
    494                 case BluetoothProfile.MAP_CLIENT:
    495                     mMapClientProfile.mapDisconnect(device);
    496                     break;
    497                 default:
    498                     Log.d("Unknown Profile Id to disconnect from. Quitting");
    499                     return; // returns on the first unknown profile  it encounters.
    500             }
    501         }
    502     }
    503 
    504     @Rpc(description = "Start intercepting all bluetooth connection pop-ups.")
    505     public void bluetoothStartPairingHelper(
    506         @RpcParameter(name = "autoConfirm",
    507                     description = "Whether connection should be auto confirmed")
    508         @RpcDefault("true") @RpcOptional
    509         Boolean autoConfirm) {
    510         Log.d("Staring pairing helper");
    511         mPairingHelper.setAutoConfirm(autoConfirm);
    512         mService.registerReceiver(mPairingHelper, mPairingFilter);
    513     }
    514 
    515     @Rpc(description = "Return a list of devices connected through bluetooth")
    516     public List<BluetoothDevice> bluetoothGetConnectedDevices() {
    517         ArrayList<BluetoothDevice> results = new ArrayList<BluetoothDevice>();
    518         for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) {
    519             if (bd.isConnected()) {
    520                 results.add(bd);
    521             }
    522         }
    523         return results;
    524     }
    525 
    526     /**
    527      * Return a list of service UUIDS supported by the bonded device.
    528      * @param macAddress the String mac address of the bonded device.
    529      *
    530      * @return the String list of supported UUIDS.
    531      * @throws Exception
    532      */
    533     @Rpc(description = "Return a list of service UUIDS supported by the bonded device")
    534     public List<String> bluetoothGetBondedDeviceUuids(
    535         @RpcParameter(name = "macAddress") String macAddress) throws Exception {
    536         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    537                 macAddress);
    538         ArrayList<String> uuidStrings = new ArrayList<>();
    539         for (ParcelUuid parcelUuid : mDevice.getUuids()) {
    540             uuidStrings.add(parcelUuid.toString());
    541         }
    542         return uuidStrings;
    543     }
    544 
    545     @Rpc(description = "Return a list of devices connected through bluetooth LE")
    546     public List<BluetoothDevice> bluetoothGetConnectedLeDevices(Integer profile) {
    547         return mBluetoothManager.getConnectedDevices(profile);
    548     }
    549 
    550     @Rpc(description = "Bluetooth init Bond by Mac Address")
    551     public boolean bluetoothBond(@RpcParameter(name = "macAddress") String macAddress) {
    552         return mBluetoothAdapter.getRemoteDevice(macAddress).createBond();
    553     }
    554 
    555     @Rpc(description = "Return true if a bluetooth device is connected.")
    556     public Boolean bluetoothIsDeviceConnected(String deviceID) {
    557         for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) {
    558             if (BluetoothFacade.deviceMatch(bd, deviceID)) {
    559                 return bd.isConnected();
    560             }
    561         }
    562         return false;
    563     }
    564 
    565     @Rpc(description = "Return list of connected bluetooth devices over a profile",
    566             returns = "List of devices connected over the profile")
    567     public List<BluetoothDevice> bluetoothGetConnectedDevicesOnProfile(
    568             @RpcParameter(name = "profileId",
    569                     description = "profileId same as BluetoothProfile")
    570                     Integer profileId) {
    571         BluetoothProfile profile = null;
    572         switch (profileId) {
    573             case BluetoothProfile.A2DP_SINK:
    574                 return mA2dpSinkProfile.bluetoothA2dpSinkGetConnectedDevices();
    575             case BluetoothProfile.HEADSET_CLIENT:
    576                 return mHfpClientProfile.bluetoothHfpClientGetConnectedDevices();
    577             case BluetoothProfile.PBAP_CLIENT:
    578                 return mPbapClientProfile.bluetoothPbapClientGetConnectedDevices();
    579             case BluetoothProfile.MAP_CLIENT:
    580                 return mMapClientProfile.bluetoothMapClientGetConnectedDevices();
    581             default:
    582                 Log.w("Profile id " + profileId + " is not yet supported.");
    583                 return new ArrayList<BluetoothDevice>();
    584         }
    585     }
    586 
    587     @Rpc(description = "Connect to a specified device once it's discovered.",
    588             returns = "Whether discovery started successfully.")
    589     public Boolean bluetoothDiscoverAndConnect(
    590             @RpcParameter(name = "deviceID",
    591                     description = "Name or MAC address of a bluetooth device.")
    592                     String deviceID) {
    593         mBluetoothAdapter.cancelDiscovery();
    594         if (listeningDevices.containsKey(deviceID)) {
    595             Log.d("This device is already in the process of discovery and connecting.");
    596             return true;
    597         }
    598         DiscoverConnectReceiver receiver = new DiscoverConnectReceiver(deviceID);
    599         listeningDevices.put("Connect" + deviceID, receiver);
    600         mService.registerReceiver(receiver, mDiscoverConnectFilter);
    601         return mBluetoothAdapter.startDiscovery();
    602     }
    603 
    604     @Rpc(description = "Bond to a specified device once it's discovered.",
    605             returns = "Whether discovery started successfully. ")
    606     public Boolean bluetoothDiscoverAndBond(
    607             @RpcParameter(name = "deviceID",
    608                     description = "Name or MAC address of a bluetooth device.")
    609                     String deviceID) {
    610         mBluetoothAdapter.cancelDiscovery();
    611         if (listeningDevices.containsKey(deviceID)) {
    612             Log.d("This device is already in the process of discovery and bonding.");
    613             return true;
    614         }
    615         if (BluetoothFacade.deviceExists(mBluetoothAdapter.getBondedDevices(), deviceID)) {
    616             Log.d("Device " + deviceID + " is already bonded.");
    617             mEventFacade.postEvent("Bond" + deviceID, mGoodNews);
    618             return true;
    619         }
    620         DiscoverBondReceiver receiver = new DiscoverBondReceiver(deviceID);
    621         if (listeningDevices.containsKey("Bond" + deviceID)) {
    622             mService.unregisterReceiver(listeningDevices.remove("Bond" + deviceID));
    623         }
    624         listeningDevices.put("Bond" + deviceID, receiver);
    625         mService.registerReceiver(receiver, mBondFilter);
    626         Log.d("Start discovery for bonding.");
    627         return mBluetoothAdapter.startDiscovery();
    628     }
    629 
    630     @Rpc(description = "Unbond a device.",
    631             returns = "Whether the device was successfully unbonded.")
    632     public Boolean bluetoothUnbond(
    633             @RpcParameter(name = "deviceID",
    634                     description = "Name or MAC address of a bluetooth device.")
    635                     String deviceID) throws Exception {
    636         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    637                 deviceID);
    638         return mDevice.removeBond();
    639     }
    640 
    641     @Rpc(description = "Connect to a device that is already bonded.")
    642     public void bluetoothConnectBonded(
    643             @RpcParameter(name = "deviceID",
    644                     description = "Name or MAC address of a bluetooth device.")
    645                     String deviceID) throws Exception {
    646         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    647                 deviceID);
    648         connectProfile(mDevice, deviceID);
    649     }
    650 
    651     @Rpc(description = "Disconnect from a device that is already connected.")
    652     public void bluetoothDisconnectConnected(
    653             @RpcParameter(name = "deviceID",
    654                     description = "Name or MAC address of a bluetooth device.")
    655                     String deviceID) throws Exception {
    656         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    657                 deviceID);
    658         disconnectProfiles(mDevice, deviceID);
    659     }
    660 
    661     @Rpc(description = "Disconnect on a profile from a device that is already connected.")
    662     public void bluetoothDisconnectConnectedProfile(
    663             @RpcParameter(name = "deviceID",
    664                     description = "Name or MAC address of a bluetooth device.")
    665                     String deviceID,
    666             @RpcParameter(name = "profileSet",
    667                     description = "List of profiles to disconnect from.")
    668                     JSONArray profileSet
    669     ) throws Exception {
    670         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    671                 deviceID);
    672         disconnectProfiles(mDevice, deviceID, jsonArrayToIntegerList(profileSet));
    673     }
    674 
    675     @Rpc(description = "Change permissions for a profile.")
    676     public void bluetoothChangeProfileAccessPermission(
    677             @RpcParameter(name = "deviceID",
    678                     description = "Name or MAC address of a bluetooth device.")
    679                     String deviceID,
    680             @RpcParameter(name = "profileID",
    681                     description = "Number of Profile to change access permission")
    682                     Integer profileID,
    683             @RpcParameter(name = "access",
    684                     description = "Access level 0 = Unknown, 1 = Allowed, 2 = Rejected")
    685                     Integer access
    686     ) throws Exception {
    687         if (access < 0 || access > 2) {
    688             Log.w("Unsupported access level.");
    689             return;
    690         }
    691         BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
    692                 deviceID);
    693         switch (profileID) {
    694             case BluetoothProfile.PBAP:
    695                 mDevice.setPhonebookAccessPermission(access);
    696                 break;
    697             default:
    698                 Log.w("Unsupported profile access change.");
    699         }
    700     }
    701 
    702 
    703     @Override
    704     public void shutdown() {
    705         for (BroadcastReceiver receiver : listeningDevices.values()) {
    706             try {
    707                 mService.unregisterReceiver(receiver);
    708             } catch (IllegalArgumentException ex) {
    709                 Log.e("Failed to unregister " + ex);
    710             }
    711         }
    712         listeningDevices.clear();
    713         mService.unregisterReceiver(mPairingHelper);
    714     }
    715 }
    716 
    717