mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-04 01:09:47 +01:00
Refactoring: extracted inner GBDeviceIOThread classes to own files
To enable this, extracted some shared functionality into new GB helper class.
This commit is contained in:
parent
b3c036ef3b
commit
09bb5d4a3e
@ -1,48 +1,26 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge;
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
import android.app.Notification;
|
import nodomain.freeyourgadget.gadgetbridge.GBDevice.State;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.pebble.PebbleIoThread;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.MibandProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.PebbleProtocol;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.bluetooth.BluetoothSocket;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelUuid;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.zip.ZipInputStream;
|
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.pebble.PBWReader;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.pebble.STM32CRC;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommand;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandCallControl;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandMusicControl;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandVersionInfo;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceProtocol;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.MibandProtocol;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.protocol.PebbleProtocol;
|
|
||||||
|
|
||||||
public class BluetoothCommunicationService extends Service {
|
public class BluetoothCommunicationService extends Service {
|
||||||
public static final String ACTION_START
|
public static final String ACTION_START
|
||||||
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.start";
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.start";
|
||||||
@ -70,9 +48,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.install_pebbbleapp";
|
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.install_pebbbleapp";
|
||||||
|
|
||||||
private static final String TAG = "CommunicationService";
|
private static final String TAG = "CommunicationService";
|
||||||
private static final int NOTIFICATION_ID = 1;
|
|
||||||
private BluetoothAdapter mBtAdapter = null;
|
private BluetoothAdapter mBtAdapter = null;
|
||||||
private BluetoothSocket mBtSocket = null;
|
|
||||||
private GBDeviceIoThread mGBDeviceIoThread = null;
|
private GBDeviceIoThread mGBDeviceIoThread = null;
|
||||||
|
|
||||||
private boolean mStarted = false;
|
private boolean mStarted = false;
|
||||||
@ -80,165 +56,11 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
private GBDevice mGBDevice = null;
|
private GBDevice mGBDevice = null;
|
||||||
private GBDeviceProtocol mGBDeviceProtocol = null;
|
private GBDeviceProtocol mGBDeviceProtocol = null;
|
||||||
|
|
||||||
private void setReceiversEnableState(boolean enable) {
|
|
||||||
final Class[] receiverClasses = {
|
|
||||||
PhoneCallReceiver.class,
|
|
||||||
SMSReceiver.class,
|
|
||||||
K9Receiver.class,
|
|
||||||
MusicPlaybackReceiver.class,
|
|
||||||
//NotificationListener.class, // disabling this leads to loss of permission to read notifications
|
|
||||||
};
|
|
||||||
|
|
||||||
int newState;
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
|
||||||
} else {
|
|
||||||
newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
PackageManager pm = getPackageManager();
|
|
||||||
|
|
||||||
for (Class receiverClass : receiverClasses) {
|
|
||||||
ComponentName compName = new ComponentName(getApplicationContext(), receiverClass);
|
|
||||||
|
|
||||||
pm.setComponentEnabledSetting(compName, newState, PackageManager.DONT_KILL_APP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Notification createNotification(String text) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
return new NotificationCompat.Builder(this)
|
|
||||||
.setContentTitle("Gadgetbridge")
|
|
||||||
.setTicker(text)
|
|
||||||
.setContentText(text)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setOngoing(true).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateNotification(String text) {
|
|
||||||
|
|
||||||
Notification notification = createNotification(text);
|
|
||||||
|
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
nm.notify(NOTIFICATION_ID, notification);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void evaluateGBCommandBundle(GBDeviceCommand deviceCmd) {
|
|
||||||
switch (deviceCmd.commandClass) {
|
|
||||||
case MUSIC_CONTROL:
|
|
||||||
Log.i(TAG, "Got command for MUSIC_CONTROL");
|
|
||||||
GBDeviceCommandMusicControl musicCmd = (GBDeviceCommandMusicControl) deviceCmd;
|
|
||||||
Intent musicIntent = new Intent(GBMusicControlReceiver.ACTION_MUSICCONTROL);
|
|
||||||
musicIntent.putExtra("command", musicCmd.command.ordinal());
|
|
||||||
musicIntent.setPackage(this.getPackageName());
|
|
||||||
sendBroadcast(musicIntent);
|
|
||||||
break;
|
|
||||||
case CALL_CONTROL:
|
|
||||||
Log.i(TAG, "Got command for CALL_CONTROL");
|
|
||||||
GBDeviceCommandCallControl callCmd = (GBDeviceCommandCallControl) deviceCmd;
|
|
||||||
Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
|
|
||||||
callIntent.putExtra("command", callCmd.command.ordinal());
|
|
||||||
callIntent.setPackage(this.getPackageName());
|
|
||||||
sendBroadcast(callIntent);
|
|
||||||
break;
|
|
||||||
case VERSION_INFO:
|
|
||||||
Log.i(TAG, "Got command for VERSION_INFO");
|
|
||||||
if (mGBDevice == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GBDeviceCommandVersionInfo infoCmd = (GBDeviceCommandVersionInfo) deviceCmd;
|
|
||||||
mGBDevice.setFirmwareVersion(infoCmd.fwVersion);
|
|
||||||
sendDeviceUpdateIntent();
|
|
||||||
break;
|
|
||||||
case APP_INFO:
|
|
||||||
Log.i(TAG, "Got command for APP_INFO");
|
|
||||||
GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd;
|
|
||||||
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
|
|
||||||
((PebbleIoThread) mGBDeviceIoThread).setInstallSlot(appInfoCmd.freeSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST);
|
|
||||||
int appCount = appInfoCmd.apps.length;
|
|
||||||
appInfoIntent.putExtra("app_count", appCount);
|
|
||||||
for (Integer i = 0; i < appCount; i++) {
|
|
||||||
appInfoIntent.putExtra("app_name" + i.toString(), appInfoCmd.apps[i].getName());
|
|
||||||
appInfoIntent.putExtra("app_creator" + i.toString(), appInfoCmd.apps[i].getCreator());
|
|
||||||
appInfoIntent.putExtra("app_id" + i.toString(), appInfoCmd.apps[i].getId());
|
|
||||||
appInfoIntent.putExtra("app_index" + i.toString(), appInfoCmd.apps[i].getIndex());
|
|
||||||
appInfoIntent.putExtra("app_type" + i.toString(), appInfoCmd.apps[i].getType().ordinal());
|
|
||||||
}
|
|
||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(appInfoIntent);
|
|
||||||
break;
|
|
||||||
case APP_MANAGEMENT_RES:
|
|
||||||
GBDeviceCommandAppManagementResult appMgmtRes = (GBDeviceCommandAppManagementResult) deviceCmd;
|
|
||||||
switch (appMgmtRes.type) {
|
|
||||||
case DELETE:
|
|
||||||
// right now on the Pebble we also receive this on a failed/successful installation ;/
|
|
||||||
switch (appMgmtRes.result) {
|
|
||||||
case FAILURE:
|
|
||||||
Log.i(TAG, "failure removing app"); // TODO: report to AppManager
|
|
||||||
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
|
|
||||||
((PebbleIoThread) mGBDeviceIoThread).finishInstall(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
|
|
||||||
((PebbleIoThread) mGBDeviceIoThread).finishInstall(false);
|
|
||||||
}
|
|
||||||
// refresh app list
|
|
||||||
mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppInfoReq());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case INSTALL:
|
|
||||||
switch (appMgmtRes.result) {
|
|
||||||
case FAILURE:
|
|
||||||
Log.i(TAG, "failure installing app"); // TODO: report to Installer
|
|
||||||
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
|
|
||||||
((PebbleIoThread) mGBDeviceIoThread).finishInstall(true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
|
|
||||||
((PebbleIoThread) mGBDeviceIoThread).setToken(appMgmtRes.token);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendDeviceUpdateIntent() {
|
|
||||||
Intent deviceUpdateIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
|
||||||
deviceUpdateIntent.putExtra("device_address", mGBDevice.getAddress());
|
|
||||||
deviceUpdateIntent.putExtra("device_state", mGBDevice.getState().ordinal());
|
|
||||||
deviceUpdateIntent.putExtra("firmware_version", mGBDevice.getFirmwareVersion());
|
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(deviceUpdateIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
|
||||||
@ -264,8 +86,8 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!action.equals(ACTION_START) && !action.equals(ACTION_CONNECT) && mBtSocket == null) {
|
if (!action.equals(ACTION_START) && !action.equals(ACTION_CONNECT) && !isConnected()) {
|
||||||
// trying to send notification without valid Blutooth socket
|
// trying to send notification without valid Blutooth connection
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +103,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).commit();
|
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).commit();
|
||||||
|
|
||||||
if (btDeviceAddress != null && (mBtSocket == null || !mBtSocket.isConnected())) {
|
if (btDeviceAddress != null && !isConnected()) {
|
||||||
// currently only one thread allowed
|
// currently only one thread allowed
|
||||||
if (mGBDeviceIoThread != null) {
|
if (mGBDeviceIoThread != null) {
|
||||||
mGBDeviceIoThread.quit();
|
mGBDeviceIoThread.quit();
|
||||||
@ -294,20 +116,18 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
}
|
}
|
||||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
|
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
|
||||||
if (btDevice != null) {
|
if (btDevice != null) {
|
||||||
GBDevice.Type deviceType = GBDevice.Type.UNKNOWN;
|
|
||||||
if (btDevice.getName() == null || btDevice.getName().equals("MI")) { //FIXME: workaround for Miband not being paired
|
if (btDevice.getName() == null || btDevice.getName().equals("MI")) { //FIXME: workaround for Miband not being paired
|
||||||
deviceType = GBDevice.Type.MIBAND;
|
mGBDevice = new GBDevice(btDeviceAddress, btDevice.getName(), GBDevice.Type.MIBAND);
|
||||||
mGBDeviceProtocol = new MibandProtocol();
|
mGBDeviceProtocol = new MibandProtocol();
|
||||||
mGBDeviceIoThread = new MibandIoThread(btDeviceAddress);
|
mGBDeviceIoThread = new MibandIoThread(mGBDevice, this);
|
||||||
} else if (btDevice.getName().indexOf("Pebble") == 0) {
|
} else if (btDevice.getName().indexOf("Pebble") == 0) {
|
||||||
deviceType = GBDevice.Type.PEBBLE;
|
mGBDevice = new GBDevice(btDeviceAddress, btDevice.getName(), GBDevice.Type.PEBBLE);
|
||||||
mGBDeviceProtocol = new PebbleProtocol();
|
mGBDeviceProtocol = new PebbleProtocol();
|
||||||
mGBDeviceIoThread = new PebbleIoThread(btDeviceAddress);
|
mGBDeviceIoThread = new PebbleIoThread(mGBDevice, mGBDeviceProtocol, mBtAdapter, this);
|
||||||
}
|
}
|
||||||
if (mGBDeviceProtocol != null) {
|
if (mGBDeviceProtocol != null) {
|
||||||
mGBDevice = new GBDevice(btDeviceAddress, btDevice.getName(), deviceType);
|
|
||||||
mGBDevice.setState(GBDevice.State.CONNECTING);
|
mGBDevice.setState(GBDevice.State.CONNECTING);
|
||||||
sendDeviceUpdateIntent();
|
mGBDevice.sendDeviceUpdateIntent(this);
|
||||||
|
|
||||||
mGBDeviceIoThread.start();
|
mGBDeviceIoThread.start();
|
||||||
}
|
}
|
||||||
@ -354,7 +174,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
byte[] msg = mGBDeviceProtocol.encodeFirmwareVersionReq();
|
byte[] msg = mGBDeviceProtocol.encodeFirmwareVersionReq();
|
||||||
mGBDeviceIoThread.write(msg);
|
mGBDeviceIoThread.write(msg);
|
||||||
} else {
|
} else {
|
||||||
sendDeviceUpdateIntent();
|
mGBDevice.sendDeviceUpdateIntent(this);
|
||||||
}
|
}
|
||||||
} else if (action.equals(ACTION_REQUEST_APPINFO)) {
|
} else if (action.equals(ACTION_REQUEST_APPINFO)) {
|
||||||
mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppInfoReq());
|
mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppInfoReq());
|
||||||
@ -369,18 +189,22 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
((PebbleIoThread) mGBDeviceIoThread).installApp(Uri.parse(uriString));
|
((PebbleIoThread) mGBDeviceIoThread).installApp(Uri.parse(uriString));
|
||||||
}
|
}
|
||||||
} else if (action.equals(ACTION_START)) {
|
} else if (action.equals(ACTION_START)) {
|
||||||
startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running"));
|
startForeground(GB.NOTIFICATION_ID, GB.createNotification("Gadgetbridge running", this));
|
||||||
mStarted = true;
|
mStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isConnected() {
|
||||||
|
return mGBDevice != null && mGBDevice.getState() == State.CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
setReceiversEnableState(false); // disable BroadcastReceivers
|
GB.setReceiversEnableState(false, this); // disable BroadcastReceivers
|
||||||
|
|
||||||
if (mGBDeviceIoThread != null) {
|
if (mGBDeviceIoThread != null) {
|
||||||
try {
|
try {
|
||||||
@ -391,7 +215,7 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
nm.cancel(NOTIFICATION_ID); // need to do this because the updated notification wont be cancelled when service stops
|
nm.cancel(GB.NOTIFICATION_ID); // need to do this because the updated notification wont be cancelled when service stops
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -424,362 +248,4 @@ public class BluetoothCommunicationService extends Service {
|
|||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class GBDeviceIoThread extends Thread {
|
|
||||||
protected final String mmBtDeviceAddress;
|
|
||||||
|
|
||||||
public GBDeviceIoThread(String btDeviceAddress) {
|
|
||||||
mmBtDeviceAddress = btDeviceAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean connect(String btDeviceAddress) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void write(byte[] bytes) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void quit() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MibandIoThread extends GBDeviceIoThread {
|
|
||||||
public MibandIoThread(String btDeviceAddress) {
|
|
||||||
super(btDeviceAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
// implement connect() run() write() and quit() here
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum PebbleAppInstallState {
|
|
||||||
UNKNOWN,
|
|
||||||
APP_WAIT_SLOT,
|
|
||||||
APP_START_INSTALL,
|
|
||||||
APP_WAIT_TOKEN,
|
|
||||||
APP_UPLOAD_CHUNK,
|
|
||||||
APP_UPLOAD_COMMIT,
|
|
||||||
APP_WAIT_COMMMIT,
|
|
||||||
APP_UPLOAD_COMPLETE,
|
|
||||||
APP_REFRESH,
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PebbleIoThread extends GBDeviceIoThread {
|
|
||||||
private final PebbleProtocol mmPebbleProtocol;
|
|
||||||
private InputStream mmInStream = null;
|
|
||||||
private OutputStream mmOutStream = null;
|
|
||||||
private boolean mmQuit = false;
|
|
||||||
private boolean mmIsConnected = false;
|
|
||||||
private boolean mmIsInstalling = false;
|
|
||||||
private int mmConnectionAttempts = 0;
|
|
||||||
|
|
||||||
/* app installation */
|
|
||||||
private Uri mmInstallURI = null;
|
|
||||||
private PBWReader mmPBWReader = null;
|
|
||||||
private int mmAppInstallToken = -1;
|
|
||||||
private ZipInputStream mmZis = null;
|
|
||||||
private STM32CRC mmSTM32CRC = new STM32CRC();
|
|
||||||
private PebbleAppInstallState mmInstallState = PebbleAppInstallState.UNKNOWN;
|
|
||||||
private String[] mmFilesToInstall = null;
|
|
||||||
private int mmCurrentFileIndex = -1;
|
|
||||||
private int mmInstallSlot = -1;
|
|
||||||
|
|
||||||
public PebbleIoThread(String btDeviceAddress) {
|
|
||||||
super(btDeviceAddress);
|
|
||||||
mmPebbleProtocol = (PebbleProtocol) mGBDeviceProtocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean connect(String btDeviceAddress) {
|
|
||||||
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
|
|
||||||
ParcelUuid uuids[] = btDevice.getUuids();
|
|
||||||
try {
|
|
||||||
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
|
||||||
mBtSocket.connect();
|
|
||||||
mmInStream = mBtSocket.getInputStream();
|
|
||||||
mmOutStream = mBtSocket.getOutputStream();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
mmInStream = null;
|
|
||||||
mmOutStream = null;
|
|
||||||
mBtSocket = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mGBDevice.setState(GBDevice.State.CONNECTED);
|
|
||||||
sendDeviceUpdateIntent();
|
|
||||||
updateNotification("connected to " + btDevice.getName());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
mmIsConnected = connect(mmBtDeviceAddress);
|
|
||||||
setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers
|
|
||||||
mmQuit = !mmIsConnected; // quit if not connected
|
|
||||||
|
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
int bytes;
|
|
||||||
|
|
||||||
while (!mmQuit) {
|
|
||||||
try {
|
|
||||||
if (mmIsInstalling) {
|
|
||||||
switch (mmInstallState) {
|
|
||||||
case APP_WAIT_SLOT:
|
|
||||||
if (mmInstallSlot != -1) {
|
|
||||||
updateNotification("starting installation");
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_START_INSTALL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case APP_START_INSTALL:
|
|
||||||
Log.i(TAG, "start installing app binary");
|
|
||||||
mmSTM32CRC.reset();
|
|
||||||
if (mmPBWReader == null) {
|
|
||||||
mmPBWReader = new PBWReader(mmInstallURI, getApplicationContext());
|
|
||||||
mmFilesToInstall = mmPBWReader.getFilesToInstall();
|
|
||||||
mmCurrentFileIndex = 0;
|
|
||||||
}
|
|
||||||
String fileName = mmFilesToInstall[mmCurrentFileIndex];
|
|
||||||
mmZis = mmPBWReader.getInputStreamFile(fileName);
|
|
||||||
int binarySize = mmPBWReader.getFileSize(fileName);
|
|
||||||
// FIXME: do not assume type from filename, parse json correctly in PBWReader
|
|
||||||
byte type = -1;
|
|
||||||
if (fileName.equals("pebble-app.bin")) {
|
|
||||||
type = PebbleProtocol.PUTBYTES_TYPE_BINARY;
|
|
||||||
} else if (fileName.equals("pebble-worker.bin")) {
|
|
||||||
type = PebbleProtocol.PUTBYTES_TYPE_WORKER;
|
|
||||||
} else if (fileName.equals("app_resources.pbpack")) {
|
|
||||||
type = PebbleProtocol.PUTBYTES_TYPE_RESOURCES;
|
|
||||||
} else {
|
|
||||||
finishInstall(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeUploadStart(type, (byte) mmInstallSlot, binarySize));
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
|
|
||||||
break;
|
|
||||||
case APP_WAIT_TOKEN:
|
|
||||||
if (mmAppInstallToken != -1) {
|
|
||||||
Log.i(TAG, "got token " + mmAppInstallToken);
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_UPLOAD_CHUNK;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case APP_UPLOAD_CHUNK:
|
|
||||||
bytes = mmZis.read(buffer);
|
|
||||||
|
|
||||||
if (bytes != -1) {
|
|
||||||
mmSTM32CRC.addData(buffer, bytes);
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeUploadChunk(mmAppInstallToken, buffer, bytes));
|
|
||||||
mmAppInstallToken = -1;
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
|
|
||||||
} else {
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_UPLOAD_COMMIT;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case APP_UPLOAD_COMMIT:
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeUploadCommit(mmAppInstallToken, mmSTM32CRC.getResult()));
|
|
||||||
mmAppInstallToken = -1;
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_WAIT_COMMMIT;
|
|
||||||
break;
|
|
||||||
case APP_WAIT_COMMMIT:
|
|
||||||
if (mmAppInstallToken != -1) {
|
|
||||||
Log.i(TAG, "got token " + mmAppInstallToken);
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_UPLOAD_COMPLETE;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case APP_UPLOAD_COMPLETE:
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeUploadComplete(mmAppInstallToken));
|
|
||||||
if (++mmCurrentFileIndex < mmFilesToInstall.length) {
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_START_INSTALL;
|
|
||||||
} else {
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_REFRESH;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case APP_REFRESH:
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeAppRefresh(mmInstallSlot));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(mmPebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
|
|
||||||
write(mmPebbleProtocol.encodeFirmwareVersionReq());
|
|
||||||
|
|
||||||
// this does not really belong here, but since the pebble only asks for our version once it should do the job
|
|
||||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(BluetoothCommunicationService.this);
|
|
||||||
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
|
|
||||||
Log.i(TAG, "syncing time");
|
|
||||||
write(mmPebbleProtocol.encodeSetTime(-1));
|
|
||||||
}
|
|
||||||
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
|
|
||||||
GBDeviceCommand deviceCmd = mmPebbleProtocol.decodeResponse(buffer);
|
|
||||||
if (deviceCmd == null) {
|
|
||||||
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
|
|
||||||
} else {
|
|
||||||
evaluateGBCommandBundle(deviceCmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
|
|
||||||
Log.i(TAG, e.getMessage());
|
|
||||||
mGBDevice.setState(GBDevice.State.CONNECTING);
|
|
||||||
sendDeviceUpdateIntent();
|
|
||||||
updateNotification("connection lost, trying to reconnect");
|
|
||||||
|
|
||||||
while (mmConnectionAttempts++ < 10) {
|
|
||||||
Log.i(TAG, "Trying to reconnect (attempt " + mmConnectionAttempts + ")");
|
|
||||||
mmIsConnected = connect(mmBtDeviceAddress);
|
|
||||||
if (mmIsConnected)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mmConnectionAttempts = 0;
|
|
||||||
if (!mmIsConnected) {
|
|
||||||
mBtSocket = null;
|
|
||||||
setReceiversEnableState(false);
|
|
||||||
Log.i(TAG, "Bluetooth socket closed, will quit IO Thread");
|
|
||||||
mmQuit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mmIsConnected = false;
|
|
||||||
if (mBtSocket != null) {
|
|
||||||
try {
|
|
||||||
mBtSocket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mBtSocket = null;
|
|
||||||
updateNotification("not connected");
|
|
||||||
mGBDevice.setState(GBDevice.State.NOT_CONNECTED);
|
|
||||||
sendDeviceUpdateIntent();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void write(byte[] bytes) {
|
|
||||||
// block writes if app installation in in progress
|
|
||||||
if (mmIsConnected && !mmIsInstalling) {
|
|
||||||
try {
|
|
||||||
mmOutStream.write(bytes);
|
|
||||||
mmOutStream.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToken(int token) {
|
|
||||||
mmAppInstallToken = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInstallSlot(int slot) {
|
|
||||||
if (mmIsInstalling) {
|
|
||||||
mmInstallSlot = slot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeInstallApp(byte[] bytes) {
|
|
||||||
if (!mmIsInstalling) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int length = bytes.length;
|
|
||||||
Log.i(TAG, "got bytes for writeInstallApp()" + length);
|
|
||||||
/*
|
|
||||||
final char[] hexArray = "0123456789ABCDEF".toCharArray();
|
|
||||||
char[] hexChars = new char[length * 2];
|
|
||||||
for (int j = 0; j < length; j++) {
|
|
||||||
int v = bytes[j] & 0xFF;
|
|
||||||
hexChars[j * 2] = hexArray[v >>> 4];
|
|
||||||
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
|
||||||
}
|
|
||||||
Log.i(TAG, new String(hexChars));
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
mmOutStream.write(bytes);
|
|
||||||
mmOutStream.flush();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void installApp(Uri uri) {
|
|
||||||
if (mmIsInstalling) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
write(mmPebbleProtocol.encodeAppInfoReq()); // do this here to get run() out of its blocking read
|
|
||||||
mmInstallState = PebbleAppInstallState.APP_WAIT_SLOT;
|
|
||||||
mmInstallURI = uri;
|
|
||||||
mmIsInstalling = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finishInstall(boolean hadError) {
|
|
||||||
if (!mmIsInstalling) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (hadError) {
|
|
||||||
updateNotification("installation failed!");
|
|
||||||
} else {
|
|
||||||
updateNotification("installation successful");
|
|
||||||
}
|
|
||||||
mmInstallState = PebbleAppInstallState.UNKNOWN;
|
|
||||||
|
|
||||||
if (hadError == true && mmAppInstallToken != -1) {
|
|
||||||
writeInstallApp(mmPebbleProtocol.encodeUploadCancel(mmAppInstallToken));
|
|
||||||
}
|
|
||||||
|
|
||||||
mmPBWReader = null;
|
|
||||||
mmIsInstalling = false;
|
|
||||||
mmZis = null;
|
|
||||||
mmAppInstallToken = -1;
|
|
||||||
mmInstallSlot = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void quit() {
|
|
||||||
mmQuit = true;
|
|
||||||
if (mBtSocket != null) {
|
|
||||||
try {
|
|
||||||
mBtSocket.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
|
public class GB {
|
||||||
|
public static final int NOTIFICATION_ID = 1;
|
||||||
|
|
||||||
|
public static Notification createNotification(String text, Context context) {
|
||||||
|
Intent notificationIntent = new Intent(context, ControlCenter.class);
|
||||||
|
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
|
||||||
|
notificationIntent, 0);
|
||||||
|
|
||||||
|
return new NotificationCompat.Builder(context)
|
||||||
|
.setContentTitle("Gadgetbridge")
|
||||||
|
.setTicker(text)
|
||||||
|
.setContentText(text)
|
||||||
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setOngoing(true).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateNotification(String text, Context context) {
|
||||||
|
Notification notification = createNotification(text, context);
|
||||||
|
|
||||||
|
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
nm.notify(NOTIFICATION_ID, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setReceiversEnableState(boolean enable, Context context) {
|
||||||
|
final Class[] receiverClasses = {
|
||||||
|
PhoneCallReceiver.class,
|
||||||
|
SMSReceiver.class,
|
||||||
|
K9Receiver.class,
|
||||||
|
MusicPlaybackReceiver.class,
|
||||||
|
//NotificationListener.class, // disabling this leads to loss of permission to read notifications
|
||||||
|
};
|
||||||
|
|
||||||
|
int newState;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
newState = PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||||
|
} else {
|
||||||
|
newState = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
|
||||||
|
for (Class receiverClass : receiverClasses) {
|
||||||
|
ComponentName compName = new ComponentName(context, receiverClass);
|
||||||
|
|
||||||
|
pm.setComponentEnabledSetting(compName, newState, PackageManager.DONT_KILL_APP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,9 @@
|
|||||||
package nodomain.freeyourgadget.gadgetbridge;
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
|
||||||
public class GBDevice {
|
public class GBDevice {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String address;
|
private final String address;
|
||||||
@ -61,6 +65,15 @@ public class GBDevice {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendDeviceUpdateIntent(Context context) {
|
||||||
|
Intent deviceUpdateIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||||
|
deviceUpdateIntent.putExtra("device_address", getAddress());
|
||||||
|
deviceUpdateIntent.putExtra("device_state", getState().ordinal());
|
||||||
|
deviceUpdateIntent.putExtra("firmware_version", getFirmwareVersion());
|
||||||
|
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(deviceUpdateIntent);
|
||||||
|
}
|
||||||
|
|
||||||
public enum State {
|
public enum State {
|
||||||
NOT_CONNECTED,
|
NOT_CONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
public abstract class GBDeviceIoThread extends Thread {
|
||||||
|
protected final GBDevice gbDevice;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public GBDeviceIoThread(GBDevice gbDevice, Context context) {
|
||||||
|
this.gbDevice = gbDevice;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GBDevice getDevice() {
|
||||||
|
return gbDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean connect(String btDeviceAddress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public void write(byte[] bytes) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quit() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
class MibandIoThread extends GBDeviceIoThread {
|
||||||
|
public MibandIoThread(GBDevice gbDevice, Context context) {
|
||||||
|
super(gbDevice, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// implement connect() run() write() and quit() here
|
||||||
|
}
|
@ -0,0 +1,455 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.pebble;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.AppManagerActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GB;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBCallControlReceiver;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBDeviceIoThread;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBMusicControlReceiver;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommand;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandCallControl;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandMusicControl;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandVersionInfo;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceProtocol;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.protocol.PebbleProtocol;
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelUuid;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class PebbleIoThread extends GBDeviceIoThread {
|
||||||
|
private static final String TAG = PebbleIoThread.class.getSimpleName();
|
||||||
|
|
||||||
|
private enum PebbleAppInstallState {
|
||||||
|
UNKNOWN,
|
||||||
|
APP_WAIT_SLOT,
|
||||||
|
APP_START_INSTALL,
|
||||||
|
APP_WAIT_TOKEN,
|
||||||
|
APP_UPLOAD_CHUNK,
|
||||||
|
APP_UPLOAD_COMMIT,
|
||||||
|
APP_WAIT_COMMMIT,
|
||||||
|
APP_UPLOAD_COMPLETE,
|
||||||
|
APP_REFRESH,
|
||||||
|
}
|
||||||
|
|
||||||
|
private final PebbleProtocol mmPebbleProtocol;
|
||||||
|
private InputStream mmInStream = null;
|
||||||
|
private OutputStream mmOutStream = null;
|
||||||
|
private boolean mmQuit = false;
|
||||||
|
private boolean mmIsConnected = false;
|
||||||
|
private boolean mmIsInstalling = false;
|
||||||
|
private int mmConnectionAttempts = 0;
|
||||||
|
|
||||||
|
/* app installation */
|
||||||
|
private Uri mmInstallURI = null;
|
||||||
|
private PBWReader mmPBWReader = null;
|
||||||
|
private int mmAppInstallToken = -1;
|
||||||
|
private ZipInputStream mmZis = null;
|
||||||
|
private STM32CRC mmSTM32CRC = new STM32CRC();
|
||||||
|
private PebbleAppInstallState mmInstallState = PebbleAppInstallState.UNKNOWN;
|
||||||
|
private String[] mmFilesToInstall = null;
|
||||||
|
private int mmCurrentFileIndex = -1;
|
||||||
|
private int mmInstallSlot = -1;
|
||||||
|
private BluetoothAdapter mBtAdapter = null;
|
||||||
|
private BluetoothSocket mBtSocket = null;
|
||||||
|
|
||||||
|
public PebbleIoThread(GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) {
|
||||||
|
super(gbDevice, context);
|
||||||
|
mmPebbleProtocol = (PebbleProtocol) gbDeviceProtocol;
|
||||||
|
mBtAdapter = btAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean connect(String btDeviceAddress) {
|
||||||
|
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
|
||||||
|
ParcelUuid uuids[] = btDevice.getUuids();
|
||||||
|
try {
|
||||||
|
mBtSocket = btDevice.createRfcommSocketToServiceRecord(uuids[0].getUuid());
|
||||||
|
mBtSocket.connect();
|
||||||
|
mmInStream = mBtSocket.getInputStream();
|
||||||
|
mmOutStream = mBtSocket.getOutputStream();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
|
||||||
|
mmInStream = null;
|
||||||
|
mmOutStream = null;
|
||||||
|
mBtSocket = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gbDevice.setState(GBDevice.State.CONNECTED);
|
||||||
|
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||||
|
GB.updateNotification("connected to " + btDevice.getName(), getContext());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
mmIsConnected = connect(gbDevice.getAddress());
|
||||||
|
GB.setReceiversEnableState(mmIsConnected, getContext()); // enable/disable BroadcastReceivers
|
||||||
|
mmQuit = !mmIsConnected; // quit if not connected
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytes;
|
||||||
|
|
||||||
|
while (!mmQuit) {
|
||||||
|
try {
|
||||||
|
if (mmIsInstalling) {
|
||||||
|
switch (mmInstallState) {
|
||||||
|
case APP_WAIT_SLOT:
|
||||||
|
if (mmInstallSlot != -1) {
|
||||||
|
GB.updateNotification("starting installation", getContext());
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_START_INSTALL;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APP_START_INSTALL:
|
||||||
|
Log.i(TAG, "start installing app binary");
|
||||||
|
mmSTM32CRC.reset();
|
||||||
|
if (mmPBWReader == null) {
|
||||||
|
mmPBWReader = new PBWReader(mmInstallURI, getContext());
|
||||||
|
mmFilesToInstall = mmPBWReader.getFilesToInstall();
|
||||||
|
mmCurrentFileIndex = 0;
|
||||||
|
}
|
||||||
|
String fileName = mmFilesToInstall[mmCurrentFileIndex];
|
||||||
|
mmZis = mmPBWReader.getInputStreamFile(fileName);
|
||||||
|
int binarySize = mmPBWReader.getFileSize(fileName);
|
||||||
|
// FIXME: do not assume type from filename, parse json correctly in PBWReader
|
||||||
|
byte type = -1;
|
||||||
|
if (fileName.equals("pebble-app.bin")) {
|
||||||
|
type = PebbleProtocol.PUTBYTES_TYPE_BINARY;
|
||||||
|
} else if (fileName.equals("pebble-worker.bin")) {
|
||||||
|
type = PebbleProtocol.PUTBYTES_TYPE_WORKER;
|
||||||
|
} else if (fileName.equals("app_resources.pbpack")) {
|
||||||
|
type = PebbleProtocol.PUTBYTES_TYPE_RESOURCES;
|
||||||
|
} else {
|
||||||
|
finishInstall(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeUploadStart(type, (byte) mmInstallSlot, binarySize));
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
|
||||||
|
break;
|
||||||
|
case APP_WAIT_TOKEN:
|
||||||
|
if (mmAppInstallToken != -1) {
|
||||||
|
Log.i(TAG, "got token " + mmAppInstallToken);
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_UPLOAD_CHUNK;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APP_UPLOAD_CHUNK:
|
||||||
|
bytes = mmZis.read(buffer);
|
||||||
|
|
||||||
|
if (bytes != -1) {
|
||||||
|
mmSTM32CRC.addData(buffer, bytes);
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeUploadChunk(mmAppInstallToken, buffer, bytes));
|
||||||
|
mmAppInstallToken = -1;
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
|
||||||
|
} else {
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_UPLOAD_COMMIT;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APP_UPLOAD_COMMIT:
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeUploadCommit(mmAppInstallToken, mmSTM32CRC.getResult()));
|
||||||
|
mmAppInstallToken = -1;
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_WAIT_COMMMIT;
|
||||||
|
break;
|
||||||
|
case APP_WAIT_COMMMIT:
|
||||||
|
if (mmAppInstallToken != -1) {
|
||||||
|
Log.i(TAG, "got token " + mmAppInstallToken);
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_UPLOAD_COMPLETE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APP_UPLOAD_COMPLETE:
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeUploadComplete(mmAppInstallToken));
|
||||||
|
if (++mmCurrentFileIndex < mmFilesToInstall.length) {
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_START_INSTALL;
|
||||||
|
} else {
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_REFRESH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APP_REFRESH:
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeAppRefresh(mmInstallSlot));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(mmPebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
|
||||||
|
write(mmPebbleProtocol.encodeFirmwareVersionReq());
|
||||||
|
|
||||||
|
// this does not really belong here, but since the pebble only asks for our version once it should do the job
|
||||||
|
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
|
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
|
||||||
|
Log.i(TAG, "syncing time");
|
||||||
|
write(mmPebbleProtocol.encodeSetTime(-1));
|
||||||
|
}
|
||||||
|
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
|
||||||
|
GBDeviceCommand deviceCmd = mmPebbleProtocol.decodeResponse(buffer);
|
||||||
|
if (deviceCmd == null) {
|
||||||
|
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
|
||||||
|
} else {
|
||||||
|
evaluateGBCommandBundle(deviceCmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
|
||||||
|
Log.i(TAG, e.getMessage());
|
||||||
|
gbDevice.setState(GBDevice.State.CONNECTING);
|
||||||
|
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||||
|
GB.updateNotification("connection lost, trying to reconnect", getContext());
|
||||||
|
|
||||||
|
while (mmConnectionAttempts++ < 10) {
|
||||||
|
Log.i(TAG, "Trying to reconnect (attempt " + mmConnectionAttempts + ")");
|
||||||
|
mmIsConnected = connect(gbDevice.getAddress());
|
||||||
|
if (mmIsConnected)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mmConnectionAttempts = 0;
|
||||||
|
if (!mmIsConnected) {
|
||||||
|
mBtSocket = null;
|
||||||
|
GB.setReceiversEnableState(false, getContext());
|
||||||
|
Log.i(TAG, "Bluetooth socket closed, will quit IO Thread");
|
||||||
|
mmQuit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mmIsConnected = false;
|
||||||
|
if (mBtSocket != null) {
|
||||||
|
try {
|
||||||
|
mBtSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mBtSocket = null;
|
||||||
|
GB.updateNotification("not connected", getContext());
|
||||||
|
gbDevice.setState(GBDevice.State.NOT_CONNECTED);
|
||||||
|
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public void write(byte[] bytes) {
|
||||||
|
// block writes if app installation in in progress
|
||||||
|
if (mmIsConnected && !mmIsInstalling) {
|
||||||
|
try {
|
||||||
|
mmOutStream.write(bytes);
|
||||||
|
mmOutStream.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void evaluateGBCommandBundle(GBDeviceCommand deviceCmd) {
|
||||||
|
Context context = getContext();
|
||||||
|
|
||||||
|
switch (deviceCmd.commandClass) {
|
||||||
|
case MUSIC_CONTROL:
|
||||||
|
Log.i(TAG, "Got command for MUSIC_CONTROL");
|
||||||
|
GBDeviceCommandMusicControl musicCmd = (GBDeviceCommandMusicControl) deviceCmd;
|
||||||
|
Intent musicIntent = new Intent(GBMusicControlReceiver.ACTION_MUSICCONTROL);
|
||||||
|
musicIntent.putExtra("command", musicCmd.command.ordinal());
|
||||||
|
musicIntent.setPackage(context.getPackageName());
|
||||||
|
context.sendBroadcast(musicIntent);
|
||||||
|
break;
|
||||||
|
case CALL_CONTROL:
|
||||||
|
Log.i(TAG, "Got command for CALL_CONTROL");
|
||||||
|
GBDeviceCommandCallControl callCmd = (GBDeviceCommandCallControl) deviceCmd;
|
||||||
|
Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
|
||||||
|
callIntent.putExtra("command", callCmd.command.ordinal());
|
||||||
|
callIntent.setPackage(context.getPackageName());
|
||||||
|
context.sendBroadcast(callIntent);
|
||||||
|
break;
|
||||||
|
case VERSION_INFO:
|
||||||
|
Log.i(TAG, "Got command for VERSION_INFO");
|
||||||
|
if (gbDevice == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GBDeviceCommandVersionInfo infoCmd = (GBDeviceCommandVersionInfo) deviceCmd;
|
||||||
|
gbDevice.setFirmwareVersion(infoCmd.fwVersion);
|
||||||
|
gbDevice.sendDeviceUpdateIntent(context);
|
||||||
|
break;
|
||||||
|
case APP_INFO:
|
||||||
|
Log.i(TAG, "Got command for APP_INFO");
|
||||||
|
GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd;
|
||||||
|
setInstallSlot(appInfoCmd.freeSlot);
|
||||||
|
|
||||||
|
Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST);
|
||||||
|
int appCount = appInfoCmd.apps.length;
|
||||||
|
appInfoIntent.putExtra("app_count", appCount);
|
||||||
|
for (Integer i = 0; i < appCount; i++) {
|
||||||
|
appInfoIntent.putExtra("app_name" + i.toString(), appInfoCmd.apps[i].getName());
|
||||||
|
appInfoIntent.putExtra("app_creator" + i.toString(), appInfoCmd.apps[i].getCreator());
|
||||||
|
appInfoIntent.putExtra("app_id" + i.toString(), appInfoCmd.apps[i].getId());
|
||||||
|
appInfoIntent.putExtra("app_index" + i.toString(), appInfoCmd.apps[i].getIndex());
|
||||||
|
appInfoIntent.putExtra("app_type" + i.toString(), appInfoCmd.apps[i].getType().ordinal());
|
||||||
|
}
|
||||||
|
LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
|
||||||
|
break;
|
||||||
|
case APP_MANAGEMENT_RES:
|
||||||
|
GBDeviceCommandAppManagementResult appMgmtRes = (GBDeviceCommandAppManagementResult) deviceCmd;
|
||||||
|
switch (appMgmtRes.type) {
|
||||||
|
case DELETE:
|
||||||
|
// right now on the Pebble we also receive this on a failed/successful installation ;/
|
||||||
|
switch (appMgmtRes.result) {
|
||||||
|
case FAILURE:
|
||||||
|
Log.i(TAG, "failure removing app"); // TODO: report to AppManager
|
||||||
|
finishInstall(true);
|
||||||
|
break;
|
||||||
|
case SUCCESS:
|
||||||
|
finishInstall(false);
|
||||||
|
// refresh app list
|
||||||
|
write(mmPebbleProtocol.encodeAppInfoReq());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INSTALL:
|
||||||
|
switch (appMgmtRes.result) {
|
||||||
|
case FAILURE:
|
||||||
|
Log.i(TAG, "failure installing app"); // TODO: report to Installer
|
||||||
|
finishInstall(true);
|
||||||
|
break;
|
||||||
|
case SUCCESS:
|
||||||
|
setToken(appMgmtRes.token);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(int token) {
|
||||||
|
mmAppInstallToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInstallSlot(int slot) {
|
||||||
|
if (mmIsInstalling) {
|
||||||
|
mmInstallSlot = slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeInstallApp(byte[] bytes) {
|
||||||
|
if (!mmIsInstalling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int length = bytes.length;
|
||||||
|
Log.i(TAG, "got bytes for writeInstallApp()" + length);
|
||||||
|
/*
|
||||||
|
final char[] hexArray = "0123456789ABCDEF".toCharArray();
|
||||||
|
char[] hexChars = new char[length * 2];
|
||||||
|
for (int j = 0; j < length; j++) {
|
||||||
|
int v = bytes[j] & 0xFF;
|
||||||
|
hexChars[j * 2] = hexArray[v >>> 4];
|
||||||
|
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
|
||||||
|
}
|
||||||
|
Log.i(TAG, new String(hexChars));
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
mmOutStream.write(bytes);
|
||||||
|
mmOutStream.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void installApp(Uri uri) {
|
||||||
|
if (mmIsInstalling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
write(mmPebbleProtocol.encodeAppInfoReq()); // do this here to get run() out of its blocking read
|
||||||
|
mmInstallState = PebbleAppInstallState.APP_WAIT_SLOT;
|
||||||
|
mmInstallURI = uri;
|
||||||
|
mmIsInstalling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void finishInstall(boolean hadError) {
|
||||||
|
if (!mmIsInstalling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hadError) {
|
||||||
|
GB.updateNotification("installation failed!", getContext());
|
||||||
|
} else {
|
||||||
|
GB.updateNotification("installation successful", getContext());
|
||||||
|
}
|
||||||
|
mmInstallState = PebbleAppInstallState.UNKNOWN;
|
||||||
|
|
||||||
|
if (hadError == true && mmAppInstallToken != -1) {
|
||||||
|
writeInstallApp(mmPebbleProtocol.encodeUploadCancel(mmAppInstallToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
mmPBWReader = null;
|
||||||
|
mmIsInstalling = false;
|
||||||
|
mmZis = null;
|
||||||
|
mmAppInstallToken = -1;
|
||||||
|
mmInstallSlot = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quit() {
|
||||||
|
mmQuit = true;
|
||||||
|
if (mBtSocket != null) {
|
||||||
|
try {
|
||||||
|
mBtSocket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user