1 /*
2  * Copyright (C) 2016 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * 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 final BleStateReceiver mBleStateReceiver;
57     private Map<String, BluetoothConnection> connections =
58             new HashMap<String, BluetoothConnection>();
59     private BluetoothAdapter mBluetoothAdapter;
60 
61     public static ConcurrentHashMap<String, BluetoothDevice> DiscoveredDevices;
62 
BluetoothFacade(FacadeManager manager)63     public BluetoothFacade(FacadeManager manager) {
64         super(manager);
65         mBluetoothAdapter = MainThread.run(manager.getService(), new Callable<BluetoothAdapter>() {
66             @Override
67             public BluetoothAdapter call() throws Exception {
68                 return BluetoothAdapter.getDefaultAdapter();
69             }
70         });
71         mEventFacade = manager.getReceiver(EventFacade.class);
72         mService = manager.getService();
73 
74         DiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>();
75         discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
76         discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
77         mDiscoveryReceiver = new DiscoveryCacheReceiver();
78         mStateReceiver = new BluetoothStateReceiver();
79         mBleStateReceiver = new BleStateReceiver();
80     }
81 
82     class DiscoveryCacheReceiver extends BroadcastReceiver {
83         @Override
onReceive(Context context, Intent intent)84         public void onReceive(Context context, Intent intent) {
85             String action = intent.getAction();
86             if (action.equals(BluetoothDevice.ACTION_FOUND)) {
87                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
88                 Log.d("Found device " + device.getAliasName());
89                 if (!DiscoveredDevices.containsKey(device.getAddress())) {
90                     String name = device.getAliasName();
91                     if (name != null) {
92                         DiscoveredDevices.put(device.getAliasName(), device);
93                     }
94                     DiscoveredDevices.put(device.getAddress(), device);
95                 }
96             } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
97                 mEventFacade.postEvent("BluetoothDiscoveryFinished", new Bundle());
98                 mService.unregisterReceiver(mDiscoveryReceiver);
99             }
100         }
101     }
102 
103     class BluetoothStateReceiver extends BroadcastReceiver {
104 
105         @Override
onReceive(Context context, Intent intent)106         public void onReceive(Context context, Intent intent) {
107             String action = intent.getAction();
108             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
109                 final int state = mBluetoothAdapter.getState();
110                 Bundle msg = new Bundle();
111                 if (state == BluetoothAdapter.STATE_ON) {
112                     msg.putString("State", "ON");
113                     mEventFacade.postEvent("BluetoothStateChangedOn", msg);
114                     mService.unregisterReceiver(mStateReceiver);
115                 } else if(state == BluetoothAdapter.STATE_OFF) {
116                     msg.putString("State", "OFF");
117                     mEventFacade.postEvent("BluetoothStateChangedOff", msg);
118                     mService.unregisterReceiver(mStateReceiver);
119                 }
120                 msg.clear();
121             }
122         }
123     }
124 
125     class BleStateReceiver extends BroadcastReceiver {
126 
127         @Override
onReceive(Context context, Intent intent)128         public void onReceive(Context context, Intent intent) {
129             String action = intent.getAction();
130             if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) {
131                 int state = mBluetoothAdapter.getLeState();
132                 if (state == BluetoothAdapter.STATE_BLE_ON) {
133                     mEventFacade.postEvent("BleStateChangedOn", new Bundle());
134                     mService.unregisterReceiver(mBleStateReceiver);
135                 } else if (state == BluetoothAdapter.STATE_OFF) {
136                     mEventFacade.postEvent("BleStateChangedOff", new Bundle());
137                     mService.unregisterReceiver(mBleStateReceiver);
138                 }
139             }
140         }
141     }
142 
143 
deviceMatch(BluetoothDevice device, String deviceID)144     public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
145         return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress());
146     }
147 
getDevice(ConcurrentHashMap<String, T> devices, String device)148     public static <T> BluetoothDevice getDevice(ConcurrentHashMap<String, T> devices, String device)
149             throws Exception {
150         if (devices.containsKey(device)) {
151             return (BluetoothDevice) devices.get(device);
152         } else {
153             throw new Exception("Can't find device " + device);
154         }
155     }
156 
getDevice(Collection<BluetoothDevice> devices, String deviceID)157     public static BluetoothDevice getDevice(Collection<BluetoothDevice> devices, String deviceID)
158             throws Exception {
159         Log.d("Looking for " + deviceID);
160         for (BluetoothDevice bd : devices) {
161             Log.d(bd.getAliasName() + " " + bd.getAddress());
162             if (deviceMatch(bd, deviceID)) {
163                 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress());
164                 return bd;
165             }
166         }
167         throw new Exception("Can't find device " + deviceID);
168     }
169 
deviceExists(Collection<BluetoothDevice> devices, String deviceID)170     public static boolean deviceExists(Collection<BluetoothDevice> devices, String deviceID) {
171         for (BluetoothDevice bd : devices) {
172             if (deviceMatch(bd, deviceID)) {
173                 Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress());
174                 return true;
175             }
176         }
177         return false;
178     }
179 
180     @Rpc(description = "Requests that the device be made connectable.")
bluetoothMakeConnectable()181     public void bluetoothMakeConnectable() {
182         mBluetoothAdapter
183                 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
184     }
185 
186     @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)187     public void bluetoothMakeDiscoverable(
188             @RpcParameter(name = "duration",
189                           description = "period of time, in seconds,"
190                                       + "during which the device should be discoverable")
191             @RpcDefault("300")
192             Integer duration) {
193         Log.d("Making discoverable for " + duration + " seconds.\n");
194         mBluetoothAdapter
195                 .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration);
196     }
197 
198     @Rpc(description = "Requests that the device be not discoverable.")
bluetoothMakeUndiscoverable()199     public void bluetoothMakeUndiscoverable() {
200         Log.d("Making undiscoverable\n");
201         mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
202     }
203 
204     @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)205     public String bluetoothGetRemoteDeviceName(
206             @RpcParameter(name = "address", description = "Bluetooth Address For Target Device")
207             String address) {
208         try {
209             BluetoothDevice mDevice;
210             mDevice = mBluetoothAdapter.getRemoteDevice(address);
211             return mDevice.getName();
212         } catch (Exception e) {
213             return null;
214         }
215     }
216 
217     @Rpc(description = "Get local Bluetooth device name")
bluetoothGetLocalName()218     public String bluetoothGetLocalName() {
219         return mBluetoothAdapter.getName();
220     }
221 
222     @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
bluetoothSetLocalName( @pcParametername = "name", description = "New local name") String name)223     public boolean bluetoothSetLocalName(
224         @RpcParameter(name = "name", description = "New local name")
225         String name) {
226         return mBluetoothAdapter.setName(name);
227     }
228 
229     @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ")
bluetoothGetLocalAddress()230     public String bluetoothGetLocalAddress() {
231         return mBluetoothAdapter.getAddress();
232     }
233 
234     @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
bluetoothGetLocalUuids()235     public ParcelUuid[] bluetoothGetLocalUuids() {
236         return mBluetoothAdapter.getUuids();
237     }
238 
239     @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n"
240             + "\t-1 when Bluetooth is disabled.\r\n"
241             + "\t0 if non discoverable and non connectable.\r\n"
242             + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.")
bluetoothGetScanMode()243     public int bluetoothGetScanMode() {
244         if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF
245                 || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) {
246             return -1;
247         }
248         switch (mBluetoothAdapter.getScanMode()) {
249             case BluetoothAdapter.SCAN_MODE_NONE:
250                 return 0;
251             case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
252                 return 1;
253             case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
254                 return 3;
255             default:
256                 return mBluetoothAdapter.getScanMode() - 20;
257         }
258     }
259 
260     @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.")
bluetoothGetBondedDevices()261     public Set<BluetoothDevice> bluetoothGetBondedDevices() {
262         return mBluetoothAdapter.getBondedDevices();
263     }
264 
265     @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
bluetoothCheckState()266     public Boolean bluetoothCheckState() {
267         return mBluetoothAdapter.isEnabled();
268     }
269 
270     @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)271     public Boolean bluetoothToggleState(@RpcParameter(name = "enabled")
272     @RpcOptional
273     Boolean enabled,
274             @RpcParameter(name = "prompt",
275                           description = "Prompt the user to confirm changing the Bluetooth state.")
276             @RpcDefault("false")
277             Boolean prompt) {
278         mService.registerReceiver(mStateReceiver,
279                                   new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
280         if (enabled == null) {
281             enabled = !bluetoothCheckState();
282         }
283         if (enabled) {
284             mBluetoothAdapter.enable();
285         } else {
286             shutdown();
287             mBluetoothAdapter.disable();
288         }
289         return enabled;
290     }
291 
292 
293     @Rpc(description = "Start the remote device discovery process. ",
294          returns = "true on success, false on error")
bluetoothStartDiscovery()295     public Boolean bluetoothStartDiscovery() {
296         DiscoveredDevices.clear();
297         mService.registerReceiver(mDiscoveryReceiver, discoveryFilter);
298         return mBluetoothAdapter.startDiscovery();
299     }
300 
301     @Rpc(description = "Cancel the current device discovery process.",
302          returns = "true on success, false on error")
bluetoothCancelDiscovery()303     public Boolean bluetoothCancelDiscovery() {
304         try {
305             mService.unregisterReceiver(mDiscoveryReceiver);
306         } catch (IllegalArgumentException e) {
307             Log.d("IllegalArgumentExeption found when trying to unregister reciever");
308         }
309         return mBluetoothAdapter.cancelDiscovery();
310     }
311 
312     @Rpc(description = "If the local Bluetooth adapter is currently"
313                      + "in the device discovery process.")
bluetoothIsDiscovering()314     public Boolean bluetoothIsDiscovering() {
315         return mBluetoothAdapter.isDiscovering();
316     }
317 
318     @Rpc(description = "Get all the discovered bluetooth devices.")
bluetoothGetDiscoveredDevices()319     public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
320         while (bluetoothIsDiscovering())
321             ;
322         return DiscoveredDevices.values();
323     }
324 
325     @Rpc(description = "Enable or disable the Bluetooth HCI snoop log")
bluetoothConfigHciSnoopLog( @pcParametername = "value", description = "enable or disable log") Boolean value )326     public boolean bluetoothConfigHciSnoopLog(
327             @RpcParameter(name = "value", description = "enable or disable log")
328             Boolean value
329             ) {
330         return mBluetoothAdapter.configHciSnoopLog(value);
331     }
332 
333     @Rpc(description = "Get Bluetooth controller activity energy info.")
bluetoothGetControllerActivityEnergyInfo( @pcParametername = "value") Integer value )334     public String bluetoothGetControllerActivityEnergyInfo(
335         @RpcParameter(name = "value")
336         Integer value
337             ) {
338         BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter
339             .getControllerActivityEnergyInfo(value);
340         while (energyInfo == null) {
341           energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value);
342         }
343         return energyInfo.toString();
344     }
345 
346     @Rpc(description = "Return true if hardware has entries" +
347             "available for matching beacons.")
bluetoothIsHardwareTrackingFiltersAvailable()348     public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
349         return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
350     }
351 
352     @Rpc(description = "Gets the current state of LE.")
bluetoothGetLeState()353     public int bluetoothGetLeState() {
354         return mBluetoothAdapter.getLeState();
355     }
356 
357     @Rpc(description = "Enables BLE functionalities.")
bluetoothEnableBLE()358     public boolean bluetoothEnableBLE() {
359         mService.registerReceiver(mBleStateReceiver,
360             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
361         return mBluetoothAdapter.enableBLE();
362     }
363 
364     @Rpc(description = "Disables BLE functionalities.")
bluetoothDisableBLE()365     public boolean bluetoothDisableBLE() {
366         mService.registerReceiver(mBleStateReceiver,
367             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
368         return mBluetoothAdapter.disableBLE();
369     }
370 
371     @Override
shutdown()372     public void shutdown() {
373         for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
374             entry.getValue().stop();
375         }
376         connections.clear();
377     }
378 }
379