Home | History | Annotate | Download | only in bluetooth
      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 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             mBluetoothAdapter.enable();
    303         } else {
    304             shutdown();
    305             mBluetoothAdapter.disable();
    306         }
    307         return enabled;
    308     }
    309 
    310 
    311     @Rpc(description = "Start the remote device discovery process. ",
    312          returns = "true on success, false on error")
    313     public Boolean bluetoothStartDiscovery() {
    314         DiscoveredDevices.clear();
    315         mService.registerReceiver(mDiscoveryReceiver, discoveryFilter);
    316         return mBluetoothAdapter.startDiscovery();
    317     }
    318 
    319     @Rpc(description = "Cancel the current device discovery process.",
    320          returns = "true on success, false on error")
    321     public Boolean bluetoothCancelDiscovery() {
    322         try {
    323             mService.unregisterReceiver(mDiscoveryReceiver);
    324         } catch (IllegalArgumentException e) {
    325             Log.d("IllegalArgumentExeption found when trying to unregister reciever");
    326         }
    327         return mBluetoothAdapter.cancelDiscovery();
    328     }
    329 
    330     @Rpc(description = "If the local Bluetooth adapter is currently"
    331                      + "in the device discovery process.")
    332     public Boolean bluetoothIsDiscovering() {
    333         return mBluetoothAdapter.isDiscovering();
    334     }
    335 
    336     @Rpc(description = "Get all the discovered bluetooth devices.")
    337     public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
    338         while (bluetoothIsDiscovering())
    339             ;
    340         return DiscoveredDevices.values();
    341     }
    342 
    343     @Rpc(description = "Enable or disable the Bluetooth HCI snoop log")
    344     public boolean bluetoothConfigHciSnoopLog(
    345             @RpcParameter(name = "value", description = "enable or disable log")
    346             Boolean value
    347             ) {
    348         return mBluetoothAdapter.configHciSnoopLog(value);
    349     }
    350 
    351     @Rpc(description = "Get Bluetooth controller activity energy info.")
    352     public String bluetoothGetControllerActivityEnergyInfo(
    353         @RpcParameter(name = "value")
    354         Integer value
    355             ) {
    356         BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter
    357             .getControllerActivityEnergyInfo(value);
    358         while (energyInfo == null) {
    359           energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value);
    360         }
    361         return energyInfo.toString();
    362     }
    363 
    364     @Rpc(description = "Return true if hardware has entries" +
    365             "available for matching beacons.")
    366     public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
    367         return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
    368     }
    369 
    370     @Rpc(description = "Gets the current state of LE.")
    371     public int bluetoothGetLeState() {
    372         return mBluetoothAdapter.getLeState();
    373     }
    374 
    375     @Rpc(description = "Enables BLE functionalities.")
    376     public boolean bluetoothEnableBLE() {
    377         mService.registerReceiver(mBleStateReceiver,
    378             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
    379         return mBluetoothAdapter.enableBLE();
    380     }
    381 
    382     @Rpc(description = "Disables BLE functionalities.")
    383     public boolean bluetoothDisableBLE() {
    384         mService.registerReceiver(mBleStateReceiver,
    385             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
    386         return mBluetoothAdapter.disableBLE();
    387     }
    388 
    389     @Rpc(description = "Listen for Bluetooth State Changes.")
    390     public boolean bluetoothStartListeningForAdapterStateChange() {
    391         synchronized (mReceiverLock) {
    392             if (mMultiStateReceiver != null) {
    393                 Log.e("Persistent Bluetooth Receiver State Change Listener Already Active");
    394                 return false;
    395             }
    396             mMultiStateReceiver = new BluetoothStateReceiver(true);
    397             mService.registerReceiver(mMultiStateReceiver,
    398                     new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
    399         }
    400         return true;
    401     }
    402 
    403     @Rpc(description = "Stop Listening for Bluetooth State Changes.")
    404     public boolean bluetoothStopListeningForAdapterStateChange() {
    405         synchronized (mReceiverLock) {
    406             if (mMultiStateReceiver == null) {
    407                 Log.d("No Persistent Bluetooth Receiever State Change Listener Found to Stop");
    408                 return false;
    409             }
    410             mService.unregisterReceiver(mMultiStateReceiver);
    411             mMultiStateReceiver = null;
    412         }
    413         return true;
    414     }
    415 
    416     @Override
    417     public void shutdown() {
    418         for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
    419             entry.getValue().stop();
    420         }
    421         if (mMultiStateReceiver != null ) bluetoothStopListeningForAdapterStateChange();
    422         connections.clear();
    423     }
    424 }
    425