2015-01-12 00:35:15 +01:00
|
|
|
package nodomain.freeyourgadget.gadgetbridge;
|
|
|
|
|
|
|
|
import android.app.Notification;
|
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.app.Service;
|
|
|
|
import android.bluetooth.BluetoothAdapter;
|
|
|
|
import android.bluetooth.BluetoothDevice;
|
2015-01-18 01:10:44 +01:00
|
|
|
import android.bluetooth.BluetoothServerSocket;
|
2015-01-12 00:35:15 +01:00
|
|
|
import android.bluetooth.BluetoothSocket;
|
2015-01-23 11:32:58 +01:00
|
|
|
import android.content.ContentResolver;
|
2015-01-12 00:35:15 +01:00
|
|
|
import android.content.Intent;
|
2015-01-23 11:32:58 +01:00
|
|
|
import android.database.Cursor;
|
|
|
|
import android.net.Uri;
|
2015-01-12 00:35:15 +01:00
|
|
|
import android.os.IBinder;
|
|
|
|
import android.os.ParcelUuid;
|
2015-01-23 11:32:58 +01:00
|
|
|
import android.provider.ContactsContract;
|
2015-01-12 00:35:15 +01:00
|
|
|
import android.support.v4.app.NotificationCompat;
|
|
|
|
import android.util.Log;
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.OutputStream;
|
2015-01-20 23:51:55 +01:00
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.ByteOrder;
|
2015-01-12 00:35:15 +01:00
|
|
|
import java.util.Set;
|
2015-01-18 01:10:44 +01:00
|
|
|
import java.util.UUID;
|
2015-01-12 00:35:15 +01:00
|
|
|
|
|
|
|
public class BluetoothCommunicationService extends Service {
|
|
|
|
private static final String TAG = "BluetoothCommunicationService";
|
|
|
|
|
2015-01-18 01:10:44 +01:00
|
|
|
public static final String ACTION_START
|
2015-01-12 00:35:15 +01:00
|
|
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.start";
|
2015-01-18 01:10:44 +01:00
|
|
|
public static final String ACTION_STOP
|
2015-01-12 00:35:15 +01:00
|
|
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.stop";
|
2015-01-18 01:10:44 +01:00
|
|
|
public static final String ACTION_SENDMESSAGE
|
2015-01-22 22:49:50 +01:00
|
|
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.sendmessage";
|
|
|
|
public static final String ACTION_INCOMINGCALL
|
|
|
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.incomingcall";
|
2015-01-18 01:10:44 +01:00
|
|
|
public static final String ACTION_SETTIME
|
|
|
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.settime";
|
2015-01-12 00:35:15 +01:00
|
|
|
|
|
|
|
private BluetoothAdapter mBtAdapter = null;
|
|
|
|
private String mBtDeviceAddress = null;
|
|
|
|
private BluetoothSocket mBtSocket = null;
|
|
|
|
private BtSocketIoThread mBtSocketIoThread = null;
|
2015-01-18 01:10:44 +01:00
|
|
|
private BtSocketAcceptThread mBtSocketAcceptThread = null;
|
|
|
|
private static final UUID PEBBLE_UUID = UUID.fromString("00000000-deca-fade-deca-deafdecacafe");
|
|
|
|
private boolean mPassiveMode = false;
|
2015-01-12 00:35:15 +01:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onCreate() {
|
|
|
|
super.onCreate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
2015-01-18 01:10:44 +01:00
|
|
|
if (intent.getAction().equals(ACTION_START)) {
|
2015-01-12 00:35:15 +01:00
|
|
|
Intent notificationIntent = new Intent(this, ControlCenter.class);
|
|
|
|
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
|
|
|
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
|
|
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
|
|
|
|
notificationIntent, 0);
|
|
|
|
|
|
|
|
Notification notification = new NotificationCompat.Builder(this)
|
|
|
|
.setContentTitle("Gadgetbridge")
|
|
|
|
.setTicker("Gadgetbridge Running")
|
|
|
|
.setContentText("Gadgetbrige Running")
|
|
|
|
.setSmallIcon(R.drawable.ic_launcher)
|
|
|
|
.setContentIntent(pendingIntent)
|
|
|
|
.setOngoing(true).build();
|
|
|
|
|
|
|
|
|
|
|
|
//Check the system status
|
|
|
|
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
|
|
if (mBtAdapter == null) {
|
|
|
|
Toast.makeText(this, "Bluetooth is not supported.", Toast.LENGTH_SHORT).show();
|
|
|
|
} else if (!mBtAdapter.isEnabled()) {
|
|
|
|
Toast.makeText(this, "Bluetooth is disabled.", Toast.LENGTH_SHORT).show();
|
|
|
|
} else {
|
|
|
|
Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices();
|
|
|
|
for (BluetoothDevice device : pairedDevices) {
|
|
|
|
if (device.getName().indexOf("Pebble") == 0) {
|
|
|
|
// Matching device found
|
|
|
|
mBtDeviceAddress = device.getAddress();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2015-01-18 01:10:44 +01:00
|
|
|
if (mPassiveMode) {
|
|
|
|
mBtSocketAcceptThread = new BtSocketAcceptThread();
|
|
|
|
mBtSocketAcceptThread.start();
|
|
|
|
} else if (mBtSocket == null || !mBtSocket.isConnected()) {
|
2015-01-12 00:35:15 +01:00
|
|
|
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(mBtDeviceAddress);
|
|
|
|
ParcelUuid uuids[] = btDevice.getUuids();
|
|
|
|
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
|
|
|
mBtSocket.connect();
|
|
|
|
mBtSocketIoThread = new BtSocketIoThread(mBtSocket.getInputStream(), mBtSocket.getOutputStream());
|
|
|
|
mBtSocketIoThread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
startForeground(1, notification); //FIXME: don't hardcode id
|
|
|
|
}
|
2015-01-18 01:10:44 +01:00
|
|
|
} else if (intent.getAction().equals(ACTION_STOP)) {
|
2015-01-12 00:35:15 +01:00
|
|
|
try {
|
|
|
|
mBtSocketIoThread.join();
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
try {
|
2015-01-23 11:32:58 +01:00
|
|
|
|
2015-01-12 00:35:15 +01:00
|
|
|
mBtSocket.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
mBtSocket = null;
|
|
|
|
mBtSocketIoThread = null;
|
|
|
|
|
|
|
|
stopForeground(true);
|
|
|
|
stopSelf();
|
2015-01-18 01:10:44 +01:00
|
|
|
} else if (intent.getAction().equals(ACTION_SENDMESSAGE)) {
|
2015-01-12 00:35:15 +01:00
|
|
|
String title = intent.getStringExtra("notification_title");
|
|
|
|
String content = intent.getStringExtra("notification_content");
|
|
|
|
if (mBtSocketIoThread != null) {
|
2015-01-18 01:10:44 +01:00
|
|
|
byte[] msg = PebbleProtocol.encodeSMS(title, content);
|
|
|
|
mBtSocketIoThread.write(msg);
|
|
|
|
}
|
2015-01-22 22:49:50 +01:00
|
|
|
} else if (intent.getAction().equals(ACTION_INCOMINGCALL)) {
|
|
|
|
String phoneNumber = intent.getStringExtra("incomingcall_phonenumber");
|
|
|
|
byte phoneState = intent.getByteExtra("incomingcall_state", (byte) 0);
|
|
|
|
|
2015-01-23 11:32:58 +01:00
|
|
|
String callerName = getContactDisplayNameByNumber(phoneNumber);
|
|
|
|
|
2015-01-22 22:49:50 +01:00
|
|
|
if (mBtSocketIoThread != null) {
|
2015-01-23 11:32:58 +01:00
|
|
|
byte[] msg = PebbleProtocol.encodeIncomingCall(phoneNumber, callerName, phoneState);
|
2015-01-22 22:49:50 +01:00
|
|
|
mBtSocketIoThread.write(msg);
|
|
|
|
}
|
2015-01-18 01:10:44 +01:00
|
|
|
} else if (intent.getAction().equals(ACTION_SETTIME)) {
|
|
|
|
if (mBtSocketIoThread != null) {
|
|
|
|
byte[] msg = PebbleProtocol.encodeSetTime(-1);
|
2015-01-12 00:35:15 +01:00
|
|
|
mBtSocketIoThread.write(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return START_STICKY;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
|
|
|
super.onDestroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public IBinder onBind(Intent intent) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-01-23 11:32:58 +01:00
|
|
|
|
|
|
|
private String getContactDisplayNameByNumber(String number) {
|
|
|
|
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
|
|
|
|
String name = "Unknown";
|
|
|
|
|
|
|
|
if (number == null || number.equals("")) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentResolver contentResolver = getContentResolver();
|
|
|
|
Cursor contactLookup = contentResolver.query(uri, null, null, null, null);
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (contactLookup != null && contactLookup.getCount() > 0) {
|
|
|
|
contactLookup.moveToNext();
|
|
|
|
name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (contactLookup != null) {
|
|
|
|
contactLookup.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-18 01:10:44 +01:00
|
|
|
private class BtSocketAcceptThread extends Thread {
|
|
|
|
private final BluetoothServerSocket mmServerSocket;
|
|
|
|
|
|
|
|
public BtSocketAcceptThread() {
|
|
|
|
BluetoothServerSocket tmp = null;
|
|
|
|
try {
|
|
|
|
tmp = mBtAdapter.listenUsingRfcommWithServiceRecord("PebbleListener", PEBBLE_UUID);
|
|
|
|
} catch (IOException e) {
|
|
|
|
}
|
|
|
|
mmServerSocket = tmp;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
|
|
|
while (true) {
|
|
|
|
try {
|
|
|
|
mBtSocket = mmServerSocket.accept();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (mBtSocket != null) {
|
|
|
|
try {
|
|
|
|
mBtSocketIoThread = new BtSocketIoThread(mBtSocket.getInputStream(), mBtSocket.getOutputStream());
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mBtSocketIoThread.start();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-12 00:35:15 +01:00
|
|
|
private class BtSocketIoThread extends Thread {
|
|
|
|
private final InputStream mmInStream;
|
|
|
|
private final OutputStream mmOutStream;
|
|
|
|
|
|
|
|
public BtSocketIoThread(InputStream instream, OutputStream outstream) {
|
|
|
|
mmInStream = instream;
|
|
|
|
mmOutStream = outstream;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
2015-01-20 23:51:55 +01:00
|
|
|
byte[] buffer = new byte[8192];
|
|
|
|
int bytes;
|
2015-01-12 00:35:15 +01:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
try {
|
2015-01-20 23:51:55 +01:00
|
|
|
bytes = mmInStream.read(buffer, 0, 4);
|
|
|
|
if (bytes < 4)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ByteBuffer buf = ByteBuffer.wrap(buffer);
|
|
|
|
buf.order(ByteOrder.BIG_ENDIAN);
|
|
|
|
short length = buf.getShort();
|
|
|
|
short endpoint = buf.getShort();
|
|
|
|
if (length < 0 || length > 8192) {
|
|
|
|
Log.i(TAG, "invalid length " + length);
|
|
|
|
while (mmInStream.available() > 0) {
|
|
|
|
mmInStream.read(buffer); // read all
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes = mmInStream.read(buffer, 4, length);
|
|
|
|
if (bytes < length) {
|
|
|
|
try {
|
|
|
|
Thread.sleep(100);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
Log.i(TAG, "Read " + bytes + ", expected " + length + " reading remaining " + (length - bytes));
|
|
|
|
int bytes_rest = mmInStream.read(buffer, 4 + bytes, length - bytes);
|
|
|
|
bytes += bytes_rest;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) {
|
|
|
|
Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!");
|
|
|
|
write(PebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
|
2015-01-22 22:49:50 +01:00
|
|
|
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
|
2015-01-20 23:51:55 +01:00
|
|
|
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
Thread.sleep(100);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2015-01-12 00:35:15 +01:00
|
|
|
} catch (IOException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
synchronized public void write(byte[] bytes) {
|
|
|
|
try {
|
|
|
|
mmOutStream.write(bytes);
|
|
|
|
mmOutStream.flush();
|
|
|
|
} catch (IOException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|