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