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(), 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 = "Fetch UUIDS with SDP")
    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")
    244     public String bluetoothGetLocalName() {
    245         return mBluetoothAdapter.getName();
    246     }
    247 
    248     @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
    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. ")
    256     public String bluetoothGetLocalAddress() {
    257         return mBluetoothAdapter.getAddress();
    258     }
    259 
    260     @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
    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.")
    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.")
    287     public Set<BluetoothDevice> bluetoothGetBondedDevices() {
    288         return mBluetoothAdapter.getBondedDevices();
    289     }
    290 
    291     @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
    292     public Boolean bluetoothCheckState() {
    293         return mBluetoothAdapter.isEnabled();
    294     }
    295 
    296     @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.")
    297     public boolean bluetoothFactoryReset() {
    298         return mBluetoothAdapter.factoryReset();
    299     }
    300 
    301     @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
    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")
    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")
    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.")
    344     public Boolean bluetoothIsDiscovering() {
    345         return mBluetoothAdapter.isDiscovering();
    346     }
    347 
    348     @Rpc(description = "Get all the discovered bluetooth devices.")
    349     public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
    350         while (bluetoothIsDiscovering())
    351             ;
    352         return DiscoveredDevices.values();
    353     }
    354 
    355     @Rpc(description = "Get Bluetooth controller activity energy info.")
    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.")
    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")
    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")
    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")
    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")
    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")
    421     public int bluetoothGetLeMaximumAdvertisingDataLength() {
    422         return mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
    423     }
    424 
    425     @Rpc(description = "Gets the current state of LE.")
    426     public int bluetoothGetLeState() {
    427         return mBluetoothAdapter.getLeState();
    428     }
    429 
    430     @Rpc(description = "Enables BLE functionalities.")
    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.")
    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.")
    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.")
    452     public boolean bluetoothStopListeningForBleStateChange() {
    453         mService.unregisterReceiver(mBleStateReceiver);
    454         return true;
    455     }
    456 
    457     @Rpc(description = "Listen for Bluetooth State Changes.")
    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.")
    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
    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