1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-27 20:36:51 +01:00

Pebble: working on app installation (WIP, do not use)

This commit is contained in:
Andreas Shimokawa 2015-04-06 20:58:35 +02:00
parent 1766c82ab8
commit 2c1cacedb3
10 changed files with 464 additions and 119 deletions

View File

@ -40,7 +40,7 @@
android:value=".ControlCenter" /> android:value=".ControlCenter" />
</activity> </activity>
<activity <activity
android:name=".AppInstallerActivity" android:name=".pebble.PebbleAppInstallerActivity"
android:label="@string/title_activity_appinstaller"> android:label="@string/title_activity_appinstaller">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"

View File

@ -1,98 +0,0 @@
package nodomain.freeyourgadget.gadgetbridge;
import android.app.Activity;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import android.widget.TextView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class AppInstallerActivity extends Activity {
private final String TAG = this.getClass().getSimpleName();
TextView debugTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appinstaller);
getActionBar().setDisplayHomeAsUpEnabled(true);
debugTextView = (TextView) findViewById(R.id.debugTextView);
debugTextView.setText("contents:\n");
Uri uri = getIntent().getData();
ContentResolver cr = getContentResolver();
InputStream fin = null;
try {
fin = cr.openInputStream(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze = null;
GBDeviceApp app;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals("appinfo.json")) {
long bytes = ze.getSize();
if (bytes > 8192) // that should be too much
break;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = zis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
String jsonString = baos.toString();
try {
JSONObject json = new JSONObject(jsonString);
String appName = json.getString("shortName");
String appCreator = json.getString("companyName");
String appVersion = json.getString("versionLabel");
if (appName != null && appCreator != null && appVersion != null) {
debugTextView.setText("This is just a test, you cant install anything yet \n\n" + appName + " Version " + appVersion + " by " + appCreator + "\n");
}
} catch (JSONException e) {
e.printStackTrace();
break;
}
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}

View File

@ -29,7 +29,9 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.zip.ZipInputStream;
import nodomain.freeyourgadget.gadgetbridge.pebble.PBWReader;
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommand; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommand;
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppInfo;
import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult; import nodomain.freeyourgadget.gadgetbridge.protocol.GBDeviceCommandAppManagementResult;
@ -63,8 +65,10 @@ public class BluetoothCommunicationService extends Service {
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.request_appinfo"; = "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.request_appinfo";
public static final String ACTION_DELETEAPP public static final String ACTION_DELETEAPP
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.deleteapp"; = "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.deleteapp";
public static final String ACTION_INSTALL_PEBBLEAPP
= "nodomain.freeyourgadget.gadgetbride.bluetoothcommunicationservice.action.install_pebbbleapp";
private static final String TAG = "BluetoothCommunicationService"; private static final String TAG = "CommunicationService";
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private BluetoothAdapter mBtAdapter = null; private BluetoothAdapter mBtAdapter = null;
private BluetoothSocket mBtSocket = null; private BluetoothSocket mBtSocket = null;
@ -161,6 +165,7 @@ public class BluetoothCommunicationService extends Service {
case APP_INFO: case APP_INFO:
Log.i(TAG, "Got command for APP_INFO"); Log.i(TAG, "Got command for APP_INFO");
GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd; GBDeviceCommandAppInfo appInfoCmd = (GBDeviceCommandAppInfo) deviceCmd;
mGBDevice.setFreeAppSlot(appInfoCmd.freeSlot);
Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST); Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST);
int appCount = appInfoCmd.apps.length; int appCount = appInfoCmd.apps.length;
appInfoIntent.putExtra("app_count", appCount); appInfoIntent.putExtra("app_count", appCount);
@ -189,6 +194,20 @@ public class BluetoothCommunicationService extends Service {
break; break;
} }
break; break;
case INSTALL:
switch (appMgmtRes.result) {
case FAILURE:
Log.i(TAG, "failure installing app"); // TODO: report to Installer
break;
case SUCCESS:
if (mGBDevice.getType() == GBDevice.Type.PEBBLE) {
((PebbleIoThread) mGBDeviceIoThread).setToken(appMgmtRes.token);
}
break;
default:
break;
}
break;
default: default:
break; break;
} }
@ -329,6 +348,12 @@ public class BluetoothCommunicationService extends Service {
int id = intent.getIntExtra("app_id", -1); int id = intent.getIntExtra("app_id", -1);
int index = intent.getIntExtra("app_index", -1); int index = intent.getIntExtra("app_index", -1);
mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppDelete(id, index)); mGBDeviceIoThread.write(mGBDeviceProtocol.encodeAppDelete(id, index));
} else if (action.equals(ACTION_INSTALL_PEBBLEAPP)) {
String uriString = intent.getStringExtra("app_uri");
if (uriString != null && mGBDevice.getFreeAppSlot() != -1) {
Log.i(TAG, "will try to install app in slot " + mGBDevice.getFreeAppSlot());
((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(NOTIFICATION_ID, createNotification("Gadgetbridge running"));
mStarted = true; mStarted = true;
@ -415,15 +440,37 @@ public class BluetoothCommunicationService extends Service {
// implement connect() run() write() and quit() here // implement connect() run() write() and quit() here
} }
private enum PebbleAppInstallState {
UNKNOWN,
APP_START_INSTALL,
APP_WAIT_TOKEN,
APP_UPLOAD_CHUNK,
APP_UPLOAD_COMPLETE,
RES_START_INSTALL,
RES_WAIT_TOKEN,
RES_UPLOAD_CHUNK,
RES_UPLOAD_COMPLETE,
}
private class PebbleIoThread extends GBDeviceIoThread { private class PebbleIoThread extends GBDeviceIoThread {
private final PebbleProtocol mmPebbleProtocol;
private InputStream mmInStream = null; private InputStream mmInStream = null;
private OutputStream mmOutStream = null; private OutputStream mmOutStream = null;
private boolean mQuit = false; private boolean mmQuit = false;
private boolean mmIsConnected = false; private boolean mmIsConnected = false;
private boolean mmIsInstalling = false;
private int mmConnectionAttempts = 0; private int mmConnectionAttempts = 0;
/* app installation */
private Uri mmInstallURI = null;
private PBWReader mmPBWReader = null;
private int mmAppInstallToken = -1;
private ZipInputStream mmZis = null;
private PebbleAppInstallState mmInstallState = PebbleAppInstallState.UNKNOWN;
public PebbleIoThread(String btDeviceAddress) { public PebbleIoThread(String btDeviceAddress) {
super(btDeviceAddress); super(btDeviceAddress);
mmPebbleProtocol = (PebbleProtocol) mGBDeviceProtocol;
} }
private boolean connect(String btDeviceAddress) { private boolean connect(String btDeviceAddress) {
@ -451,13 +498,48 @@ public class BluetoothCommunicationService extends Service {
public void run() { public void run() {
mmIsConnected = connect(mmBtDeviceAddress); mmIsConnected = connect(mmBtDeviceAddress);
setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers setReceiversEnableState(mmIsConnected); // enable/disable BroadcastReceivers
mQuit = !mmIsConnected; // quit if not connected mmQuit = !mmIsConnected; // quit if not connected
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int bytes; int bytes;
while (!mQuit) { while (!mmQuit) {
try { try {
if (mmIsInstalling) {
switch (mmInstallState) {
case APP_START_INSTALL:
Log.i(TAG, "start installing app binary");
mmPBWReader = new PBWReader(mmInstallURI, getApplicationContext());
mmZis = mmPBWReader.getInputStreamAppBinary();
int binarySize = mmPBWReader.getAppBinarySize();
writeInstallApp(mmPebbleProtocol.encodeUploadStart(PebbleProtocol.PUTBYTES_TYPE_BINARY, mGBDevice.getFreeAppSlot(), binarySize), -1);
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) {
writeInstallApp(mmPebbleProtocol.encodeUploadChunk(mmAppInstallToken, buffer, bytes), -1);
mmAppInstallToken = -1;
mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN;
} else {
mmInstallState = PebbleAppInstallState.UNKNOWN;
mmIsInstalling = false;
mmZis = null;
mmAppInstallToken = -1;
}
break;
default:
break;
}
}
bytes = mmInStream.read(buffer, 0, 4); bytes = mmInStream.read(buffer, 0, 4);
if (bytes < 4) if (bytes < 4)
continue; continue;
@ -488,17 +570,17 @@ public class BluetoothCommunicationService extends Service {
if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) { if (length == 1 && endpoint == PebbleProtocol.ENDPOINT_PHONEVERSION) {
Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!"); Log.i(TAG, "Pebble asked for Phone/App Version - repLYING!");
write(mGBDeviceProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID)); write(mmPebbleProtocol.encodePhoneVersion(PebbleProtocol.PHONEVERSION_REMOTE_OS_ANDROID));
write(mGBDeviceProtocol.encodeFirmwareVersionReq()); write(mmPebbleProtocol.encodeFirmwareVersionReq());
// this does not really belong here, but since the pebble only asks for our version once it should do the job // 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); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(BluetoothCommunicationService.this);
if (sharedPrefs.getBoolean("datetime_synconconnect", true)) { if (sharedPrefs.getBoolean("datetime_synconconnect", true)) {
Log.i(TAG, "syncing time"); Log.i(TAG, "syncing time");
write(mGBDeviceProtocol.encodeSetTime(-1)); write(mmPebbleProtocol.encodeSetTime(-1));
} }
} else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) { } else if (endpoint != PebbleProtocol.ENDPOINT_DATALOG) {
GBDeviceCommand deviceCmd = mGBDeviceProtocol.decodeResponse(buffer); GBDeviceCommand deviceCmd = mmPebbleProtocol.decodeResponse(buffer);
if (deviceCmd == null) { if (deviceCmd == null) {
Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)"); Log.i(TAG, "unhandled message to endpoint " + endpoint + " (" + bytes + " bytes)");
} else { } else {
@ -512,6 +594,7 @@ public class BluetoothCommunicationService extends Service {
} }
} catch (IOException e) { } catch (IOException e) {
if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right if (e.getMessage().contains("socket closed")) { //FIXME: this does not feel right
Log.i(TAG, e.getMessage());
mGBDevice.setState(GBDevice.State.CONNECTING); mGBDevice.setState(GBDevice.State.CONNECTING);
sendDeviceUpdateIntent(); sendDeviceUpdateIntent();
updateNotification("connection lost, trying to reconnect"); updateNotification("connection lost, trying to reconnect");
@ -527,7 +610,7 @@ public class BluetoothCommunicationService extends Service {
mBtSocket = null; mBtSocket = null;
setReceiversEnableState(false); setReceiversEnableState(false);
Log.i(TAG, "Bluetooth socket closed, will quit IO Thread"); Log.i(TAG, "Bluetooth socket closed, will quit IO Thread");
mQuit = true; mmQuit = true;
} }
} }
} }
@ -547,7 +630,8 @@ public class BluetoothCommunicationService extends Service {
} }
synchronized public void write(byte[] bytes) { synchronized public void write(byte[] bytes) {
if (mmIsConnected) { // block writes if app installation in in progress
if (mmIsConnected && !mmIsInstalling) {
try { try {
mmOutStream.write(bytes); mmOutStream.write(bytes);
mmOutStream.flush(); mmOutStream.flush();
@ -556,8 +640,41 @@ public class BluetoothCommunicationService extends Service {
} }
} }
public void setToken(int token) {
mmAppInstallToken = token;
}
private void writeInstallApp(byte[] bytes, int length) {
if (!mmIsInstalling) {
return;
}
if (length == -1) {
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) {
mmInstallState = PebbleAppInstallState.APP_START_INSTALL;
mmIsInstalling = true;
mmInstallURI = uri;
}
public void quit() { public void quit() {
mQuit = true; mmQuit = true;
if (mBtSocket != null) { if (mBtSocket != null) {
try { try {
mBtSocket.close(); mBtSocket.close();

View File

@ -6,6 +6,7 @@ public class GBDevice {
private final Type type; private final Type type;
private String firmwareVersion = null; private String firmwareVersion = null;
private State state = State.NOT_CONNECTED; private State state = State.NOT_CONNECTED;
private byte freeAppSlot = -1;
public GBDevice(String address, String name, Type type) { public GBDevice(String address, String name, Type type) {
this.address = address; this.address = address;
@ -61,6 +62,14 @@ public class GBDevice {
return type; return type;
} }
public void setFreeAppSlot(byte freeAppSlot) {
this.freeAppSlot = freeAppSlot;
}
public byte getFreeAppSlot() {
return freeAppSlot;
}
public enum State { public enum State {
NOT_CONNECTED, NOT_CONNECTED,
CONNECTING, CONNECTING,

View File

@ -0,0 +1,154 @@
package nodomain.freeyourgadget.gadgetbridge.pebble;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp;
public class PBWReader {
private GBDeviceApp app;
private final Uri uri;
private final ContentResolver cr;
public PBWReader(Uri uri, Context context) {
this.uri = uri;
cr = context.getContentResolver();
InputStream fin = null;
try {
fin = new BufferedInputStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze = null;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals("appinfo.json")) {
long bytes = ze.getSize();
if (bytes > 8192) // that should be too much
break;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = zis.read(buffer)) != -1) {
baos.write(buffer, 0, count);
}
String jsonString = baos.toString();
try {
JSONObject json = new JSONObject(jsonString);
String appName = json.getString("shortName");
String appCreator = json.getString("companyName");
String appVersion = json.getString("versionLabel");
if (appName != null && appCreator != null && appVersion != null) {
// FIXME: dont assume WATCHFACE
app = new GBDeviceApp(-1, -1, appName, appCreator, appVersion, GBDeviceApp.Type.WATCHFACE);
}
} catch (JSONException e) {
e.printStackTrace();
break;
}
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public GBDeviceApp getGBDeviceApp() {
return app;
}
public ZipInputStream getInputStreamFile(String filename) {
InputStream fin = null;
try {
fin = new BufferedInputStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze = null;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals(filename)) {
return zis;
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public int getFileSize(String filename) {
InputStream fin = null;
try {
fin = new BufferedInputStream(cr.openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return -1;
}
ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze = null;
try {
while ((ze = zis.getNextEntry()) != null) {
if (ze.getName().equals(filename)) {
return (int) ze.getSize();
}
}
zis.close();
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
public ZipInputStream getInputStreamAppBinary() {
return getInputStreamFile("pebble-app.bin");
}
public int getAppBinarySize() {
return getFileSize("pebble-app.bin");
}
public ZipInputStream getInputStreamAppWorker() {
return getInputStreamFile("pebble-worker.bin");
}
public int getAppWorkerSize() {
return getFileSize("pebble-worker.bin");
}
public ZipInputStream getInputStreamAppResources() {
return getInputStreamFile("app_resources.pbpack");
}
public int getAppResourcesSize() {
return getFileSize("pebble-resources.pbpack");
}
}

View File

@ -0,0 +1,85 @@
package nodomain.freeyourgadget.gadgetbridge.pebble;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.zip.ZipInputStream;
import nodomain.freeyourgadget.gadgetbridge.BluetoothCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.R;
public class PebbleAppInstallerActivity extends Activity {
private final String TAG = this.getClass().getSimpleName();
TextView debugTextView;
Button installButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appinstaller);
getActionBar().setDisplayHomeAsUpEnabled(true);
debugTextView = (TextView) findViewById(R.id.debugTextView);
installButton = (Button) findViewById(R.id.installButton);
debugTextView.setText("contents:\n");
final Uri uri = getIntent().getData();
PBWReader pbwReader = new PBWReader(uri, getApplicationContext());
GBDeviceApp app = pbwReader.getGBDeviceApp();
ZipInputStream zis = pbwReader.getInputStreamAppBinary();
/*
STM32CRC stm32crc = new STM32CRC();
byte[] buffer = new byte[1000];
int count = 0;
try {
while ((count = zis.read(buffer)) != -1) {
stm32crc.addData(buffer, count);
}
} catch (IOException e) {
e.printStackTrace();
}
Log.i("TEST", "STMCRC32: " + stm32crc.getResult());
*/
if (pbwReader != null && app != null) {
debugTextView.setText("This is just a test, you cant install anything yet \n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n");
installButton.setEnabled(true);
installButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(PebbleAppInstallerActivity.this, BluetoothCommunicationService.class);
startIntent.setAction(BluetoothCommunicationService.ACTION_INSTALL_PEBBLEAPP);
startIntent.putExtra("app_uri", uri.toString());
startService(startIntent);
}
});
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}

View File

@ -4,6 +4,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp;
public class GBDeviceCommandAppInfo extends GBDeviceCommand { public class GBDeviceCommandAppInfo extends GBDeviceCommand {
public GBDeviceApp apps[]; public GBDeviceApp apps[];
public byte freeSlot = -1;
public GBDeviceCommandAppInfo() { public GBDeviceCommandAppInfo() {
commandClass = CommandClass.APP_INFO; commandClass = CommandClass.APP_INFO;

View File

@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.protocol;
public class GBDeviceCommandAppManagementResult extends GBDeviceCommand { public class GBDeviceCommandAppManagementResult extends GBDeviceCommand {
public Result result = Result.UNKNOWN; public Result result = Result.UNKNOWN;
public CommandType type = CommandType.UNKNOWN; public CommandType type = CommandType.UNKNOWN;
public int token = -1;
public GBDeviceCommandAppManagementResult() { public GBDeviceCommandAppManagementResult() {
commandClass = CommandClass.APP_MANAGEMENT_RES; commandClass = CommandClass.APP_MANAGEMENT_RES;
@ -10,12 +11,14 @@ public class GBDeviceCommandAppManagementResult extends GBDeviceCommand {
public enum CommandType { public enum CommandType {
UNKNOWN, UNKNOWN,
INSTALL,
DELETE, DELETE,
} }
public enum Result { public enum Result {
UNKNOWN, UNKNOWN,
SUCCESS, SUCCESS,
ACKNOLEDGE,
FAILURE, FAILURE,
} }
} }

View File

@ -74,11 +74,19 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final int APPMANAGER_RES_SUCCESS = 1; static final int APPMANAGER_RES_SUCCESS = 1;
static final short LENGTH_PREFIX = 4; static final byte PUTBYTES_INIT = 1;
static final short LENGTH_SETTIME = 5; static final byte PUTBYTES_SEND = 2;
static final short LENGTH_REMOVEAPP = 9; static final byte PUTBYTES_COMMIT = 3;
static final short LENGTH_PHONEVERSION = 17; static final byte PUTBYTES_ABORT = 4;
static final byte PUTBYTES_COMPLETE = 5;
static final byte PUTBYTES_TYPE_FIRMWARE = 1;
static final byte PUTBYTES_TYPE_RECOVERY = 2;
static final byte PUTBYTES_TYPE_SYSRESOURCES = 3;
static final byte PUTBYTES_TYPE_RESOURCES = 4;
public static final byte PUTBYTES_TYPE_BINARY = 5;
static final byte PUTBYTES_TYPE_FILE = 6;
static final byte PUTBYTES_TYPE_WORKER = 7;
static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains
static final byte PHONEVERSION_APPVERSION_MAJOR = 2; static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
@ -104,6 +112,14 @@ public class PebbleProtocol extends GBDeviceProtocol {
static final byte PHONEVERSION_REMOTE_OS_LINUX = 4; static final byte PHONEVERSION_REMOTE_OS_LINUX = 4;
static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5; static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5;
static final short LENGTH_PREFIX = 4;
static final short LENGTH_SETTIME = 5;
static final short LENGTH_REMOVEAPP = 9;
static final short LENGTH_PHONEVERSION = 17;
static final short LENGTH_UPLOADSTART = 7;
static final short LENGTH_UPLOADCHUNK = 9;
private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) { private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) {
// Calculate length first // Calculate length first
int length = LENGTH_PREFIX + 1; int length = LENGTH_PREFIX + 1;
@ -265,6 +281,31 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array(); return buf.array();
} }
/* pebble specific install methods */
public byte[] encodeUploadStart(byte type, byte index, int size) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADSTART);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_UPLOADSTART);
buf.putShort(ENDPOINT_PUTBYTES);
buf.put(PUTBYTES_INIT);
buf.putInt(size);
buf.put(type);
buf.put(index);
return buf.array();
}
public byte[] encodeUploadChunk(int token, byte[] buffer, int size) {
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_UPLOADCHUNK + size);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort((short) (LENGTH_UPLOADCHUNK + size));
buf.putShort(ENDPOINT_PUTBYTES);
buf.put(PUTBYTES_SEND);
buf.putInt(token);
buf.putInt(size);
buf.put(buffer, 0, size);
return buf.array();
}
public GBDeviceCommand decodeResponse(byte[] responseData) { public GBDeviceCommand decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData); ByteBuffer buf = ByteBuffer.wrap(responseData);
buf.order(ByteOrder.BIG_ENDIAN); buf.order(ByteOrder.BIG_ENDIAN);
@ -322,15 +363,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
switch (pebbleCmd) { switch (pebbleCmd) {
case APPMANAGER_GETAPPBANKSTATUS: case APPMANAGER_GETAPPBANKSTATUS:
GBDeviceCommandAppInfo appInfoCmd = new GBDeviceCommandAppInfo(); GBDeviceCommandAppInfo appInfoCmd = new GBDeviceCommandAppInfo();
int banks = buf.getInt(); int slotCount = buf.getInt();
int banksUsed = buf.getInt(); int slotsUsed = buf.getInt();
byte[] appName = new byte[32]; byte[] appName = new byte[32];
byte[] appCreator = new byte[32]; byte[] appCreator = new byte[32];
appInfoCmd.apps = new GBDeviceApp[banksUsed]; appInfoCmd.apps = new GBDeviceApp[slotsUsed];
boolean[] slotInUse = new boolean[slotCount];
for (int i = 0; i < banksUsed; i++) { for (int i = 0; i < slotsUsed; i++) {
int id = buf.getInt(); int id = buf.getInt();
int index = buf.getInt(); int index = buf.getInt();
slotInUse[index] = true;
buf.get(appName, 0, 32); buf.get(appName, 0, 32);
buf.get(appCreator, 0, 32); buf.get(appCreator, 0, 32);
int flags = buf.getInt(); int flags = buf.getInt();
@ -347,6 +390,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
Short appVersion = buf.getShort(); Short appVersion = buf.getShort();
appInfoCmd.apps[i] = new GBDeviceApp(id, index, new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType); appInfoCmd.apps[i] = new GBDeviceApp(id, index, new String(appName).trim(), new String(appCreator).trim(), appVersion.toString(), appType);
} }
for (int i = 0; i < slotCount; i++) {
if (!slotInUse[i]) {
appInfoCmd.freeSlot = (byte) i;
Log.i(TAG, "found free slot " + i);
break;
}
}
cmd = appInfoCmd; cmd = appInfoCmd;
break; break;
case APPMANAGER_REMOVEAPP: case APPMANAGER_REMOVEAPP:
@ -369,6 +419,21 @@ public class PebbleProtocol extends GBDeviceProtocol {
break; break;
} }
break; break;
case ENDPOINT_PUTBYTES:
GBDeviceCommandAppManagementResult installRes = new GBDeviceCommandAppManagementResult();
installRes.type = GBDeviceCommandAppManagementResult.CommandType.INSTALL;
switch (pebbleCmd) {
case PUTBYTES_INIT:
installRes.token = buf.getInt();
installRes.result = GBDeviceCommandAppManagementResult.Result.SUCCESS;
break;
default:
installRes.token = buf.getInt();
installRes.result = GBDeviceCommandAppManagementResult.Result.FAILURE;
break;
}
cmd = installRes;
break;
default: default:
break; break;
} }

View File

@ -6,11 +6,20 @@
android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
tools:context="nodomain.freeyourgadget.gadgetbridge.AppInstallerActivity"> tools:context="nodomain.freeyourgadget.gadgetbridge.pebble.PebbleAppInstallerActivity">
<TextView <TextView
android:id="@+id/debugTextView" android:id="@+id/debugTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Install"
android:id="@+id/installButton"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:enabled="false" />
</RelativeLayout> </RelativeLayout>