blob: 286c0aa2915fad5d27f641f9298036a17c024008 [file] [log] [blame]
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());
}
}
}