• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothServerSocket;
23 import android.bluetooth.BluetoothSocket;
24 import android.os.ParcelFileDescriptor;
25 
26 import com.googlecode.android_scripting.Log;
27 import com.googlecode.android_scripting.facade.EventFacade;
28 import com.googlecode.android_scripting.facade.FacadeManager;
29 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
30 import com.googlecode.android_scripting.rpc.Rpc;
31 import com.googlecode.android_scripting.rpc.RpcDefault;
32 import com.googlecode.android_scripting.rpc.RpcOptional;
33 import com.googlecode.android_scripting.rpc.RpcParameter;
34 
35 import java.io.BufferedReader;
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.InputStreamReader;
39 import java.io.OutputStream;
40 import java.util.HashMap;
41 import java.util.Map;
42 import java.util.UUID;
43 import java.lang.reflect.Field;
44 import java.lang.Thread;
45 import java.lang.reflect.*;
46 
47 import org.apache.commons.codec.binary.Base64Codec;
48 
49 /**
50  * Bluetooth functions.
51  *
52  */
53 // Discovery functions added by Eden Sayag
54 
55 public class BluetoothRfcommFacade extends RpcReceiver {
56 
57   // UUID for SL4A.
58   private static final String DEFAULT_UUID = "457807c0-4897-11df-9879-0800200c9a66";
59   private static final String SDP_NAME = "SL4A";
60   private final Service mService;
61   private final BluetoothAdapter mBluetoothAdapter;
62   private Map<String, BluetoothConnection>
63           connections = new HashMap<String, BluetoothConnection>();
64   private final EventFacade mEventFacade;
65   private ConnectThread mConnectThread;
66   private AcceptThread mAcceptThread;
67 
BluetoothRfcommFacade(FacadeManager manager)68   public BluetoothRfcommFacade(FacadeManager manager) {
69     super(manager);
70     mEventFacade = manager.getReceiver(EventFacade.class);
71     mService = manager.getService();
72     mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
73   }
74 
getConnection(String connID)75   private BluetoothConnection getConnection(String connID) throws IOException {
76     BluetoothConnection conn = null;
77     if (connID.trim().length() > 0) {
78       conn = connections.get(connID);
79     } else if (connections.size() == 1) {
80       conn = (BluetoothConnection) connections.values().toArray()[0];
81     }
82     if (conn == null) {
83       throw new IOException("Bluetooth connection not established.");
84     }
85     return conn;
86   }
87 
addConnection(BluetoothConnection conn)88   private String addConnection(BluetoothConnection conn) {
89     String uuid = UUID.randomUUID().toString();
90     connections.put(uuid, conn);
91     conn.setUUID(uuid);
92     return uuid;
93   }
94 
95   /**
96      * Create L2CAP socket to Bluetooth device
97      *
98      * @param address the bluetooth address to open the socket on
99      * @param channel the channel to open the socket on
100      * @throws Exception
101      */
102     @Rpc(description = "Create L2CAP socket to Bluetooth deice")
rfcommCreateL2capSocket( @pcParametername = "address") String address, @RpcParameter(name = "channel") Integer channel)103     public void rfcommCreateL2capSocket(
104             @RpcParameter(name = "address") String address,
105             @RpcParameter(name = "channel") Integer channel) throws Exception {
106         BluetoothDevice mDevice;
107         mDevice = mBluetoothAdapter.getRemoteDevice(address);
108         Class bluetoothDeviceClass = Class.forName("android.bluetooth.BluetoothDevice");
109         Method createL2capSocketMethod = bluetoothDeviceClass.getMethod(
110             "createL2capSocket", int.class);
111         createL2capSocketMethod.invoke(mDevice, channel);
112     }
113 
114     /**
115      * Create RFCOMM socket to Bluetooth device
116      *
117      * @param address the bluetooth address to open the socket on
118      * @param channel the channel to open the socket on
119      * @throws Exception
120      */
121     @Rpc(description = "Create RFCOMM socket to Bluetooth deice")
rfcommCreateRfcommSocket( @pcParametername = "address") String address, @RpcParameter(name = "channel") Integer channel)122     public void rfcommCreateRfcommSocket(
123             @RpcParameter(name = "address") String address,
124             @RpcParameter(name = "channel") Integer channel) throws Exception {
125         BluetoothDevice mDevice;
126         mDevice = mBluetoothAdapter.getRemoteDevice(address);
127         Class bluetoothDeviceClass = Class.forName("android.bluetooth.BluetoothDevice");
128         Method createL2capSocketMethod = bluetoothDeviceClass.getMethod(
129             "createL2capSocket", int.class);
130         createL2capSocketMethod.invoke(mDevice, channel);
131     }
132 
133   @Rpc(description = "Begins a thread initiate an Rfcomm connection over Bluetooth. ")
bluetoothRfcommBeginConnectThread( @pcParametername = "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)134   public void bluetoothRfcommBeginConnectThread(
135       @RpcParameter(name = "address", description = "The mac address of the device to connect to.")
136       String address,
137       @RpcParameter(name = "uuid",
138       description = "The UUID passed here must match the UUID used by the server device.")
139       @RpcDefault(DEFAULT_UUID)
140       String uuid)
141       throws IOException {
142     BluetoothDevice mDevice;
143     mDevice = mBluetoothAdapter.getRemoteDevice(address);
144     ConnectThread connectThread = new ConnectThread(mDevice, uuid);
145     connectThread.start();
146     mConnectThread = connectThread;
147   }
148 
149   @Rpc(description = "Kill thread")
bluetoothRfcommKillConnThread()150   public void bluetoothRfcommKillConnThread() {
151     try {
152       mConnectThread.cancel();
153       mConnectThread.join(5000);
154     } catch (InterruptedException e) {
155       Log.e("Interrupted Exception: " + e.toString());
156     }
157   }
158 
159   /**
160    * Closes an active Rfcomm Client socket
161    */
162   @Rpc(description = "Close an active Rfcomm Client socket")
bluetoothRfcommEndConnectThread()163   public void bluetoothRfcommEndConnectThread()
164     throws IOException {
165     mConnectThread.cancel();
166   }
167 
168   /**
169    * Closes an active Rfcomm Server socket
170    */
171   @Rpc(description = "Close an active Rfcomm Server socket")
bluetoothRfcommEndAcceptThread()172   public void bluetoothRfcommEndAcceptThread()
173     throws IOException {
174     mAcceptThread.cancel();
175   }
176 
177   @Rpc(description = "Returns active Bluetooth connections.")
bluetoothRfcommActiveConnections()178   public Map<String, String> bluetoothRfcommActiveConnections() {
179     Map<String, String> out = new HashMap<String, String>();
180     for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
181       if (entry.getValue().isConnected()) {
182         out.put(entry.getKey(), entry.getValue().getRemoteBluetoothAddress());
183       }
184     }
185     return out;
186   }
187 
188   @Rpc(description = "Returns the name of the connected device.")
bluetoothRfcommGetConnectedDeviceName( @pcParametername = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)189   public String bluetoothRfcommGetConnectedDeviceName(
190       @RpcParameter(name = "connID", description = "Connection id")
191       @RpcOptional @RpcDefault("")
192       String connID)
193       throws IOException {
194     BluetoothConnection conn = getConnection(connID);
195     return conn.getConnectedDeviceName();
196   }
197 
198   @Rpc(description = "Begins a thread to accept an Rfcomm connection over Bluetooth. ")
bluetoothRfcommBeginAcceptThread( @pcParametername = "uuid") @pcDefaultDEFAULT_UUID) String uuid, @RpcParameter(name = "timeout", description = "How long to wait for a new connection, 0 is wait for ever") @RpcDefault("0") Integer timeout)199   public void bluetoothRfcommBeginAcceptThread(
200       @RpcParameter(name = "uuid") @RpcDefault(DEFAULT_UUID) String uuid,
201       @RpcParameter(name = "timeout",
202                     description = "How long to wait for a new connection, 0 is wait for ever")
203       @RpcDefault("0") Integer timeout)
204       throws IOException {
205     Log.d("Accept bluetooth connection");
206     BluetoothServerSocket mServerSocket;
207     AcceptThread acceptThread = new AcceptThread(uuid, timeout.intValue());
208     acceptThread.start();
209     mAcceptThread = acceptThread;
210   }
211 
212   @Rpc(description = "Sends ASCII characters over the currently open Bluetooth connection.")
bluetoothRfcommWrite(@pcParametername = "ascii") String ascii, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") String connID)213   public void bluetoothRfcommWrite(@RpcParameter(name = "ascii") String ascii,
214       @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") String connID)
215       throws IOException {
216     BluetoothConnection conn = getConnection(connID);
217     try {
218       conn.write(ascii);
219     } catch (IOException e) {
220       connections.remove(conn.getUUID());
221       throw e;
222     }
223   }
224 
225   @Rpc(description = "Read up to bufferSize ASCII characters.")
bluetoothRfcommRead( @pcParametername = "bufferSize") @pcDefault"4096") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("") String connID)226   public String bluetoothRfcommRead(
227       @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize,
228       @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("")
229       String connID)
230       throws IOException {
231     BluetoothConnection conn = getConnection(connID);
232     try {
233       return conn.read(bufferSize);
234     } catch (IOException e) {
235       connections.remove(conn.getUUID());
236       throw e;
237     }
238   }
239 
240   @Rpc(description = "Send bytes over the currently open Bluetooth connection.")
bluetoothRfcommWriteBinary( @pcParametername = "base64", description = "A base64 encoded String of the bytes to be sent.") String base64, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)241   public void bluetoothRfcommWriteBinary(
242       @RpcParameter(name = "base64",
243                     description = "A base64 encoded String of the bytes to be sent.")
244       String base64,
245       @RpcParameter(name = "connID", description = "Connection id")
246       @RpcDefault("") @RpcOptional
247       String connID)
248       throws IOException {
249     BluetoothConnection conn = getConnection(connID);
250     try {
251       conn.write(Base64Codec.decodeBase64(base64));
252     } catch (IOException e) {
253       connections.remove(conn.getUUID());
254       throw e;
255     }
256   }
257 
258   @Rpc(description = "Read up to bufferSize bytes and return a chunked, base64 encoded string.")
bluetoothRfcommReadBinary( @pcParametername = "bufferSize") @pcDefault"4096") Integer bufferSize, @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional String connID)259   public String bluetoothRfcommReadBinary(
260       @RpcParameter(name = "bufferSize") @RpcDefault("4096") Integer bufferSize,
261       @RpcParameter(name = "connID", description = "Connection id")
262       @RpcDefault("") @RpcOptional
263       String connID)
264       throws IOException {
265 
266     BluetoothConnection conn = getConnection(connID);
267     try {
268       return Base64Codec.encodeBase64String(conn.readBinary(bufferSize));
269     } catch (IOException e) {
270       connections.remove(conn.getUUID());
271       throw e;
272     }
273   }
274 
275   @Rpc(description = "Returns True if the next read is guaranteed not to block.")
bluetoothRfcommReadReady( @pcParametername = "connID", description = "Connection id") @pcDefault"") @pcOptional String connID)276   public Boolean bluetoothRfcommReadReady(
277       @RpcParameter(name = "connID", description = "Connection id") @RpcDefault("") @RpcOptional
278       String connID)
279       throws IOException {
280     BluetoothConnection conn = getConnection(connID);
281     try {
282       return conn.readReady();
283     } catch (IOException e) {
284       connections.remove(conn.getUUID());
285       throw e;
286     }
287   }
288 
289   @Rpc(description = "Read the next line.")
bluetoothRfcommReadLine( @pcParametername = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)290   public String bluetoothRfcommReadLine(
291       @RpcParameter(name = "connID", description = "Connection id") @RpcOptional @RpcDefault("")
292       String connID)
293       throws IOException {
294     BluetoothConnection conn = getConnection(connID);
295     try {
296       return conn.readLine();
297     } catch (IOException e) {
298       connections.remove(conn.getUUID());
299       throw e;
300     }
301   }
302 
303   @Rpc(description = "Stops Bluetooth connection.")
bluetoothRfcommStop( @pcParameter name = "connID", description = "Connection id") @pcOptional @pcDefault"") String connID)304   public void bluetoothRfcommStop(
305       @RpcParameter
306       (name = "connID", description = "Connection id") @RpcOptional @RpcDefault("")
307       String connID) {
308     BluetoothConnection conn;
309     try {
310       conn = getConnection(connID);
311     } catch (IOException e) {
312       e.printStackTrace();
313       return;
314     }
315     if (conn == null) {
316       return;
317     }
318 
319     conn.stop();
320     connections.remove(conn.getUUID());
321 
322     if (mAcceptThread != null) {
323         mAcceptThread.cancel();
324     }
325     if (mConnectThread != null) {
326         mConnectThread.cancel();
327     }
328   }
329 
330   @Override
shutdown()331   public void shutdown() {
332     for (Map.Entry<String, BluetoothConnection> entry : connections.entrySet()) {
333       entry.getValue().stop();
334     }
335     connections.clear();
336     if (mAcceptThread != null) {
337         mAcceptThread.cancel();
338     }
339     if (mConnectThread != null) {
340         mConnectThread.cancel();
341     }
342   }
343 
344   private class ConnectThread extends Thread {
345     private final BluetoothSocket mmSocket;
346 
ConnectThread(BluetoothDevice device, String uuid)347     public ConnectThread(BluetoothDevice device, String uuid) {
348       BluetoothSocket tmp = null;
349       try {
350         tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(uuid));
351       } catch (IOException createSocketException) {
352         Log.e("Failed to create socket: " + createSocketException.toString());
353       }
354       mmSocket = tmp;
355     }
356 
run()357     public void run() {
358       mBluetoothAdapter.cancelDiscovery();
359       try {
360         BluetoothConnection conn;
361         mmSocket.connect();
362         conn = new BluetoothConnection(mmSocket);
363         Log.d("Connection Successful");
364         addConnection(conn);
365       } catch(IOException connectException) {
366         cancel();
367         return;
368       }
369     }
370 
cancel()371     public void cancel() {
372       if (mmSocket != null) {
373         try {
374           mmSocket.close();
375         } catch (IOException closeException){
376           Log.e("Failed to close socket: " + closeException.toString());
377         }
378       }
379     }
380 
getSocket()381     public BluetoothSocket getSocket() {
382       return mmSocket;
383     }
384   }
385 
386 
387   private class AcceptThread extends Thread {
388     private final BluetoothServerSocket mmServerSocket;
389     private final int mTimeout;
390     private BluetoothSocket mmSocket;
391 
AcceptThread(String uuid, int timeout)392     public AcceptThread(String uuid, int timeout) {
393       BluetoothServerSocket tmp = null;
394       mTimeout = timeout;
395       try {
396         tmp =
397             mBluetoothAdapter.listenUsingRfcommWithServiceRecord(SDP_NAME, UUID.fromString(uuid));
398       } catch (IOException createSocketException) {
399         Log.e("Failed to create socket: " + createSocketException.toString());
400       }
401       mmServerSocket = tmp;
402     }
403 
run()404     public void run() {
405       try {
406         mmSocket = mmServerSocket.accept(mTimeout);
407         BluetoothConnection conn = new BluetoothConnection(mmSocket, mmServerSocket);
408         addConnection(conn);
409       } catch(IOException connectException) {
410         Log.e("Failed to connect socket: " + connectException.toString());
411         if (mmSocket != null) {
412           cancel();
413         }
414         return;
415       }
416     }
417 
cancel()418     public void cancel() {
419       if (mmSocket != null) {
420         try {
421           mmSocket.close();
422         } catch (IOException closeException){
423           Log.e("Failed to close socket: " + closeException.toString());
424         }
425       }
426       if (mmServerSocket != null) {
427         try{
428           mmServerSocket.close();
429         } catch (IOException closeException) {
430           Log.e("Failed to close socket: " + closeException.toString());
431         }
432       }
433     }
434 
getSocket()435     public BluetoothSocket getSocket() {
436       return mmSocket;
437     }
438   }
439 
440 }
441 
442 
443 class BluetoothConnection {
444   private BluetoothSocket mSocket;
445   private BluetoothDevice mDevice;
446   private OutputStream mOutputStream;
447   private InputStream mInputStream;
448   private BufferedReader mReader;
449   private BluetoothServerSocket mServerSocket;
450   private String UUID;
451 
BluetoothConnection(BluetoothSocket mSocket)452   public BluetoothConnection(BluetoothSocket mSocket) throws IOException {
453     this(mSocket, null);
454   }
455 
BluetoothConnection(BluetoothSocket mSocket, BluetoothServerSocket mServerSocket)456   public BluetoothConnection(BluetoothSocket mSocket, BluetoothServerSocket mServerSocket)
457       throws IOException {
458     this.mSocket = mSocket;
459     mOutputStream = mSocket.getOutputStream();
460     mInputStream = mSocket.getInputStream();
461     mDevice = mSocket.getRemoteDevice();
462     mReader = new BufferedReader(new InputStreamReader(mInputStream, "ASCII"));
463     this.mServerSocket = mServerSocket;
464   }
465 
setUUID(String UUID)466   public void setUUID(String UUID) {
467     this.UUID = UUID;
468   }
469 
getUUID()470   public String getUUID() {
471     return UUID;
472   }
473 
getRemoteBluetoothAddress()474   public String getRemoteBluetoothAddress() {
475     return mDevice.getAddress();
476   }
477 
isConnected()478   public boolean isConnected() {
479     if (mSocket == null) {
480       return false;
481     }
482     try {
483       mSocket.getRemoteDevice();
484       mInputStream.available();
485       mReader.ready();
486       return true;
487     } catch (Exception e) {
488       return false;
489     }
490   }
491 
write(byte[] out)492   public void write(byte[] out) throws IOException {
493     if (mOutputStream != null) {
494       mOutputStream.write(out);
495     } else {
496       throw new IOException("Bluetooth not ready.");
497     }
498   }
499 
write(String out)500   public void write(String out) throws IOException {
501     this.write(out.getBytes());
502   }
503 
readReady()504   public Boolean readReady() throws IOException {
505     if (mReader != null) {
506       return mReader.ready();
507     }
508     throw new IOException("Bluetooth not ready.");
509   }
510 
readBinary()511   public byte[] readBinary() throws IOException {
512     return this.readBinary(4096);
513   }
514 
readBinary(int bufferSize)515   public byte[] readBinary(int bufferSize) throws IOException {
516     if (mReader != null) {
517       byte[] buffer = new byte[bufferSize];
518       int bytesRead = mInputStream.read(buffer);
519       if (bytesRead == -1) {
520         Log.e("Read failed.");
521         throw new IOException("Read failed.");
522       }
523       byte[] truncatedBuffer = new byte[bytesRead];
524       System.arraycopy(buffer, 0, truncatedBuffer, 0, bytesRead);
525       return truncatedBuffer;
526     }
527 
528     throw new IOException("Bluetooth not ready.");
529 
530   }
531 
read()532   public String read() throws IOException {
533     return this.read(4096);
534   }
535 
read(int bufferSize)536   public String read(int bufferSize) throws IOException {
537     if (mReader != null) {
538       char[] buffer = new char[bufferSize];
539       int bytesRead = mReader.read(buffer);
540       if (bytesRead == -1) {
541         Log.e("Read failed.");
542         throw new IOException("Read failed.");
543       }
544       return new String(buffer, 0, bytesRead);
545     }
546     throw new IOException("Bluetooth not ready.");
547   }
548 
readLine()549   public String readLine() throws IOException {
550     if (mReader != null) {
551       return mReader.readLine();
552     }
553     throw new IOException("Bluetooth not ready.");
554   }
555 
getConnectedDeviceName()556   public String getConnectedDeviceName() {
557     return mDevice.getName();
558   }
559 
clearFileDescriptor()560   private synchronized void clearFileDescriptor() {
561     try {
562       Field field = BluetoothSocket.class.getDeclaredField("mPfd");
563       field.setAccessible(true);
564       ParcelFileDescriptor mPfd = (ParcelFileDescriptor) field.get(mSocket);
565       Log.d("Closing mPfd: " + mPfd);
566       if (mPfd == null)
567         return;
568       mPfd.close();
569       mPfd = null;
570       try { field.set(mSocket, mPfd); }
571       catch(Exception e) {
572           Log.d("Exception setting mPfd = null in cleanCloseFix(): " + e.toString());
573       }
574     } catch (Exception e) {
575         Log.w("ParcelFileDescriptor could not be cleanly closed.", e);
576     }
577   }
578 
stop()579   public void stop() {
580     if (mSocket != null) {
581       try {
582         mSocket.close();
583         clearFileDescriptor();
584       } catch (IOException e) {
585         Log.e(e);
586       }
587     }
588     mSocket = null;
589     if (mServerSocket != null) {
590       try {
591         mServerSocket.close();
592       } catch (IOException e) {
593         Log.e(e);
594       }
595     }
596     mServerSocket = null;
597 
598     if (mInputStream != null) {
599       try {
600         mInputStream.close();
601       } catch (IOException e) {
602         Log.e(e);
603       }
604     }
605     mInputStream = null;
606     if (mOutputStream != null) {
607       try {
608         mOutputStream.close();
609       } catch (IOException e) {
610         Log.e(e);
611       }
612     }
613     mOutputStream = null;
614     if (mReader != null) {
615       try {
616         mReader.close();
617       } catch (IOException e) {
618         Log.e(e);
619       }
620     }
621     mReader = null;
622   }
623 }
624