| /* |
| * Copyright (C) 2010 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; |
| |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothServerSocket; |
| import android.bluetooth.BluetoothSocket; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| |
| import com.googlecode.android_scripting.Log; |
| import com.googlecode.android_scripting.MainThread; |
| 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.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.UUID; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import org.apache.commons.codec.binary.Base64Codec; |
| |
| /** |
| * Bluetooth functions. |
| * |
| */ |
| // Discovery functions added by Eden Sayag |
| |
| |
| |
| @RpcMinSdk(5) |
| public class BluetoothFacade extends RpcReceiver { |
| |
| // UUID for SL4A. |
| private static final String DEFAULT_UUID = "457807c0-4897-11df-9879-0800200c9a66"; |
| private static final String SDP_NAME = "SL4A"; |
| |
| private final Service mService; |
| private Map<String, BluetoothConnection> |
| connections = new HashMap<String, BluetoothConnection>(); |
| private AndroidFacade mAndroidFacade; |
| private BluetoothAdapter mBluetoothAdapter; |
| private final ConcurrentHashMap<String, BluetoothDevice> mDiscoveredDevices; |
| private final BroadcastReceiver mReceiver; |
| |
| public static class BluetoothPairingReceiver extends BroadcastReceiver { |
| public BluetoothPairingReceiver() { |
| super(); |
| } |
| /** |
| * Blindly confirm passkey |
| */ |
| @Override |
| public void onReceive(Context c, Intent intent) { |
| String action = intent.getAction(); |
| int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); |
| Log.d("Bluetooth pairing intent received: " + action + " with type " + type); |
| BluetoothDevice mBtDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| if(action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { |
| Log.d("Processing Action Paring Request."); |
| if(type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) { |
| Log.d("Confirming connection"); |
| mBtDevice.setPairingConfirmation(true); |
| abortBroadcast(); // Abort the broadcast so Settings app doesn't get it. |
| } |
| } |
| } |
| } |
| |
| public BluetoothFacade(FacadeManager manager) { |
| super(manager); |
| mAndroidFacade = manager.getReceiver(AndroidFacade.class); |
| mBluetoothAdapter = MainThread.run(manager.getService(), new Callable<BluetoothAdapter>() { |
| @Override |
| public BluetoothAdapter call() throws Exception { |
| return BluetoothAdapter.getDefaultAdapter(); |
| } |
| }); |
| mDiscoveredDevices = new ConcurrentHashMap<String, BluetoothDevice>(); |
| mService = manager.getService(); |
| mReceiver = new BroadcastReceiver() { |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| // When discovery finds a device |
| if (BluetoothDevice.ACTION_FOUND.equals(action)) { |
| // Get the BluetoothDevice object from the Intent |
| BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| if (mDiscoveredDevices.containsKey(device.getAddress())) { |
| mDiscoveredDevices.put(device.getAddress(), device); |
| } |
| } |
| } |
| }; |
| } |
| |
| @Rpc(description = "Returns active Bluetooth connections.") |
| public Map<String, String> bluetoothActiveConnections() { |
| Map<String, String> out = new HashMap<String, String>(); |
| for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) { |
| if (entry.getValue().isConnected()) { |
| out.put(entry.getKey(), entry.getValue().getRemoteBluetoothAddress()); |
| } |
| } |
| return out; |
| } |
| |
| private BluetoothConnection getConnection(String connID) throws IOException { |
| BluetoothConnection conn = null; |
| if (connID.trim().length() > 0) { |
| conn = connections.get(connID); |
| } else if (connections.size() == 1) { |
| conn = (BluetoothConnection) connections.values().toArray()[0]; |
| } |
| if (conn == null) { |
| throw new IOException("Bluetooth not ready for this connID."); |
| } |
| return conn; |
| } |
| |
| @Rpc(description = "Send bytes over the currently open Bluetooth connection.") |
| public void bluetoothWriteBinary( |
| @RpcParameter(name = "base64", |
| description = "A base64 encoded String of the bytes to be sent.") |
| String base64, |
| @RpcParameter(name = "connID", description = "Connection id") |
| @RpcDefault("") @RpcOptional |
| String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| conn.write(Base64Codec.decodeBase64(base64)); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| @Rpc(description = "Read up to bufferSize bytes and return a chunked, base64 encoded string.") |
| public String bluetoothReadBinary( |
| @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize, |
| @RpcParameter(name = "connID", description = "Connection id") |
| @RpcDefault("") @RpcOptional |
| String connID) |
| throws IOException { |
| |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| return Base64Codec.encodeBase64String(conn.readBinary(bufferSize)); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| private String addConnection(BluetoothConnection conn) { |
| String uuid = UUID.randomUUID().toString(); |
| connections.put(uuid, conn); |
| conn.setUUID(uuid); |
| return uuid; |
| } |
| |
| @Rpc(description = "Connect to a device over Bluetooth. " |
| + "Blocks until the connection is established or fails.", |
| returns = "True if the connection was established successfully.") |
| public String bluetoothConnect( |
| @RpcParameter(name = "address", description = "The mac address of the device to connect to.") |
| String address, |
| @RpcParameter(name = "uuid", |
| description = "The UUID passed here must match the UUID used by the server device.") |
| @RpcDefault(DEFAULT_UUID) |
| String uuid) |
| throws IOException { |
| BluetoothDevice mDevice; |
| BluetoothSocket mSocket; |
| BluetoothConnection conn; |
| mDevice = mBluetoothAdapter.getRemoteDevice(address); |
| mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(uuid)); |
| |
| // Register a broadcast receiver to bypass manual confirmation |
| BluetoothPairingReceiver mBtReceiver = new BluetoothPairingReceiver(); |
| IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| mService.registerReceiver(mBtReceiver, filter); |
| |
| // Always cancel discovery because it will slow down a connection. |
| mBluetoothAdapter.cancelDiscovery(); |
| mSocket.connect(); |
| conn = new BluetoothConnection(mSocket); |
| |
| mService.unregisterReceiver(mBtReceiver); |
| return addConnection(conn); |
| } |
| |
| @Rpc(description = "Listens for and accepts a Bluetooth connection." |
| + "Blocks until the connection is established or fails.") |
| public String bluetoothAccept( |
| @RpcParameter(name = "uuid") @RpcDefault(DEFAULT_UUID) String uuid, |
| @RpcParameter(name = "timeout", |
| description = "How long to wait for a new connection, 0 is wait for ever") |
| @RpcDefault("0") Integer timeout) |
| throws IOException { |
| Log.d("Accept bluetooth connection"); |
| BluetoothServerSocket mServerSocket; |
| mServerSocket = |
| mBluetoothAdapter.listenUsingRfcommWithServiceRecord(SDP_NAME, UUID.fromString(uuid)); |
| // Register a broadcast receiver to bypass manual confirmation |
| BluetoothPairingReceiver mBtReceiver = new BluetoothPairingReceiver(); |
| IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| mService.registerReceiver(mBtReceiver, filter); |
| |
| BluetoothSocket mSocket = mServerSocket.accept(timeout.intValue()); |
| BluetoothConnection conn = new BluetoothConnection(mSocket, mServerSocket); |
| mService.unregisterReceiver(mBtReceiver); |
| return addConnection(conn); |
| } |
| |
| @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 = "Sends ASCII characters over the currently open Bluetooth connection.") |
| public void bluetoothWrite(@RpcParameter(name = "ascii") String ascii, |
| @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| conn.write(ascii); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| @Rpc(description = "Returns True if the next read is guaranteed not to block.") |
| public Boolean bluetoothReadReady( |
| @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional |
| String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| return conn.readReady(); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| @Rpc(description = "Read up to bufferSize ASCII characters.") |
| public String bluetoothRead( |
| @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize, |
| @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("") |
| String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| return conn.read(bufferSize); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| @Rpc(description = "Read the next line.") |
| public String bluetoothReadLine( |
| @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("") |
| String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| try { |
| return conn.readLine(); |
| } catch (IOException e) { |
| connections.remove(conn.getUUID()); |
| throw e; |
| } |
| } |
| |
| @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 = "Gets the Bluetooth Visible 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 = "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 = "Returns the name of the connected device.") |
| public String bluetoothGetConnectedDeviceName( |
| @RpcParameter(name = "connID", description = "Connection id") |
| @RpcOptional @RpcDefault("") |
| String connID) |
| throws IOException { |
| BluetoothConnection conn = getConnection(connID); |
| return conn.getConnectedDeviceName(); |
| } |
| |
| @Rpc(description = "Checks Bluetooth state.", returns = "True if Bluetooth is enabled.") |
| public Boolean checkBluetoothState() { |
| return mBluetoothAdapter.isEnabled(); |
| } |
| |
| @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.") |
| public Boolean toggleBluetoothState( |
| @RpcParameter(name = "enabled") @RpcOptional |
| Boolean enabled, |
| @RpcParameter(name = "prompt", |
| description = "Prompt the user to confirm changing the Bluetooth state.") |
| @RpcDefault("false") |
| Boolean prompt |
| ) { |
| if (enabled == null) { |
| enabled = !checkBluetoothState(); |
| } |
| if (enabled) { |
| if (prompt) { |
| Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); |
| // TODO(damonkohler): Use the result to determine if this was successful. At any rate, keep |
| // using startActivityForResult in order to synchronize this call. |
| mAndroidFacade.startActivityForResult(intent); |
| } else { |
| // 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 = "Stops Bluetooth connection.") |
| public void bluetoothStop( |
| @RpcParameter |
| (name = "connID", description = "Connection id") @RpcOptional @RpcDefault("") |
| String connID) { |
| BluetoothConnection conn; |
| try { |
| conn = getConnection(connID); |
| } catch (IOException e) { |
| // TODO Auto-generated catch block |
| e.printStackTrace(); |
| return; |
| } |
| if (conn == null) { |
| return; |
| } |
| |
| conn.stop(); |
| connections.remove(conn.getUUID()); |
| } |
| |
| @Rpc(description = "Returns the hardware address of the local Bluetooth adapter. ") |
| public String bluetoothGetLocalAddress() { |
| return mBluetoothAdapter.getAddress(); |
| } |
| |
| @Rpc(description = "Start the remote device discovery process. ", |
| returns = "true on success, false on error") |
| public Boolean bluetoothDiscoveryStart() { |
| mService.registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)); |
| return mBluetoothAdapter.startDiscovery(); |
| } |
| |
| @Rpc(description = "Cancel the current device discovery process.", |
| returns = "true on success, false on error") |
| public Boolean bluetoothDiscoveryCancel() { |
| mService.unregisterReceiver(mReceiver); |
| return mBluetoothAdapter.cancelDiscovery(); |
| } |
| |
| @Rpc(description = |
| "If the local Bluetooth adapter is currently in the device discovery process.") |
| public Boolean bluetoothIsDiscovering() { |
| return mBluetoothAdapter.isDiscovering(); |
| } |
| |
| @Override |
| public void shutdown() { |
| for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) { |
| entry.getValue().stop(); |
| } |
| connections.clear(); |
| } |
| } |
| |
| class BluetoothConnection { |
| private BluetoothSocket mSocket; |
| private BluetoothDevice mDevice; |
| private OutputStream mOutputStream; |
| private InputStream mInputStream; |
| private BufferedReader mReader; |
| private BluetoothServerSocket mServerSocket; |
| private String UUID; |
| |
| public BluetoothConnection(BluetoothSocket mSocket) throws IOException { |
| this(mSocket, null); |
| } |
| |
| public BluetoothConnection(BluetoothSocket mSocket, BluetoothServerSocket mServerSocket) |
| throws IOException { |
| this.mSocket = mSocket; |
| mOutputStream = mSocket.getOutputStream(); |
| mInputStream = mSocket.getInputStream(); |
| mDevice = mSocket.getRemoteDevice(); |
| mReader = new BufferedReader(new InputStreamReader(mInputStream, "ASCII")); |
| this.mServerSocket = mServerSocket; |
| } |
| |
| public void setUUID(String UUID) { |
| this.UUID = UUID; |
| } |
| |
| public String getUUID() { |
| return UUID; |
| } |
| |
| public String getRemoteBluetoothAddress() { |
| return mDevice.getAddress(); |
| } |
| |
| public boolean isConnected() { |
| if (mSocket == null) { |
| return false; |
| } |
| try { |
| mSocket.getRemoteDevice(); |
| mInputStream.available(); |
| mReader.ready(); |
| return true; |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| |
| public void write(byte[] out) throws IOException { |
| if (mOutputStream != null) { |
| mOutputStream.write(out); |
| } else { |
| throw new IOException("Bluetooth not ready."); |
| } |
| } |
| |
| public void write(String out) throws IOException { |
| this.write(out.getBytes()); |
| } |
| |
| public Boolean readReady() throws IOException { |
| if (mReader != null) { |
| return mReader.ready(); |
| } |
| throw new IOException("Bluetooth not ready."); |
| } |
| |
| public byte[] readBinary() throws IOException { |
| return this.readBinary(4096); |
| } |
| |
| public byte[] readBinary(int bufferSize) throws IOException { |
| if (mReader != null) { |
| byte[] buffer = new byte[bufferSize]; |
| int bytesRead = mInputStream.read(buffer); |
| if (bytesRead == -1) { |
| Log.e("Read failed."); |
| throw new IOException("Read failed."); |
| } |
| byte[] truncatedBuffer = new byte[bytesRead]; |
| System.arraycopy(buffer, 0, truncatedBuffer, 0, bytesRead); |
| return truncatedBuffer; |
| } |
| |
| throw new IOException("Bluetooth not ready."); |
| |
| } |
| |
| public String read() throws IOException { |
| return this.read(4096); |
| } |
| |
| public String read(int bufferSize) throws IOException { |
| if (mReader != null) { |
| char[] buffer = new char[bufferSize]; |
| int bytesRead = mReader.read(buffer); |
| if (bytesRead == -1) { |
| Log.e("Read failed."); |
| throw new IOException("Read failed."); |
| } |
| return new String(buffer, 0, bytesRead); |
| } |
| throw new IOException("Bluetooth not ready."); |
| } |
| |
| public String readLine() throws IOException { |
| if (mReader != null) { |
| return mReader.readLine(); |
| } |
| throw new IOException("Bluetooth not ready."); |
| } |
| |
| public String getConnectedDeviceName() { |
| return mDevice.getName(); |
| } |
| |
| public void stop() { |
| if (mSocket != null) { |
| try { |
| mSocket.close(); |
| } catch (IOException e) { |
| Log.e(e); |
| } |
| } |
| mSocket = null; |
| if (mServerSocket != null) { |
| try { |
| mServerSocket.close(); |
| } catch (IOException e) { |
| Log.e(e); |
| } |
| } |
| mServerSocket = null; |
| |
| if (mInputStream != null) { |
| try { |
| mInputStream.close(); |
| } catch (IOException e) { |
| Log.e(e); |
| } |
| } |
| mInputStream = null; |
| if (mOutputStream != null) { |
| try { |
| mOutputStream.close(); |
| } catch (IOException e) { |
| Log.e(e); |
| } |
| } |
| mOutputStream = null; |
| if (mReader != null) { |
| try { |
| mReader.close(); |
| } catch (IOException e) { |
| Log.e(e); |
| } |
| } |
| mReader = null; |
| } |
| } |