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