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