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