BluetoothFacade.java revision f04335f899f2cce69f843692a3cb9cec229683c2
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
17package com.googlecode.android_scripting.facade.bluetooth;
18
19import android.app.Service;
20import android.bluetooth.BluetoothActivityEnergyInfo;
21import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.os.Bundle;
28import android.os.ParcelUuid;
29
30import com.googlecode.android_scripting.Log;
31import com.googlecode.android_scripting.MainThread;
32import com.googlecode.android_scripting.facade.EventFacade;
33import com.googlecode.android_scripting.facade.FacadeManager;
34import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
35import com.googlecode.android_scripting.rpc.Rpc;
36import com.googlecode.android_scripting.rpc.RpcDefault;
37import com.googlecode.android_scripting.rpc.RpcOptional;
38import com.googlecode.android_scripting.rpc.RpcParameter;
39
40import java.util.Collection;
41import java.util.HashMap;
42import java.util.Map;
43import java.util.Set;
44import java.util.concurrent.Callable;
45import java.util.concurrent.ConcurrentHashMap;
46
47/**
48 * Basic Bluetooth functions.
49 */
50public 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
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
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
110        public BluetoothStateReceiver() {
111            mIsMultiBroadcast = false;
112        }
113
114        public BluetoothStateReceiver(boolean isMultiBroadcast) {
115            mIsMultiBroadcast = isMultiBroadcast;
116        }
117
118        @Override
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
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
157    public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
158        return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress());
159    }
160
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
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
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.")
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.")
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.")
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")
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 = "Get local Bluetooth device name")
231    public String bluetoothGetLocalName() {
232        return mBluetoothAdapter.getName();
233    }
234
235    @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
236    public boolean bluetoothSetLocalName(
237        @RpcParameter(name = "name", description = "New local name")
238        String name) {
239        return mBluetoothAdapter.setName(name);
240    }
241
242    @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ")
243    public String bluetoothGetLocalAddress() {
244        return mBluetoothAdapter.getAddress();
245    }
246
247    @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
248    public ParcelUuid[] bluetoothGetLocalUuids() {
249        return mBluetoothAdapter.getUuids();
250    }
251
252    @Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n"
253            + "\t-1 when Bluetooth is disabled.\r\n"
254            + "\t0 if non discoverable and non connectable.\r\n"
255            + "\r1 connectable non discoverable." + "\r3 connectable and discoverable.")
256    public int bluetoothGetScanMode() {
257        if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF
258                || mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) {
259            return -1;
260        }
261        switch (mBluetoothAdapter.getScanMode()) {
262            case BluetoothAdapter.SCAN_MODE_NONE:
263                return 0;
264            case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
265                return 1;
266            case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
267                return 3;
268            default:
269                return mBluetoothAdapter.getScanMode() - 20;
270        }
271    }
272
273    @Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.")
274    public Set<BluetoothDevice> bluetoothGetBondedDevices() {
275        return mBluetoothAdapter.getBondedDevices();
276    }
277
278    @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
279    public Boolean bluetoothCheckState() {
280        return mBluetoothAdapter.isEnabled();
281    }
282
283    @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.")
284    public boolean bluetoothFactoryReset() {
285        return mBluetoothAdapter.factoryReset();
286    }
287
288    @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
289    public Boolean bluetoothToggleState(@RpcParameter(name = "enabled")
290    @RpcOptional
291    Boolean enabled,
292            @RpcParameter(name = "prompt",
293                          description = "Prompt the user to confirm changing the Bluetooth state.")
294            @RpcDefault("false")
295            Boolean prompt) {
296        mService.registerReceiver(mStateReceiver,
297                                  new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
298        if (enabled == null) {
299            enabled = !bluetoothCheckState();
300        }
301        if (enabled) {
302            return mBluetoothAdapter.enable();
303        } else {
304            shutdown();
305            return mBluetoothAdapter.disable();
306        }
307    }
308
309
310    @Rpc(description = "Start the remote device discovery process. ",
311         returns = "true on success, false on error")
312    public Boolean bluetoothStartDiscovery() {
313        DiscoveredDevices.clear();
314        mService.registerReceiver(mDiscoveryReceiver, discoveryFilter);
315        return mBluetoothAdapter.startDiscovery();
316    }
317
318    @Rpc(description = "Cancel the current device discovery process.",
319         returns = "true on success, false on error")
320    public Boolean bluetoothCancelDiscovery() {
321        try {
322            mService.unregisterReceiver(mDiscoveryReceiver);
323        } catch (IllegalArgumentException e) {
324            Log.d("IllegalArgumentExeption found when trying to unregister reciever");
325        }
326        return mBluetoothAdapter.cancelDiscovery();
327    }
328
329    @Rpc(description = "If the local Bluetooth adapter is currently"
330                     + "in the device discovery process.")
331    public Boolean bluetoothIsDiscovering() {
332        return mBluetoothAdapter.isDiscovering();
333    }
334
335    @Rpc(description = "Get all the discovered bluetooth devices.")
336    public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
337        while (bluetoothIsDiscovering())
338            ;
339        return DiscoveredDevices.values();
340    }
341
342    @Rpc(description = "Enable or disable the Bluetooth HCI snoop log")
343    public boolean bluetoothConfigHciSnoopLog(
344            @RpcParameter(name = "value", description = "enable or disable log")
345            Boolean value
346            ) {
347        return mBluetoothAdapter.configHciSnoopLog(value);
348    }
349
350    @Rpc(description = "Get Bluetooth controller activity energy info.")
351    public String bluetoothGetControllerActivityEnergyInfo(
352        @RpcParameter(name = "value")
353        Integer value
354            ) {
355        BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter
356            .getControllerActivityEnergyInfo(value);
357        while (energyInfo == null) {
358          energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value);
359        }
360        return energyInfo.toString();
361    }
362
363    @Rpc(description = "Return true if hardware has entries" +
364            "available for matching beacons.")
365    public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
366        return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
367    }
368
369    @Rpc(description = "Gets the current state of LE.")
370    public int bluetoothGetLeState() {
371        return mBluetoothAdapter.getLeState();
372    }
373
374    @Rpc(description = "Enables BLE functionalities.")
375    public boolean bluetoothEnableBLE() {
376        mService.registerReceiver(mBleStateReceiver,
377            new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
378        return mBluetoothAdapter.enableBLE();
379    }
380
381    @Rpc(description = "Disables BLE functionalities.")
382    public boolean bluetoothDisableBLE() {
383        mService.registerReceiver(mBleStateReceiver,
384            new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
385        return mBluetoothAdapter.disableBLE();
386    }
387
388    @Rpc(description = "Listen for a Bluetooth LE State Change.")
389    public boolean bluetoothListenForBleStateChange() {
390        mService.registerReceiver(mBleStateReceiver,
391            new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
392        return true;
393    }
394
395    @Rpc(description = "Stop Listening for a Bluetooth LE State Change.")
396    public boolean bluetoothStopListeningForBleStateChange() {
397        mService.unregisterReceiver(mBleStateReceiver);
398        return true;
399    }
400
401    @Rpc(description = "Listen for Bluetooth State Changes.")
402    public boolean bluetoothStartListeningForAdapterStateChange() {
403        synchronized (mReceiverLock) {
404            if (mMultiStateReceiver != null) {
405                Log.e("Persistent Bluetooth Receiver State Change Listener Already Active");
406                return false;
407            }
408            mMultiStateReceiver = new BluetoothStateReceiver(true);
409            mService.registerReceiver(mMultiStateReceiver,
410                    new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
411        }
412        return true;
413    }
414
415    @Rpc(description = "Stop Listening for Bluetooth State Changes.")
416    public boolean bluetoothStopListeningForAdapterStateChange() {
417        synchronized (mReceiverLock) {
418            if (mMultiStateReceiver == null) {
419                Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop");
420                return false;
421            }
422            mService.unregisterReceiver(mMultiStateReceiver);
423            mMultiStateReceiver = null;
424        }
425        return true;
426    }
427
428    @Override
429    public void shutdown() {
430        for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
431            entry.getValue().stop();
432        }
433        if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange();
434        connections.clear();
435    }
436}
437