1package com.google.android.experimental.bttraffic;
2
3import android.app.Service;
4import android.bluetooth.BluetoothAdapter;
5import android.bluetooth.BluetoothDevice;
6import android.bluetooth.BluetoothServerSocket;
7import android.bluetooth.BluetoothSocket;
8import android.content.Intent;
9import android.os.Bundle;
10import android.os.IBinder;
11import android.os.SystemClock;
12import android.util.Log;
13
14import java.io.Closeable;
15import java.io.IOException;
16import java.io.InputStream;
17import java.io.OutputStream;
18import java.lang.Exception;
19import java.lang.Runtime;
20import java.lang.RuntimeException;
21import java.lang.Process;
22import java.nio.ByteBuffer;
23import java.util.Random;
24import java.util.Set;
25import java.util.UUID;
26
27public 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
36    public BTtraffic() {}
37
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
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
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
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
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
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
156    public void onDestroy() {
157        super.onDestroy();
158    }
159
160    @Override
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
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
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
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
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
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
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
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
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