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