blob: 798c32397eb8a473385959beebe694e46e5f9acf [file] [log] [blame]
/*
* Copyright (C) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.googlecode.android_scripting.facade.bluetooth;
import android.app.Service;
import android.bluetooth.BluetoothActivityEnergyInfo;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.ParcelUuid;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.MainThread;
import com.googlecode.android_scripting.facade.EventFacade;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcDefault;
import com.googlecode.android_scripting.rpc.RpcMinSdk;
import com.googlecode.android_scripting.rpc.RpcOptional;
import com.googlecode.android_scripting.rpc.RpcParameter;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
* Basic Bluetooth functions.
*/
@RpcMinSdk(5)
public class BluetoothFacade extends RpcReceiver {
private final Service mService;
private final BroadcastReceiver mDiscoveryReceiver;
private final IntentFilter discoveryFilter;
private final EventFacade mEventFacade;
private final BluetoothStateReceiver mStateReceiver;
private final BleStateReceiver mBleStateReceiver;
private Map<String, BluetoothConnection> connections =
new HashMap<String, BluetoothConnection>();
private BluetoothAdapter mBluetoothAdapter;
public static ConcurrentHashMap<String, BluetoothDevice> DiscoveredDevices;
public BluetoothFacade(FacadeManager manager) {
super(manager);
mBluetoothAdapter = MainThread.run(manager.getService(), new Callable<BluetoothAdapter>() {
@Override
public BluetoothAdapter call() throws Exception {
return BluetoothAdapter.getDefaultAdapter();
}
});
mEventFacade = manager.getReceiver(EventFacade.class);
mService = manager.getService();
DiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>();
discoveryFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
discoveryFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
mDiscoveryReceiver = new DiscoveryCacheReceiver();
mStateReceiver = new BluetoothStateReceiver();
mBleStateReceiver = new BleStateReceiver();
}
class DiscoveryCacheReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothDevice.ACTION_FOUND)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Log.d("Found device " + device.getAliasName());
if (!DiscoveredDevices.containsKey(device.getAddress())) {
String name = device.getAliasName();
if (name != null) {
DiscoveredDevices.put(device.getAliasName(), device);
}
DiscoveredDevices.put(device.getAddress(), device);
}
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
mEventFacade.postEvent("BluetoothDiscoveryFinished", new Bundle());
mService.unregisterReceiver(mDiscoveryReceiver);
}
}
}
class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// TODO: Keep track of the separate states be a separate method.
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = mBluetoothAdapter.getState();
Bundle msg = new Bundle();
if (state == BluetoothAdapter.STATE_ON) {
msg.putString("State", "ON");
mEventFacade.postEvent("BluetoothStateChangedOn", msg);
mService.unregisterReceiver(mStateReceiver);
} else if(state == BluetoothAdapter.STATE_OFF) {
msg.putString("State", "OFF");
mEventFacade.postEvent("BluetoothStateChangedOff", msg);
mService.unregisterReceiver(mStateReceiver);
}
msg.clear();
}
}
}
class BleStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) {
int state = mBluetoothAdapter.getLeState();
if (state == BluetoothAdapter.STATE_BLE_ON) {
mEventFacade.postEvent("BleStateChangedOn", new Bundle());
mService.unregisterReceiver(mBleStateReceiver);
} else if (state == BluetoothAdapter.STATE_OFF) {
mEventFacade.postEvent("BleStateChangedOff", new Bundle());
mService.unregisterReceiver(mBleStateReceiver);
}
}
}
}
public static boolean deviceMatch(BluetoothDevice device, String deviceID) {
return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress());
}
public static <T> BluetoothDevice getDevice(ConcurrentHashMap<String, T> devices, String device)
throws Exception {
if (devices.containsKey(device)) {
return (BluetoothDevice) devices.get(device);
} else {
throw new Exception("Can't find device " + device);
}
}
public static BluetoothDevice getDevice(Collection<BluetoothDevice> devices, String deviceID)
throws Exception {
Log.d("Looking for " + deviceID);
for (BluetoothDevice bd : devices) {
Log.d(bd.getAliasName() + " " + bd.getAddress());
if (deviceMatch(bd, deviceID)) {
Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress());
return bd;
}
}
throw new Exception("Can't find device " + deviceID);
}
public static boolean deviceExists(Collection<BluetoothDevice> devices, String deviceID) {
for (BluetoothDevice bd : devices) {
if (deviceMatch(bd, deviceID)) {
Log.d("Found match " + bd.getAliasName() + " " + bd.getAddress());
return true;
}
}
return false;
}
@Rpc(description = "Requests that the device be made connectable.")
public void bluetoothMakeConnectable() {
mBluetoothAdapter
.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
}
@Rpc(description = "Requests that the device be discoverable for Bluetooth connections.")
public void bluetoothMakeDiscoverable(
@RpcParameter(name = "duration",
description = "period of time, in seconds,"
+ "during which the device should be discoverable")
@RpcDefault("300")
Integer duration) {
Log.d("Making discoverable for " + duration + " seconds.\n");
mBluetoothAdapter
.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration);
}
@Rpc(description = "Requests that the device be not discoverable.")
public void bluetoothMakeUndiscoverable() {
Log.d("Making undiscoverable\n");
mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
}
@Rpc(description = "Queries a remote device for it's name or null if it can't be resolved")
public String bluetoothGetRemoteDeviceName(
@RpcParameter(name = "address", description = "Bluetooth Address For Target Device")
String address) {
try {
BluetoothDevice mDevice;
mDevice = mBluetoothAdapter.getRemoteDevice(address);
return mDevice.getName();
} catch (Exception e) {
return null;
}
}
@Rpc(description = "Get local Bluetooth device name")
public String bluetoothGetLocalName() {
return mBluetoothAdapter.getName();
}
@Rpc(description = "Sets the Bluetooth visible device name", returns = "true on success")
public boolean bluetoothSetLocalName(
@RpcParameter(name = "name", description = "New local name")
String name) {
return mBluetoothAdapter.setName(name);
}
@Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ")
public String bluetoothGetLocalAddress() {
return mBluetoothAdapter.getAddress();
}
@Rpc(description = "Returns the UUIDs supported by local Bluetooth adapter.")
public ParcelUuid[] bluetoothGetLocalUuids() {
return mBluetoothAdapter.getUuids();
}
@Rpc(description = "Gets the scan mode for the local dongle.\r\n" + "Return values:\r\n"
+ "\t-1 when Bluetooth is disabled.\r\n"
+ "\t0 if non discoverable and non connectable.\r\n"
+ "\r1 connectable non discoverable." + "\r3 connectable and discoverable.")
public int bluetoothGetScanMode() {
if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF
|| mBluetoothAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF) {
return -1;
}
switch (mBluetoothAdapter.getScanMode()) {
case BluetoothAdapter.SCAN_MODE_NONE:
return 0;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
return 1;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
return 3;
default:
return mBluetoothAdapter.getScanMode() - 20;
}
}
@Rpc(description = "Return the set of BluetoothDevice that are paired to the local adapter.")
public Set<BluetoothDevice> bluetoothGetBondedDevices() {
return mBluetoothAdapter.getBondedDevices();
}
@Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.")
public Boolean bluetoothCheckState() {
return mBluetoothAdapter.isEnabled();
}
@Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.")
public Boolean bluetoothToggleState(@RpcParameter(name = "enabled")
@RpcOptional
Boolean enabled,
@RpcParameter(name = "prompt",
description = "Prompt the user to confirm changing the Bluetooth state.")
@RpcDefault("false")
Boolean prompt) {
mService.registerReceiver(mStateReceiver,
new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
if (enabled == null) {
enabled = !bluetoothCheckState();
}
if (enabled) {
// TODO(damonkohler): Make this synchronous as well.
mBluetoothAdapter.enable();
} else {
// TODO(damonkohler): Add support for prompting on disable.
// TODO(damonkohler): Make this synchronous as well.
shutdown();
mBluetoothAdapter.disable();
}
return enabled;
}
@Rpc(description = "Start the remote device discovery process. ",
returns = "true on success, false on error")
public Boolean bluetoothStartDiscovery() {
DiscoveredDevices.clear();
mService.registerReceiver(mDiscoveryReceiver, discoveryFilter);
return mBluetoothAdapter.startDiscovery();
}
@Rpc(description = "Cancel the current device discovery process.",
returns = "true on success, false on error")
public Boolean bluetoothCancelDiscovery() {
//TODO (tturney): Figure out why bluetoothStartDiscovery sometimes
//doesn't register the reiever.
try {
mService.unregisterReceiver(mDiscoveryReceiver);
} catch (IllegalArgumentException e) {
Log.d("IllegalArgumentExeption found when trying to unregister reciever");
}
return mBluetoothAdapter.cancelDiscovery();
}
@Rpc(description = "If the local Bluetooth adapter is currently"
+ "in the device discovery process.")
public Boolean bluetoothIsDiscovering() {
return mBluetoothAdapter.isDiscovering();
}
@Rpc(description = "Get all the discovered bluetooth devices.")
public Collection<BluetoothDevice> bluetoothGetDiscoveredDevices() {
while (bluetoothIsDiscovering())
;
return DiscoveredDevices.values();
}
@Rpc(description = "Enable or disable the Bluetooth HCI snoop log")
public boolean bluetoothConfigHciSnoopLog(
@RpcParameter(name = "value", description = "enable or disable log")
Boolean value
) {
return mBluetoothAdapter.configHciSnoopLog(value);
}
@Rpc(description = "Get Bluetooth controller activity energy info.")
public String bluetoothGetControllerActivityEnergyInfo(
@RpcParameter(name = "value")
Integer value
) {
BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter
.getControllerActivityEnergyInfo(value);
//TODO (tturney): Fix when method dosn't return null... cosmic issue.
while (energyInfo == null) {
energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value);
}
return energyInfo.toString();
}
@Rpc(description = "Return true if hardware has entries" +
"available for matching beacons.")
public boolean bluetoothIsHardwareTrackingFiltersAvailable() {
return mBluetoothAdapter.isHardwareTrackingFiltersAvailable();
}
@Rpc(description = "Gets the current state of LE.")
public int bluetoothGetLeState() {
return mBluetoothAdapter.getLeState();
}
@Rpc(description = "Enables BLE functionalities.")
public boolean bluetoothEnableBLE() {
mService.registerReceiver(mBleStateReceiver,
new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
return mBluetoothAdapter.enableBLE();
}
@Rpc(description = "Disables BLE functionalities.")
public boolean bluetoothDisableBLE() {
mService.registerReceiver(mBleStateReceiver,
new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED));
return mBluetoothAdapter.disableBLE();
}
@Override
public void shutdown() {
for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
entry.getValue().stop();
}
connections.clear();
}
}