1 /*
2  * Copyright 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.bluetooth.BluetoothDevice;
20 import android.bluetooth.BluetoothServerSocket;
21 import android.bluetooth.BluetoothSocket;
22 import android.os.ParcelFileDescriptor;
23 
24 import com.googlecode.android_scripting.Log;
25 
26 import java.io.BufferedReader;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.OutputStream;
31 import java.lang.reflect.Field;
32 
33 /**
34  * Class with Bluetooth Connection helper functions.
35  *
36  */
37 
38 class BluetoothConnection {
39     private BluetoothSocket mSocket;
40     private BluetoothDevice mDevice;
41     private OutputStream mOutputStream;
42     private InputStream mInputStream;
43     private BufferedReader mReader;
44     private BluetoothServerSocket mServerSocket;
45     private String mUuid;
46 
BluetoothConnection(BluetoothSocket mSocket)47     BluetoothConnection(BluetoothSocket mSocket) throws IOException {
48         this(mSocket, null);
49     }
50 
BluetoothConnection(BluetoothSocket mSocket, BluetoothServerSocket mServerSocket)51     BluetoothConnection(BluetoothSocket mSocket, BluetoothServerSocket mServerSocket)
52             throws IOException {
53         this.mSocket = mSocket;
54         mOutputStream = mSocket.getOutputStream();
55         mInputStream = mSocket.getInputStream();
56         mDevice = mSocket.getRemoteDevice();
57         mReader = new BufferedReader(new InputStreamReader(mInputStream, "ASCII"));
58         this.mServerSocket = mServerSocket;
59     }
60 
61     /**
62      * Set the UUID of this connection
63      *
64      * @param uuid the new UUID
65      */
setUUID(String uuid)66     public void setUUID(String uuid) {
67         this.mUuid = uuid;
68     }
69 
70     /**
71      * Get this connection UUID
72      *
73      * @return String this connection UUID
74      */
getUUID()75     public String getUUID() {
76         return mUuid;
77     }
78 
79     /**
80      * Get the MAC address of remote device
81      *
82      * @return String the remote device MAC address
83      */
getRemoteBluetoothAddress()84     public String getRemoteBluetoothAddress() {
85         return mDevice.getAddress();
86     }
87 
88     /**
89      * Get the connected state
90      *
91      * @return boolean TRUE if connected
92      */
isConnected()93     public boolean isConnected() {
94         if (mSocket == null) {
95             return false;
96         }
97         try {
98             mSocket.getRemoteDevice();
99             mInputStream.available();
100             mReader.ready();
101             return true;
102         } catch (Exception e) {
103             return false;
104         }
105     }
106 
107     /**
108      * Write an array of bytes to output stream
109      *
110      * @param out the array of bytes to write
111      */
write(byte[] out)112     public void write(byte[] out) throws IOException {
113         if (mOutputStream != null) {
114             mOutputStream.write(out);
115         } else {
116             throw new IOException("write: Bluetooth not ready.");
117         }
118     }
119 
120     /**
121      * Write a string of bytes to output stream
122      *
123      * @param out the String of bytes to write
124      */
write(String out)125     public void write(String out) throws IOException {
126         this.write(out.getBytes());
127     }
128 
129     /**
130      * Write one byte to output stream
131      *
132      * @param out the byte to write
133      */
write(int out)134     public void write(int out) throws IOException {
135         if (mOutputStream != null) {
136             mOutputStream.write(out);
137         } else {
138             throw new IOException("write: Bluetooth not ready.");
139         }
140     }
141 
142     /**
143      * To test if the next data is ready to be read
144      *
145      * @return Boolean TRUE if data is ready to be read
146      */
readReady()147     public Boolean readReady() throws IOException {
148         if (mReader != null) {
149             return mReader.ready();
150         }
151         throw new IOException("Bluetooth not ready.");
152     }
153 
154     /**
155      * Read an array of data
156      *
157      * @return byte[] the buffer read
158      */
readBinary()159     public byte[] readBinary() throws IOException {
160         return this.readBinary(4096);
161     }
162 
163     /**
164      * Read an array of data of given size
165      *
166      * @param bufferSize the size to read
167      *
168      * @return byte[] the buffer containing read data
169      */
readBinary(int bufferSize)170     public byte[] readBinary(int bufferSize) throws IOException {
171         if (mReader != null) {
172             byte[] buffer = new byte[bufferSize];
173             int bytesRead = mInputStream.read(buffer);
174             if (bytesRead == -1) {
175                 Log.e("Read failed.");
176                 throw new IOException("Read failed.");
177             }
178             byte[] truncatedBuffer = new byte[bytesRead];
179             System.arraycopy(buffer, 0, truncatedBuffer, 0, bytesRead);
180             return truncatedBuffer;
181         }
182 
183         throw new IOException("Bluetooth not ready.");
184 
185     }
186 
187     /**
188      * Read a string of data
189      *
190      * @return String the read string
191      */
read()192     public String read() throws IOException {
193         return this.read(4096);
194     }
195 
196     /**
197      * Read a string of data of given size
198      *
199      * @param bufferSize number of bytes to read
200      *
201      * @return String the read string
202      */
read(int bufferSize)203     public String read(int bufferSize) throws IOException {
204         if (mReader != null) {
205             char[] buffer = new char[bufferSize];
206             int bytesRead = mReader.read(buffer);
207             if (bytesRead == -1) {
208                 Log.e("Read failed.");
209                 throw new IOException("Read failed.");
210             }
211             return new String(buffer, 0, bytesRead);
212         }
213         throw new IOException("Bluetooth not ready.");
214     }
215 
216     /**
217      * To read one byte
218      *
219      * @return int byte read
220      */
readbyte()221     public int readbyte() throws IOException {
222         if (mReader != null) {
223             int byteRead = mReader.read();
224             if (byteRead == -1) {
225                 Log.e("Read failed.");
226                 throw new IOException("Read failed.");
227             }
228             return byteRead;
229         }
230         throw new IOException("readbyte: Bluetooth not ready.");
231     }
232 
233     /**
234      * To read one line
235      *
236      * @return String line read
237      */
readLine()238     public String readLine() throws IOException {
239         if (mReader != null) {
240             return mReader.readLine();
241         }
242         throw new IOException("Bluetooth not ready.");
243     }
244 
245     /**
246      * Returns the name of the connected device
247      *
248      * @return String name of connected device
249      */
getConnectedDeviceName()250     public String getConnectedDeviceName() {
251         return mDevice.getName();
252     }
253 
254 
clearFileDescriptor()255     private synchronized void clearFileDescriptor() {
256         try {
257             Field field = BluetoothSocket.class.getDeclaredField("mPfd");
258             field.setAccessible(true);
259             ParcelFileDescriptor mPfd = (ParcelFileDescriptor) field.get(mSocket);
260             Log.d("Closing mPfd: " + mPfd);
261             if (mPfd == null) return;
262             mPfd.close();
263             mPfd = null;
264             try {
265                 field.set(mSocket, mPfd);
266             } catch (Exception e) {
267                 Log.d("Exception setting mPfd = null in cleanCloseFix(): " + e.toString());
268             }
269         } catch (Exception e) {
270             Log.w("ParcelFileDescriptor could not be cleanly closed.", e);
271         }
272     }
273 
274     /**
275      * To stop this connection
276      */
stop()277     public void stop() {
278         if (mSocket != null) {
279             try {
280                 mSocket.close();
281                 clearFileDescriptor();
282             } catch (IOException e) {
283                 Log.e(e);
284             }
285         }
286         mSocket = null;
287         if (mServerSocket != null) {
288             try {
289                 mServerSocket.close();
290             } catch (IOException e) {
291                 Log.e(e);
292             }
293         }
294         mServerSocket = null;
295 
296         if (mInputStream != null) {
297             try {
298                 mInputStream.close();
299             } catch (IOException e) {
300                 Log.e(e);
301             }
302         }
303         mInputStream = null;
304         if (mOutputStream != null) {
305             try {
306                 mOutputStream.close();
307             } catch (IOException e) {
308                 Log.e(e);
309             }
310         }
311         mOutputStream = null;
312         if (mReader != null) {
313             try {
314                 mReader.close();
315             } catch (IOException e) {
316                 Log.e(e);
317             }
318         }
319         mReader = null;
320     }
321 }
322