Home | History | Annotate | Download | only in bluetooth
      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 
     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
     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.getAliasName());
     94                 if (!DiscoveredDevices.containsKey(device.getAddress())) {
     95                     String name = device.getAliasName();
     96                     if (name != null) {
     97                         DiscoveredDevices.put(device.getAliasName(), 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 
    112         public BluetoothStateReceiver() {
    113             mIsMultiBroadcast = false;
    114         }
    115 
    116         public BluetoothStateReceiver(boolean isMultiBroadcast) {
    117             mIsMultiBroadcast = isMultiBroadcast;
    118         }
    119 
    120         @Override
    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 = mBluetoothAdapter.getState();
    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
    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 
    163     public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
    164         return deviceID.equals(device.getAliasName()) || 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      */
    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      */
    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.getAliasName() + " " + bd.getAddress());
    196             if (deviceMatch(bd, deviceID)) {
    197                 Log.d("Found match " + bd.getAliasName() + " " + 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      */
    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.getAliasName() + " " + bd.getAddress());
    215                 return true;
    216             }
    217         }
    218         return false;
    219     }
    220 
    221     @Rpc(description = "Requests that the device be made connectable.")
    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.")
    228     public void bluetoothMakeDiscoverable(
    229             @RpcParameter(name = "duration",
    230                           description = "period of time, in seconds,"
    231                                       + "during which the device should be discoverable")
    232             @RpcDefault("300")
    233             Integer duration) {
    234         Log.d("Making discoverable for " + duration + " seconds.\n");
    235         mBluetoothAdapter.setScanMode(
    236                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration);
    237     }
    238 
    239     @Rpc(description = "Requests that the device be not discoverable.")
    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")
    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")
    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")
    272     public String bluetoothGetLocalName() {
    273         return mBluetoothAdapter.getName();
    274     }
    275 
    276     @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
    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. ")
    284     public String bluetoothGetLocalAddress() {
    285         return mBluetoothAdapter.getAddress();
    286     }
    287 
    288     @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
    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.")
    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.")
    315     public Set<BluetoothDevice> bluetoothGetBondedDevices() {
    316         return mBluetoothAdapter.getBondedDevices();
    317     }
    318 
    319     @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
    320     public Boolean bluetoothCheckState() {
    321         return mBluetoothAdapter.isEnabled();
    322     }
    323 
    324     @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.")
    325     public boolean bluetoothFactoryReset() {
    326         return mBluetoothAdapter.factoryReset();
    327     }
    328 
    329     @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
    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")
    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")
    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.")
    372     public Boolean bluetoothIsDiscovering() {
    373         return mBluetoothAdapter.isDiscovering();
    374     }
    375 
    376     @Rpc(description = "Get all the discovered bluetooth devices.")
    377     public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
    378         while (bluetoothIsDiscovering())
    379             ;
    380         return DiscoveredDevices.values();
    381     }
    382 
    383     @Rpc(description = "Get Bluetooth controller activity energy info.")
    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.")
    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")
    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")
    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")
    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")
    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")
    449     public int bluetoothGetLeMaximumAdvertisingDataLength() {
    450         return mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
    451     }
    452 
    453     @Rpc(description = "Gets the current state of LE.")
    454     public int bluetoothGetLeState() {
    455         return mBluetoothAdapter.getLeState();
    456     }
    457 
    458     @Rpc(description = "Enables BLE functionalities.")
    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.")
    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.")
    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.")
    480     public boolean bluetoothStopListeningForBleStateChange() {
    481         mService.unregisterReceiver(mBleStateReceiver);
    482         return true;
    483     }
    484 
    485     @Rpc(description = "Listen for Bluetooth State Changes.")
    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.")
    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
    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