1 package com.google.android.experimental.bttraffic;
2 
3 import android.app.Service;
4 import android.bluetooth.BluetoothAdapter;
5 import android.bluetooth.BluetoothDevice;
6 import android.bluetooth.BluetoothServerSocket;
7 import android.bluetooth.BluetoothSocket;
8 import android.content.Intent;
9 import android.os.Bundle;
10 import android.os.IBinder;
11 import android.os.SystemClock;
12 import android.util.Log;
13 
14 import java.io.Closeable;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.lang.Exception;
19 import java.lang.Runtime;
20 import java.lang.RuntimeException;
21 import java.lang.Process;
22 import java.nio.ByteBuffer;
23 import java.util.Random;
24 import java.util.Set;
25 import java.util.UUID;
26 
27 public class BTtraffic extends Service {
28     public static final String TAG = "bttraffic";
29     static final String SERVICE_NAME = "bttraffic";
30     static final String SYS_SERVICE_NAME = "com.android.bluetooth";
31     static final UUID SERVICE_UUID = UUID.fromString("5e8945b0-1234-5432-a5e2-0800200c9a67");
32     volatile Thread mWorkerThread;
33     volatile boolean isShuttingDown = false;
34     volatile boolean isServer = false;
35 
BTtraffic()36     public BTtraffic() {}
37 
safeClose(Closeable closeable)38     static void safeClose(Closeable closeable) {
39         try {
40             closeable.close();
41         } catch (IOException e) {
42             Log.d(TAG, "Unable to close resource.\n");
43         }
44     }
45 
46     @Override
onStartCommand(Intent intent, int flags, int startId)47     public int onStartCommand(Intent intent, int flags, int startId) {
48         if (intent == null) {
49             stopSelf();
50             return 0;
51         }
52         if ("stop".equals(intent.getAction())) {
53             stopService();
54         } else if ("start".equals(intent.getAction())) {
55             startWorker(intent);
56         } else {
57             Log.d(TAG, "unknown action: + " + intent.getAction());
58         }
59         return 0;
60     }
61 
startWorker(Intent intent)62     private void startWorker(Intent intent) {
63         if (mWorkerThread != null) {
64             Log.d(TAG, "worker thread already active");
65             return;
66         }
67         isShuttingDown = false;
68         String remoteAddr = intent.getStringExtra("addr");
69         Log.d(TAG, "startWorker: addr=" + remoteAddr);
70         Runnable worker =
71                 remoteAddr == null
72                         ? new ListenerRunnable(this, intent)
73                         : new SenderRunnable(this, remoteAddr, intent);
74         isServer = remoteAddr == null ? true: false;
75         mWorkerThread = new Thread(worker, "BTtrafficWorker");
76         try {
77             startMonitor();
78             Log.d(TAG, "Monitor service started");
79             mWorkerThread.start();
80             Log.d(TAG, "Worker thread started");
81         } catch (Exception e) {
82             Log.d(TAG, "Failed to start service", e);
83         }
84     }
85 
startMonitor()86     private void startMonitor()
87             throws Exception {
88         if (isServer) {
89             Log.d(TAG, "Start monitor on server");
90             String[] startmonitorCmd = {
91                     "/system/bin/am",
92                     "startservice",
93                     "-a", "start",
94                     "-e", "java", SERVICE_NAME,
95                     "-e", "hal", SYS_SERVICE_NAME,
96                     "com.google.android.experimental.svcmonitor/.SvcMonitor"
97             };
98             Process ps = new ProcessBuilder()
99                     .command(startmonitorCmd)
100                     .redirectErrorStream(true)
101                     .start();
102         } else {
103             Log.d(TAG, "No need to start SvcMonitor on client");
104         }
105     }
106 
stopMonitor()107     private void stopMonitor()
108             throws Exception {
109         if (isServer) {
110             Log.d(TAG, "StopMonitor on server");
111             String[] stopmonitorCmd = {
112                     "/system/bin/am",
113                     "startservice",
114                     "-a", "stop",
115                     "com.google.android.experimental.svcmonitor/.SvcMonitor"
116             };
117             Process ps = new ProcessBuilder()
118                     .command(stopmonitorCmd)
119                     .redirectErrorStream(true)
120                     .start();
121         } else {
122             Log.d(TAG, "No need to stop Svcmonitor on client");
123         }
124     }
125 
stopService()126     public void stopService() {
127         if (mWorkerThread == null) {
128             Log.d(TAG, "no active thread");
129             return;
130         }
131 
132         isShuttingDown = true;
133 
134         try {
135             stopMonitor();
136         } catch (Exception e) {
137             Log.d(TAG, "Unable to stop SvcMonitor!", e);
138         }
139 
140         if (Thread.currentThread() != mWorkerThread) {
141             mWorkerThread.interrupt();
142             Log.d(TAG, "Interrupting thread");
143             try {
144                 mWorkerThread.join();
145             } catch (InterruptedException e) {
146                 Log.d(TAG, "Unable to join thread!");
147             }
148         }
149 
150         mWorkerThread = null;
151         stopSelf();
152         Log.d(TAG, "Service stopped");
153     }
154 
155     @Override
onDestroy()156     public void onDestroy() {
157         super.onDestroy();
158     }
159 
160     @Override
onBind(Intent intent)161     public IBinder onBind(Intent intent) {
162         throw new UnsupportedOperationException("Not yet implemented");
163     }
164 
165     public static class ListenerRunnable implements Runnable {
166         private final BTtraffic bttraffic;
167         private final boolean sendAck;
168         private Intent intent;
169         private final int maxbuffersize = 20 * 1024 * 1024;
170 
ListenerRunnable(BTtraffic bttraffic, Intent intent)171         public ListenerRunnable(BTtraffic bttraffic, Intent intent) {
172             this.bttraffic = bttraffic;
173             this.sendAck = intent.getBooleanExtra("ack", true);
174             this.intent = intent;
175         }
176 
177         @Override
run()178         public void run() {
179             BluetoothServerSocket serverSocket;
180 
181             try {
182                 Log.d(TAG, "getting server socket");
183                 serverSocket = BluetoothAdapter.getDefaultAdapter()
184                         .listenUsingInsecureRfcommWithServiceRecord(
185                                 SERVICE_NAME, SERVICE_UUID);
186             } catch (IOException e) {
187                 Log.d(TAG, "error creating server socket, stopping thread");
188                 bttraffic.stopService();
189                 return;
190             }
191 
192             Log.d(TAG, "got server socket, starting accept loop");
193             BluetoothSocket socket = null;
194             try {
195                 Log.d(TAG, "accepting");
196                 socket = serverSocket.accept();
197 
198                 if (!Thread.interrupted()) {
199                     Log.d(TAG, "accepted, listening");
200                     doListening(socket.getInputStream(), socket.getOutputStream());
201                     Log.d(TAG, "listen finished");
202                 }
203             } catch (IOException e) {
204                 Log.d(TAG, "error while accepting or listening", e);
205             } finally {
206                 Log.d(TAG, "Linster interruped");
207                 Log.d(TAG, "closing socket and stopping service");
208                 safeClose(serverSocket);
209                 safeClose(socket);
210                 if (!bttraffic.isShuttingDown)
211                     bttraffic.stopService();
212             }
213 
214         }
215 
doListening(InputStream inputStream, OutputStream outputStream)216         private void doListening(InputStream inputStream, OutputStream outputStream)
217                 throws IOException {
218             ByteBuffer byteBuffer = ByteBuffer.allocate(maxbuffersize);
219 
220             while (!Thread.interrupted()) {
221                 readBytesIntoBuffer(inputStream, byteBuffer, 4);
222                 byteBuffer.flip();
223                 int length = byteBuffer.getInt();
224                 if (Thread.interrupted())
225                     break;
226                 readBytesIntoBuffer(inputStream, byteBuffer, length);
227 
228                 if (sendAck)
229                     outputStream.write(0x55);
230             }
231         }
232 
readBytesIntoBuffer(InputStream inputStream, ByteBuffer byteBuffer, int numToRead)233         void readBytesIntoBuffer(InputStream inputStream, ByteBuffer byteBuffer, int numToRead)
234                 throws IOException {
235             byteBuffer.clear();
236             while (true) {
237                 int position = byteBuffer.position();
238                 int remaining = numToRead - position;
239                 if (remaining == 0) {
240                     break;
241                 }
242                 int count = inputStream.read(byteBuffer.array(), position, remaining);
243                 if (count < 0) {
244                     throw new IOException("read the EOF");
245                 }
246                 byteBuffer.position(position + count);
247             }
248         }
249     }
250 
251     public static class SenderRunnable implements Runnable {
252         private final BTtraffic bttraffic;
253         private final String remoteAddr;
254         private final int pkgsize, period;
255         private final int defaultpkgsize = 1024;
256         private final int defaultperiod = 5000;
257         private static ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
258 
SenderRunnable(BTtraffic bttraffic, String remoteAddr, Intent intent)259         public SenderRunnable(BTtraffic bttraffic, String remoteAddr, Intent intent) {
260             this.bttraffic = bttraffic;
261             this.remoteAddr = remoteAddr;
262             this.pkgsize = intent.getIntExtra("size", defaultpkgsize);
263             this.period = intent.getIntExtra("period", defaultperiod);
264         }
265 
266         @Override
run()267         public void run() {
268             BluetoothDevice device = null;
269             try {
270                 device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr);
271             } catch (IllegalArgumentException e) {
272                 Log.d(TAG, "Invalid BT MAC address!\n");
273             }
274             if (device == null) {
275                 Log.d(TAG, "can't find matching device, stopping thread and service");
276                 bttraffic.stopService();
277                 return;
278             }
279 
280             BluetoothSocket socket = null;
281             try {
282                 Log.d(TAG, "connecting to device with MAC addr: " + remoteAddr);
283                 socket = device.createInsecureRfcommSocketToServiceRecord(SERVICE_UUID);
284                 socket.connect();
285                 Log.d(TAG, "connected, starting to send");
286                 doSending(socket.getOutputStream());
287                 Log.d(TAG, "send stopped, stopping service");
288             } catch (Exception e) {
289                 Log.d(TAG, "error while sending", e);
290             } finally {
291                 Log.d(TAG, "finishing, closing thread and service");
292                 safeClose(socket);
293                 if (!bttraffic.isShuttingDown)
294                     bttraffic.stopService();
295             }
296         }
297 
doSending(OutputStream outputStream)298         private void doSending(OutputStream outputStream) throws IOException {
299             Log.w(TAG, "doSending");
300             try {
301                 Random random = new Random(System.currentTimeMillis());
302 
303                 byte[] bytes = new byte[pkgsize];
304                 random.nextBytes(bytes);
305                 while (!Thread.interrupted()) {
306                     writeBytes(outputStream, bytes.length);
307                     outputStream.write(bytes, 0, bytes.length);
308                     if (period < 0)
309                         break;
310                     if (period == 0)
311                         continue;
312 
313                     SystemClock.sleep(period);
314                 }
315                 Log.d(TAG, "Sender interrupted");
316             } catch (IOException e) {
317                 Log.d(TAG, "doSending got error", e);
318             }
319         }
320 
writeBytes(OutputStream outputStream, int value)321         private static void writeBytes(OutputStream outputStream, int value) throws IOException {
322             lengthBuffer.putInt(value);
323             lengthBuffer.flip();
324             outputStream.write(lengthBuffer.array(), lengthBuffer.position(), lengthBuffer.limit());
325         }
326     }
327 
328 }
329