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 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 
     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
     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
    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
    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 
    144     public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
    145         return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress());
    146     }
    147 
    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 
    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 
    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.")
    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.")
    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.")
    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")
    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")
    218     public String bluetoothGetLocalName() {
    219         return mBluetoothAdapter.getName();
    220     }
    221 
    222     @Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
    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. ")
    230     public String bluetoothGetLocalAddress() {
    231         return mBluetoothAdapter.getAddress();
    232     }
    233 
    234     @Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
    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.")
    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.")
    261     public Set<BluetoothDevice> bluetoothGetBondedDevices() {
    262         return mBluetoothAdapter.getBondedDevices();
    263     }
    264 
    265     @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
    266     public Boolean bluetoothCheckState() {
    267         return mBluetoothAdapter.isEnabled();
    268     }
    269 
    270     @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
    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")
    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")
    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.")
    314     public Boolean bluetoothIsDiscovering() {
    315         return mBluetoothAdapter.isDiscovering();
    316     }
    317 
    318     @Rpc(description = "Get all the discovered bluetooth devices.")
    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")
    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.")
    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.")
    348     public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
    349         return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
    350     }
    351 
    352     @Rpc(description = "Gets the current state of LE.")
    353     public int bluetoothGetLeState() {
    354         return mBluetoothAdapter.getLeState();
    355     }
    356 
    357     @Rpc(description = "Enables BLE functionalities.")
    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.")
    365     public boolean bluetoothDisableBLE() {
    366         mService.registerReceiver(mBleStateReceiver,
    367             new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
    368         return mBluetoothAdapter.disableBLE();
    369     }
    370 
    371     @Override
    372     public void shutdown() {
    373         for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
    374             entry.getValue().stop();
    375         }
    376         connections.clear();
    377     }
    378 }
    379