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.BluetoothActivityEnergyInfo;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.os.BatteryStats;
28 import android.os.Bundle;
29 import android.os.ParcelUuid;
30 import android.os.SynchronousResultReceiver;
31 
32 import com.googlecode.android_scripting.Log;
33 import com.googlecode.android_scripting.MainThread;
34 import com.googlecode.android_scripting.facade.EventFacade;
35 import com.googlecode.android_scripting.facade.FacadeManager;
36 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
37 import com.googlecode.android_scripting.rpc.Rpc;
38 import com.googlecode.android_scripting.rpc.RpcDefault;
39 import com.googlecode.android_scripting.rpc.RpcOptional;
40 import com.googlecode.android_scripting.rpc.RpcParameter;
41 
42 import java.time.Duration;
43 import java.util.Collection;
44 import java.util.HashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.concurrent.Callable;
49 import java.util.concurrent.ConcurrentHashMap;
50 import java.util.concurrent.Executor;
51 import java.util.concurrent.TimeoutException;
52 
53 /**
54  * Basic Bluetooth functions.
55  */
56 public class BluetoothFacade extends RpcReceiver {
57     private final Service mService;
58     private final BroadcastReceiver mDiscoveryReceiver;
59     private final IntentFilter discoveryFilter;
60     private final EventFacade mEventFacade;
61     private final BluetoothStateReceiver mStateReceiver;
62     private static final Object mReceiverLock = new Object();
63     private BluetoothStateReceiver mMultiStateReceiver;
64     private final BleStateReceiver mBleStateReceiver;
65     private Map<String, BluetoothConnection> connections =
66             new HashMap<String, BluetoothConnection>();
67     private BluetoothAdapter mBluetoothAdapter;
68 
69     public static ConcurrentHashMap<String, BluetoothDevice> DiscoveredDevices;
70 
BluetoothFacade(FacadeManager manager)71     public BluetoothFacade(FacadeManager manager) {
72         super(manager);
73         mBluetoothAdapter = MainThread.run(manager.getService(),
74                 new Callable<BluetoothAdapter>() {
75             @Override
76             public BluetoothAdapter call() throws Exception {
77                 return BluetoothAdapter.getDefaultAdapter();
78             }
79         });
80         mEventFacade = manager.getReceiver(EventFacade.class);
81         mService = manager.getService();
82 
83         DiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>();
84         discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
85         discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
86         mDiscoveryReceiver = new DiscoveryCacheReceiver();
87         mStateReceiver = new BluetoothStateReceiver();
88         mMultiStateReceiver = null;
89         mBleStateReceiver = new BleStateReceiver();
90     }
91 
92     class DiscoveryCacheReceiver extends BroadcastReceiver {
93         @Override
onReceive(Context context, Intent intent)94         public void onReceive(Context context, Intent intent) {
95             String action = intent.getAction();
96             if (action.equals(BluetoothDevice.ACTION_FOUND)) {
97                 BluetoothDevice device = intent.getParcelableExtra(
98                         BluetoothDevice.EXTRA_DEVICE);
99                 Log.d("Found device " + device.getAlias());
100                 if (!DiscoveredDevices.containsKey(device.getAddress())) {
101                     String name = device.getAlias();
102                     if (name != null) {
103                         DiscoveredDevices.put(device.getAlias(), device);
104                     }
105                     DiscoveredDevices.put(device.getAddress(), device);
106                 }
107             } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
108                 mEventFacade.postEvent("BluetoothDiscoveryFinished", new Bundle());
109                 mService.unregisterReceiver(mDiscoveryReceiver);
110             }
111         }
112     }
113 
114     class BluetoothStateReceiver extends BroadcastReceiver {
115 
116         private final boolean mIsMultiBroadcast;
117 
BluetoothStateReceiver()118         public BluetoothStateReceiver() {
119             mIsMultiBroadcast = false;
120         }
121 
BluetoothStateReceiver(boolean isMultiBroadcast)122         public BluetoothStateReceiver(boolean isMultiBroadcast) {
123             mIsMultiBroadcast = isMultiBroadcast;
124         }
125 
126         @Override
onReceive(Context context, Intent intent)127         public void onReceive(Context context, Intent intent) {
128             String action = intent.getAction();
129             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
130                 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
131                 Bundle msg = new Bundle();
132                 if (state == BluetoothAdapter.STATE_ON) {
133                     msg.putString("State", "ON");
134                     mEventFacade.postEvent("BluetoothStateChangedOn", msg.clone());
135                     if (!mIsMultiBroadcast) {
136                         mService.unregisterReceiver(mStateReceiver);
137                     }
138                 } else if(state == BluetoothAdapter.STATE_OFF) {
139                     msg.putString("State", "OFF");
140                     mEventFacade.postEvent("BluetoothStateChangedOff", msg.clone());
141                     if (!mIsMultiBroadcast) {
142                         mService.unregisterReceiver(mStateReceiver);
143                     }
144                 }
145             }
146         }
147     }
148 
149     class BleStateReceiver extends BroadcastReceiver {
150 
151         @Override
onReceive(Context context, Intent intent)152         public void onReceive(Context context, Intent intent) {
153             String action = intent.getAction();
154             if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) {
155                 int state = mBluetoothAdapter.getLeState();
156                 if (state == BluetoothAdapter.STATE_BLE_ON) {
157                     mEventFacade.postEvent("BleStateChangedOn", new Bundle());
158                     mService.unregisterReceiver(mBleStateReceiver);
159                 } else if (state == BluetoothAdapter.STATE_OFF) {
160                     mEventFacade.postEvent("BleStateChangedOff", new Bundle());
161                     mService.unregisterReceiver(mBleStateReceiver);
162                 }
163             }
164         }
165     }
166 
167 
deviceMatch(BluetoothDevice device, String deviceID)168     public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
169         return deviceID.equals(device.getAlias()) || deviceID.equals(
170                 device.getAddress());
171     }
172 
173     /**
174      * Get Bluetooth device.
175      * @param devices - HashMap of Device Address and Bluetooth device name.
176      * @param device - name of the device.
177      * @return the device name if it exits.
178      */
getDevice( ConcurrentHashMap<String, T> devices, String device)179     public static <T> BluetoothDevice getDevice(
180             ConcurrentHashMap<String, T> devices, String device)
181             throws Exception {
182         if (devices.containsKey(device)) {
183             return (BluetoothDevice) devices.get(device);
184         } else {
185             throw new Exception("Can't find device " + device);
186         }
187     }
188 
189     /**
190      * Get Bluetooth device.
191      * @param devices - Collection of device IDs.
192      * @param deviceID - ID of the desired device.
193      * @return the Bluetooth device if the device ID is matched.
194      */
getDevice( Collection<BluetoothDevice> devices, String deviceID)195     public static BluetoothDevice getDevice(
196             Collection<BluetoothDevice> devices, String deviceID)
197             throws Exception {
198         Log.d("Looking for " + deviceID);
199         for (BluetoothDevice bd : devices) {
200             Log.d(bd.getAlias() + " " + bd.getAddress());
201             if (deviceMatch(bd, deviceID)) {
202                 Log.d("Found match " + bd.getAlias() + " " + bd.getAddress());
203                 return bd;
204             }
205         }
206         throw new Exception("Can't find device " + deviceID);
207     }
208 
209     /**
210      * Verify device existence.
211      * @param devices - Collection of device IDs.
212      * @param deviceID - ID of the desired device.
213      * @return if the device Exists or not.
214      */
deviceExists( Collection<BluetoothDevice> devices, String deviceID)215     public static boolean deviceExists(
216             Collection<BluetoothDevice> devices, String deviceID) {
217         for (BluetoothDevice bd : devices) {
218             if (deviceMatch(bd, deviceID)) {
219                 Log.d("Found match " + bd.getAlias() + " " + bd.getAddress());
220                 return true;
221             }
222         }
223         return false;
224     }
225 
226     @Rpc(description = "Requests that the device be made connectable.")
bluetoothMakeConnectable()227     public void bluetoothMakeConnectable() {
228         mBluetoothAdapter
229                 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
230     }
231 
232     @Rpc(description = "Requests that the device be discoverable for Bluetooth connections.")
bluetoothMakeDiscoverable( @pcParametername = "duration", description = "period of time, in milliseconds," + "during which the device should be discoverable") @pcDefault"300000") Long duration)233     public void bluetoothMakeDiscoverable(
234             @RpcParameter(name = "duration",
235                           description = "period of time, in milliseconds,"
236                                       + "during which the device should be discoverable")
237             @RpcDefault("300000")
238             Long duration) {
239         Duration finalDuration = Duration.ofMillis(duration);
240         if (finalDuration.toSeconds() <= Integer.MAX_VALUE) {
241             mBluetoothAdapter.setDiscoverableTimeout(finalDuration);
242             mBluetoothAdapter.setScanMode(
243                     BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
244             Log.d("Making discoverable for " + duration + " milliseconds.\n");
245         } else {
246             Log.e("setScanMode: Duration in seconds outside of the bounds of an int");
247             throw new IllegalArgumentException("Duration not in bounds. In seconds, the "
248                     + "duration must be in the range of an int");
249         }
250     }
251 
252     @Rpc(description = "Requests that the device be not discoverable.")
bluetoothMakeUndiscoverable()253     public void bluetoothMakeUndiscoverable() {
254         Log.d("Making undiscoverable\n");
255         mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
256     }
257 
258     @Rpc(description = "Queries a remote device for it's name or null if it can't be resolved")
bluetoothGetRemoteDeviceName( @pcParametername = "address", description = "Bluetooth Address For Target Device") String address)259     public String bluetoothGetRemoteDeviceName(
260             @RpcParameter(name = "address", description = "Bluetooth Address For Target Device")
261             String address) {
262         try {
263             BluetoothDevice mDevice;
264             mDevice = mBluetoothAdapter.getRemoteDevice(address);
265             return mDevice.getName();
266         } catch (Exception e) {
267             return null;
268         }
269     }
270 
271     @Rpc(description = "Fetch UUIDS with SDP")
bluetoothFetchUuidsWithSdp( @pcParametername = "address", description = "Bluetooth Address For Target Device") String address)272     public boolean bluetoothFetchUuidsWithSdp(
273             @RpcParameter(name = "address", description = "Bluetooth Address For Target Device")
274             String address) {
275         try {
276             BluetoothDevice mDevice;
277             mDevice = mBluetoothAdapter.getRemoteDevice(address);
278             return mDevice.fetchUuidsWithSdp();
279         } catch (Exception e) {
280             return false;
281         }
282     }
283 
284     @Rpc(description = "Get local Bluetooth device name")
bluetoothGetLocalName()285     public String bluetoothGetLocalName() {
286         return mBluetoothAdapter.getName();
287     }
288 
289     @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
bluetoothSetLocalName( @pcParametername = "name", description = "New local name") String name)290     public boolean bluetoothSetLocalName(
291         @RpcParameter(name = "name", description = "New local name")
292         String name) {
293         return mBluetoothAdapter.setName(name);
294     }
295 
296     @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ")
bluetoothGetLocalAddress()297     public String bluetoothGetLocalAddress() {
298         return mBluetoothAdapter.getAddress();
299     }
300 
301     @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
bluetoothGetLocalUuids()302     public ParcelUuid[] bluetoothGetLocalUuids() {
303         List<ParcelUuid> uuidsList = mBluetoothAdapter.getUuidsList();
304         ParcelUuid[] uuidsArray = new ParcelUuid[uuidsList.size()];
305         uuidsList.toArray(uuidsArray);
306         return uuidsArray;
307     }
308 
309     @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n"
310             + "\t-1 when Bluetooth is disabled.\r\n"
311             + "\t0 if non discoverable and non connectable.\r\n"
312             + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.")
bluetoothGetScanMode()313     public int bluetoothGetScanMode() {
314         if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF
315                 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) {
316             return -1;
317         }
318         switch (mBluetoothAdapter.getScanMode()) {
319             case BluetoothAdapter.SCAN_MODE_NONE:
320                 return 0;
321             case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
322                 return 1;
323             case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
324                 return 3;
325             default:
326                 return mBluetoothAdapter.getScanMode() - 20;
327         }
328     }
329 
330     @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.")
bluetoothGetBondedDevices()331     public Set<BluetoothDevice> bluetoothGetBondedDevices() {
332         return mBluetoothAdapter.getBondedDevices();
333     }
334 
335     @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
bluetoothCheckState()336     public Boolean bluetoothCheckState() {
337         return mBluetoothAdapter.isEnabled();
338     }
339 
340     @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.")
bluetoothFactoryReset()341     public boolean bluetoothFactoryReset() {
342         return mBluetoothAdapter.clearBluetooth();
343     }
344 
345     @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
bluetoothToggleState(@pcParametername = "enabled") @pcOptional Boolean enabled, @RpcParameter(name = "prompt", description = "Prompt the user to confirm changing the Bluetooth state.") @RpcDefault("false") Boolean prompt)346     public Boolean bluetoothToggleState(@RpcParameter(name = "enabled")
347     @RpcOptional
348     Boolean enabled,
349             @RpcParameter(name = "prompt",
350                           description = "Prompt the user to confirm changing the Bluetooth state.")
351             @RpcDefault("false")
352             Boolean prompt) {
353         mService.registerReceiver(mStateReceiver,
354                                   new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
355         if (enabled == null) {
356             enabled = !bluetoothCheckState();
357         }
358         if (enabled) {
359             return mBluetoothAdapter.enable();
360         } else {
361             shutdown();
362             return mBluetoothAdapter.disable();
363         }
364     }
365 
366 
367     @Rpc(description = "Start the remote device discovery process. ",
368          returns = "true on success, false on error")
bluetoothStartDiscovery()369     public Boolean bluetoothStartDiscovery() {
370         DiscoveredDevices.clear();
371         mService.registerReceiver(mDiscoveryReceiver, discoveryFilter);
372         return mBluetoothAdapter.startDiscovery();
373     }
374 
375     @Rpc(description = "Cancel the current device discovery process.",
376          returns = "true on success, false on error")
bluetoothCancelDiscovery()377     public Boolean bluetoothCancelDiscovery() {
378         try {
379             mService.unregisterReceiver(mDiscoveryReceiver);
380         } catch (IllegalArgumentException e) {
381             Log.d("IllegalArgumentExeption found when trying to unregister reciever");
382         }
383         return mBluetoothAdapter.cancelDiscovery();
384     }
385 
386     @Rpc(description = "If the local Bluetooth adapter is currently"
387                      + "in the device discovery process.")
bluetoothIsDiscovering()388     public Boolean bluetoothIsDiscovering() {
389         return mBluetoothAdapter.isDiscovering();
390     }
391 
392     @Rpc(description = "Get all the discovered bluetooth devices.")
bluetoothGetDiscoveredDevices()393     public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
394         while (bluetoothIsDiscovering())
395             ;
396         return DiscoveredDevices.values();
397     }
398 
399     @Rpc(description = "Get Bluetooth controller activity energy info.")
bluetoothGetControllerActivityEnergyInfo()400     public String bluetoothGetControllerActivityEnergyInfo() {
401         SynchronousResultReceiver receiver = new SynchronousResultReceiver();
402         mBluetoothAdapter.requestControllerActivityEnergyInfo(
403                 new Executor() {
404                     @Override
405                     public void execute(Runnable runnable) {
406                         runnable.run();
407                     }
408                 },
409                 new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() {
410                     @Override
411                     public void onBluetoothActivityEnergyInfoAvailable(
412                             BluetoothActivityEnergyInfo info) {
413                         Bundle bundle = new Bundle();
414                         bundle.putParcelable(
415                                 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
416                         receiver.send(0, bundle);
417                     }
418 
419                     @Override
420                     public void onBluetoothActivityEnergyInfoError(int errorCode) {
421                         Bundle bundle = new Bundle();
422                         bundle.putParcelable(
423                                 BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, null);
424                         receiver.send(0, bundle);
425                     }
426                 }
427         );
428         try {
429             SynchronousResultReceiver.Result result = receiver.awaitResult(1000);
430             if (result.bundle != null) {
431                 return result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY)
432                     .toString();
433             }
434         } catch (TimeoutException e) {
435             Log.e("getControllerActivityEnergyInfo timed out");
436         }
437         return null;
438     }
439 
440     @Rpc(description = "Return true if hardware has entries" +
441             "available for matching beacons.")
bluetoothIsHardwareTrackingFiltersAvailable()442     public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
443         return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
444     }
445 
446     /**
447      * Return true if LE 2M PHY feature is supported.
448      *
449      * @return true if chipset supports LE 2M PHY feature
450      */
451     @Rpc(description = "Return true if LE 2M PHY feature is supported")
bluetoothIsLe2MPhySupported()452     public boolean bluetoothIsLe2MPhySupported() {
453         return mBluetoothAdapter.isLe2MPhySupported();
454     }
455 
456     /**
457      * Return true if LE Coded PHY feature is supported.
458      *
459      * @return true if chipset supports LE Coded PHY feature
460      */
461     @Rpc(description = "Return true if LE Coded PHY feature is supported")
bluetoothIsLeCodedPhySupported()462     public boolean bluetoothIsLeCodedPhySupported() {
463         return mBluetoothAdapter.isLeCodedPhySupported();
464     }
465 
466     /**
467      * Return true if LE Extended Advertising feature is supported.
468      *
469      * @return true if chipset supports LE Extended Advertising feature
470      */
471     @Rpc(description = "Return true if LE Extended Advertising is supported")
bluetoothIsLeExtendedAdvertisingSupported()472     public boolean bluetoothIsLeExtendedAdvertisingSupported() {
473         return mBluetoothAdapter.isLeExtendedAdvertisingSupported();
474     }
475 
476     /**
477      * Return true if LE Periodic Advertising feature is supported.
478      *
479      * @return true if chipset supports LE Periodic Advertising feature
480      */
481     @Rpc(description = "Return true if LE Periodic Advertising is supported")
bluetoothIsLePeriodicAdvertisingSupported()482     public boolean bluetoothIsLePeriodicAdvertisingSupported() {
483         return mBluetoothAdapter.isLePeriodicAdvertisingSupported();
484     }
485 
486     /**
487      * Return the maximum LE advertising data length,
488      * if LE Extended Advertising feature is supported.
489      *
490      * @return the maximum LE advertising data length.
491      */
492     @Rpc(description = "Return the maximum LE advertising data length")
bluetoothGetLeMaximumAdvertisingDataLength()493     public int bluetoothGetLeMaximumAdvertisingDataLength() {
494         return mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
495     }
496 
497     @Rpc(description = "Gets the current state of LE.")
bluetoothGetLeState()498     public int bluetoothGetLeState() {
499         return mBluetoothAdapter.getLeState();
500     }
501 
502     @Rpc(description = "Enables BLE functionalities.")
bluetoothEnableBLE()503     public boolean bluetoothEnableBLE() {
504         mService.registerReceiver(mBleStateReceiver,
505             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
506         return mBluetoothAdapter.enableBLE();
507     }
508 
509     @Rpc(description = "Disables BLE functionalities.")
bluetoothDisableBLE()510     public boolean bluetoothDisableBLE() {
511         mService.registerReceiver(mBleStateReceiver,
512             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
513         return mBluetoothAdapter.disableBLE();
514     }
515 
516     @Rpc(description = "Listen for a Bluetooth LE State Change.")
bluetoothListenForBleStateChange()517     public boolean bluetoothListenForBleStateChange() {
518         mService.registerReceiver(mBleStateReceiver,
519             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
520         return true;
521     }
522 
523     @Rpc(description = "Stop Listening for a Bluetooth LE State Change.")
bluetoothStopListeningForBleStateChange()524     public boolean bluetoothStopListeningForBleStateChange() {
525         mService.unregisterReceiver(mBleStateReceiver);
526         return true;
527     }
528 
529     @Rpc(description = "Listen for Bluetooth State Changes.")
bluetoothStartListeningForAdapterStateChange()530     public boolean bluetoothStartListeningForAdapterStateChange() {
531         synchronized (mReceiverLock) {
532             if (mMultiStateReceiver != null) {
533                 Log.e("Persistent Bluetooth Receiver State Change Listener Already Active");
534                 return false;
535             }
536             mMultiStateReceiver = new BluetoothStateReceiver(true);
537             mService.registerReceiver(mMultiStateReceiver,
538                     new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
539         }
540         return true;
541     }
542 
543     @Rpc(description = "Stop Listening for Bluetooth State Changes.")
bluetoothStopListeningForAdapterStateChange()544     public boolean bluetoothStopListeningForAdapterStateChange() {
545         synchronized (mReceiverLock) {
546             if (mMultiStateReceiver == null) {
547                 Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop");
548                 return false;
549             }
550             mService.unregisterReceiver(mMultiStateReceiver);
551             mMultiStateReceiver = null;
552         }
553         return true;
554     }
555 
556     @Override
shutdown()557     public void shutdown() {
558         for (Map.Entry<String,
559                 BluetoothConnection> entry : connections.entrySet()) {
560             entry.getValue().stop();
561         }
562         if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange();
563         connections.clear();
564     }
565 }
566