| package com.google.android.experimental.bttraffic; |
| |
| import android.app.Service; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothServerSocket; |
| import android.bluetooth.BluetoothSocket; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.SystemClock; |
| import android.util.Log; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.Exception; |
| import java.lang.Runtime; |
| import java.lang.RuntimeException; |
| import java.lang.Process; |
| import java.nio.ByteBuffer; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| public class BTtraffic extends Service { |
| public static final String TAG = "bttraffic"; |
| static final String SERVICE_NAME = "bttraffic"; |
| static final String SYS_SERVICE_NAME = "com.android.bluetooth"; |
| static final UUID SERVICE_UUID = UUID.fromString("5e8945b0-1234-5432-a5e2-0800200c9a67"); |
| volatile Thread mWorkerThread; |
| volatile boolean isShuttingDown = false; |
| volatile boolean isServer = false; |
| |
| public BTtraffic() {} |
| |
| static void safeClose(Closeable closeable) { |
| try { |
| closeable.close(); |
| } catch (IOException e) { |
| Log.d(TAG, "Unable to close resource.\n"); |
| } |
| } |
| |
| @Override |
| public int onStartCommand(Intent intent, int flags, int startId) { |
| if (intent == null) { |
| stopSelf(); |
| return 0; |
| } |
| if ("stop".equals(intent.getAction())) { |
| stopService(); |
| } else if ("start".equals(intent.getAction())) { |
| startWorker(intent); |
| } else { |
| Log.d(TAG, "unknown action: + " + intent.getAction()); |
| } |
| return 0; |
| } |
| |
| private void startWorker(Intent intent) { |
| if (mWorkerThread != null) { |
| Log.d(TAG, "worker thread already active"); |
| return; |
| } |
| isShuttingDown = false; |
| String remoteAddr = intent.getStringExtra("addr"); |
| Log.d(TAG, "startWorker: addr=" + remoteAddr); |
| Runnable worker = |
| remoteAddr == null |
| ? new ListenerRunnable(this, intent) |
| : new SenderRunnable(this, remoteAddr, intent); |
| isServer = remoteAddr == null ? true: false; |
| mWorkerThread = new Thread(worker, "BTtrafficWorker"); |
| try { |
| startMonitor(); |
| Log.d(TAG, "Monitor service started"); |
| mWorkerThread.start(); |
| Log.d(TAG, "Worker thread started"); |
| } catch (Exception e) { |
| Log.d(TAG, "Failed to start service", e); |
| } |
| } |
| |
| private void startMonitor() |
| throws Exception { |
| if (isServer) { |
| Log.d(TAG, "Start monitor on server"); |
| String[] startmonitorCmd = { |
| "/system/bin/am", |
| "startservice", |
| "-a", "start", |
| "-e", "java", SERVICE_NAME, |
| "-e", "hal", SYS_SERVICE_NAME, |
| "com.google.android.experimental.svcmonitor/.SvcMonitor" |
| }; |
| Process ps = new ProcessBuilder() |
| .command(startmonitorCmd) |
| .redirectErrorStream(true) |
| .start(); |
| } else { |
| Log.d(TAG, "No need to start SvcMonitor on client"); |
| } |
| } |
| |
| private void stopMonitor() |
| throws Exception { |
| if (isServer) { |
| Log.d(TAG, "StopMonitor on server"); |
| String[] stopmonitorCmd = { |
| "/system/bin/am", |
| "startservice", |
| "-a", "stop", |
| "com.google.android.experimental.svcmonitor/.SvcMonitor" |
| }; |
| Process ps = new ProcessBuilder() |
| .command(stopmonitorCmd) |
| .redirectErrorStream(true) |
| .start(); |
| } else { |
| Log.d(TAG, "No need to stop Svcmonitor on client"); |
| } |
| } |
| |
| public void stopService() { |
| if (mWorkerThread == null) { |
| Log.d(TAG, "no active thread"); |
| return; |
| } |
| |
| isShuttingDown = true; |
| |
| try { |
| stopMonitor(); |
| } catch (Exception e) { |
| Log.d(TAG, "Unable to stop SvcMonitor!", e); |
| } |
| |
| if (Thread.currentThread() != mWorkerThread) { |
| mWorkerThread.interrupt(); |
| Log.d(TAG, "Interrupting thread"); |
| try { |
| mWorkerThread.join(); |
| } catch (InterruptedException e) { |
| Log.d(TAG, "Unable to join thread!"); |
| } |
| } |
| |
| mWorkerThread = null; |
| stopSelf(); |
| Log.d(TAG, "Service stopped"); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| throw new UnsupportedOperationException("Not yet implemented"); |
| } |
| |
| public static class ListenerRunnable implements Runnable { |
| private final BTtraffic bttraffic; |
| private final boolean sendAck; |
| private Intent intent; |
| private final int maxbuffersize = 20 * 1024 * 1024; |
| |
| public ListenerRunnable(BTtraffic bttraffic, Intent intent) { |
| this.bttraffic = bttraffic; |
| this.sendAck = intent.getBooleanExtra("ack", true); |
| this.intent = intent; |
| } |
| |
| @Override |
| public void run() { |
| BluetoothServerSocket serverSocket; |
| |
| try { |
| Log.d(TAG, "getting server socket"); |
| serverSocket = BluetoothAdapter.getDefaultAdapter() |
| .listenUsingInsecureRfcommWithServiceRecord( |
| SERVICE_NAME, SERVICE_UUID); |
| } catch (IOException e) { |
| Log.d(TAG, "error creating server socket, stopping thread"); |
| bttraffic.stopService(); |
| return; |
| } |
| |
| Log.d(TAG, "got server socket, starting accept loop"); |
| BluetoothSocket socket = null; |
| try { |
| Log.d(TAG, "accepting"); |
| socket = serverSocket.accept(); |
| |
| if (!Thread.interrupted()) { |
| Log.d(TAG, "accepted, listening"); |
| doListening(socket.getInputStream(), socket.getOutputStream()); |
| Log.d(TAG, "listen finished"); |
| } |
| } catch (IOException e) { |
| Log.d(TAG, "error while accepting or listening", e); |
| } finally { |
| Log.d(TAG, "Linster interruped"); |
| Log.d(TAG, "closing socket and stopping service"); |
| safeClose(serverSocket); |
| safeClose(socket); |
| if (!bttraffic.isShuttingDown) |
| bttraffic.stopService(); |
| } |
| |
| } |
| |
| private void doListening(InputStream inputStream, OutputStream outputStream) |
| throws IOException { |
| ByteBuffer byteBuffer = ByteBuffer.allocate(maxbuffersize); |
| |
| while (!Thread.interrupted()) { |
| readBytesIntoBuffer(inputStream, byteBuffer, 4); |
| byteBuffer.flip(); |
| int length = byteBuffer.getInt(); |
| if (Thread.interrupted()) |
| break; |
| readBytesIntoBuffer(inputStream, byteBuffer, length); |
| |
| if (sendAck) |
| outputStream.write(0x55); |
| } |
| } |
| |
| void readBytesIntoBuffer(InputStream inputStream, ByteBuffer byteBuffer, int numToRead) |
| throws IOException { |
| byteBuffer.clear(); |
| while (true) { |
| int position = byteBuffer.position(); |
| int remaining = numToRead - position; |
| if (remaining == 0) { |
| break; |
| } |
| int count = inputStream.read(byteBuffer.array(), position, remaining); |
| if (count < 0) { |
| throw new IOException("read the EOF"); |
| } |
| byteBuffer.position(position + count); |
| } |
| } |
| } |
| |
| public static class SenderRunnable implements Runnable { |
| private final BTtraffic bttraffic; |
| private final String remoteAddr; |
| private final int pkgsize, period; |
| private final int defaultpkgsize = 1024; |
| private final int defaultperiod = 5000; |
| private static ByteBuffer lengthBuffer = ByteBuffer.allocate(4); |
| |
| public SenderRunnable(BTtraffic bttraffic, String remoteAddr, Intent intent) { |
| this.bttraffic = bttraffic; |
| this.remoteAddr = remoteAddr; |
| this.pkgsize = intent.getIntExtra("size", defaultpkgsize); |
| this.period = intent.getIntExtra("period", defaultperiod); |
| } |
| |
| @Override |
| public void run() { |
| BluetoothDevice device = null; |
| try { |
| device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr); |
| } catch (IllegalArgumentException e) { |
| Log.d(TAG, "Invalid BT MAC address!\n"); |
| } |
| if (device == null) { |
| Log.d(TAG, "can't find matching device, stopping thread and service"); |
| bttraffic.stopService(); |
| return; |
| } |
| |
| BluetoothSocket socket = null; |
| try { |
| Log.d(TAG, "connecting to device with MAC addr: " + remoteAddr); |
| socket = device.createInsecureRfcommSocketToServiceRecord(SERVICE_UUID); |
| socket.connect(); |
| Log.d(TAG, "connected, starting to send"); |
| doSending(socket.getOutputStream()); |
| Log.d(TAG, "send stopped, stopping service"); |
| } catch (Exception e) { |
| Log.d(TAG, "error while sending", e); |
| } finally { |
| Log.d(TAG, "finishing, closing thread and service"); |
| safeClose(socket); |
| if (!bttraffic.isShuttingDown) |
| bttraffic.stopService(); |
| } |
| } |
| |
| private void doSending(OutputStream outputStream) throws IOException { |
| Log.w(TAG, "doSending"); |
| try { |
| Random random = new Random(System.currentTimeMillis()); |
| |
| byte[] bytes = new byte[pkgsize]; |
| random.nextBytes(bytes); |
| while (!Thread.interrupted()) { |
| writeBytes(outputStream, bytes.length); |
| outputStream.write(bytes, 0, bytes.length); |
| if (period < 0) |
| break; |
| if (period == 0) |
| continue; |
| |
| SystemClock.sleep(period); |
| } |
| Log.d(TAG, "Sender interrupted"); |
| } catch (IOException e) { |
| Log.d(TAG, "doSending got error", e); |
| } |
| } |
| |
| private static void writeBytes(OutputStream outputStream, int value) throws IOException { |
| lengthBuffer.putInt(value); |
| lengthBuffer.flip(); |
| outputStream.write(lengthBuffer.array(), lengthBuffer.position(), lengthBuffer.limit()); |
| } |
| } |
| |
| } |