From ebdf514c0e55201e8ecbdd3f8c2565cef9151122 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 7 Apr 2015 19:33:23 +0200 Subject: [PATCH] Pebble app installation: first successful installation :) KNOWN PROBLEMS - expected filenames inside pbw files are hardcoded (pebble-app.bin etc) - long delay before installation starts - must be in app mananger at least once before installation in possible - errors while installing are not always recognized --- .../BluetoothCommunicationService.java | 87 +++++++++++++------ .../gadgetbridge/pebble/PBWReader.java | 32 ++----- .../pebble/PebbleAppInstallerActivity.java | 2 +- .../gadgetbridge/protocol/PebbleProtocol.java | 18 +++- 4 files changed, 87 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java index cc084f226..1a1edb0df 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/BluetoothCommunicationService.java @@ -351,9 +351,10 @@ public class BluetoothCommunicationService extends Service { 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)); + int slot = mGBDevice.getFreeAppSlot(); + if (uriString != null && slot != -1) { + Log.i(TAG, "will try to install app in slot " + slot); + ((PebbleIoThread) mGBDeviceIoThread).installApp(Uri.parse(uriString), slot); } } else if (action.equals(ACTION_START)) { startForeground(NOTIFICATION_ID, createNotification("Gadgetbridge running")); @@ -449,10 +450,7 @@ public class BluetoothCommunicationService extends Service { APP_UPLOAD_COMMIT, APP_WAIT_COMMMIT, APP_UPLOAD_COMPLETE, - RES_START_INSTALL, - RES_WAIT_TOKEN, - RES_UPLOAD_CHUNK, - RES_UPLOAD_COMPLETE, + APP_REFRESH, } private class PebbleIoThread extends GBDeviceIoThread { @@ -471,6 +469,9 @@ public class BluetoothCommunicationService extends Service { 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); @@ -514,10 +515,33 @@ public class BluetoothCommunicationService extends Service { case APP_START_INSTALL: Log.i(TAG, "start installing app binary"); mmSTM32CRC.reset(); - mmPBWReader = new PBWReader(mmInstallURI, getApplicationContext()); - mmZis = mmPBWReader.getInputStreamAppBinary(); - int binarySize = mmPBWReader.getAppBinarySize(); - writeInstallApp(mmPebbleProtocol.encodeUploadStart(PebbleProtocol.PUTBYTES_TYPE_BINARY, mGBDevice.getFreeAppSlot(), binarySize), -1); + 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 { + // FIXME: proper state for cancellation + mmInstallState = PebbleAppInstallState.UNKNOWN; + mmPBWReader = null; + mmIsInstalling = false; + mmZis = null; + mmAppInstallToken = -1; + mmInstallSlot = -1; + } + + writeInstallApp(mmPebbleProtocol.encodeUploadStart(type, (byte)mmInstallSlot, binarySize), -1); mmInstallState = PebbleAppInstallState.APP_WAIT_TOKEN; break; case APP_WAIT_TOKEN: @@ -540,24 +564,33 @@ public class BluetoothCommunicationService extends Service { continue; } break; - case APP_UPLOAD_COMMIT: - writeInstallApp(mmPebbleProtocol.encodeUploadCommit(mmAppInstallToken, mmSTM32CRC.getResult()),-1); - mmAppInstallToken = -1; - mmInstallState = PebbleAppInstallState.APP_WAIT_COMMMIT; + case APP_UPLOAD_COMMIT: + writeInstallApp(mmPebbleProtocol.encodeUploadCommit(mmAppInstallToken, mmSTM32CRC.getResult()), -1); + 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; - } + 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),-1); - mmInstallState = PebbleAppInstallState.UNKNOWN; + writeInstallApp(mmPebbleProtocol.encodeUploadComplete(mmAppInstallToken), -1); + if (++mmCurrentFileIndex < mmFilesToInstall.length) { + mmInstallState = PebbleAppInstallState.APP_START_INSTALL; + } else { + mmInstallState = PebbleAppInstallState.APP_REFRESH; + } + break; + case APP_REFRESH: + writeInstallApp(mmPebbleProtocol.encodeAppRefresh(mmInstallSlot), -1); + mmPBWReader = null; mmIsInstalling = false; mmZis = null; mmAppInstallToken = -1; + mmInstallSlot = -1; break; default: break; @@ -690,10 +723,14 @@ public class BluetoothCommunicationService extends Service { } } - public void installApp(Uri uri) { + public void installApp(Uri uri, int slot) { + if (mmIsInstalling) { + return; + } mmInstallState = PebbleAppInstallState.APP_START_INSTALL; - mmIsInstalling = true; mmInstallURI = uri; + mmInstallSlot = slot; + mmIsInstalling = true; } public void quit() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java index 1838ca419..cd186cc20 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java @@ -12,6 +12,7 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -21,6 +22,7 @@ public class PBWReader { private GBDeviceApp app; private final Uri uri; private final ContentResolver cr; + private ArrayList filesToInstall; public PBWReader(Uri uri, Context context) { this.uri = uri; @@ -36,9 +38,13 @@ public class PBWReader { } ZipInputStream zis = new ZipInputStream(fin); ZipEntry ze = null; + filesToInstall = new ArrayList(); try { while ((ze = zis.getNextEntry()) != null) { - if (ze.getName().equals("appinfo.json")) { + String fileName = ze.getName(); + if (fileName.equals("pebble-app.bin") || fileName.equals("pebble-worker.bin") || fileName.equals("app_resources.pbpack")) { + filesToInstall.add(fileName); // FIXME: do not hardcode filenames above + } else if (fileName.equals("appinfo.json")) { long bytes = ze.getSize(); if (bytes > 8192) // that should be too much break; @@ -127,28 +133,8 @@ public class PBWReader { 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"); + public String[] getFilesToInstall() { + return filesToInstall.toArray(new String[filesToInstall.size()]); } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java index 3ea22fedd..3f513745f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java @@ -34,8 +34,8 @@ public class PebbleAppInstallerActivity extends Activity { debugTextView.setText("contents:\n"); final Uri uri = getIntent().getData(); PBWReader pbwReader = new PBWReader(uri, getApplicationContext()); - GBDeviceApp app = pbwReader.getGBDeviceApp(); + 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); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java index f876fe012..6745654f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/protocol/PebbleProtocol.java @@ -71,6 +71,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte APPMANAGER_GETAPPBANKSTATUS = 1; static final byte APPMANAGER_REMOVEAPP = 2; + static final byte APPMANAGER_REFRESHAPP = 3; static final int APPMANAGER_RES_SUCCESS = 1; @@ -83,10 +84,10 @@ public class PebbleProtocol extends GBDeviceProtocol { 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_RESOURCES = 4; public static final byte PUTBYTES_TYPE_BINARY = 5; static final byte PUTBYTES_TYPE_FILE = 6; - static final byte PUTBYTES_TYPE_WORKER = 7; + public static final byte PUTBYTES_TYPE_WORKER = 7; static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains static final byte PHONEVERSION_APPVERSION_MAJOR = 2; @@ -94,7 +95,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte PHONEVERSION_APPVERSION_PATCH = 0; - static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = (int)0x80000000; + static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = (int) 0x80000000; static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010; static final int PHONEVERSION_REMOTE_CAPS_SMS = 0x00000020; @@ -116,6 +117,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final short LENGTH_PREFIX = 4; static final short LENGTH_SETTIME = 5; static final short LENGTH_REMOVEAPP = 9; + static final short LENGTH_REFRESHAPP = 5; static final short LENGTH_PHONEVERSION = 17; static final short LENGTH_UPLOADSTART = 7; static final short LENGTH_UPLOADCHUNK = 9; @@ -329,6 +331,16 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + public byte[] encodeAppRefresh(int index) { + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort(LENGTH_REFRESHAPP); + buf.putShort(ENDPOINT_APPMANAGER); + buf.put(APPMANAGER_REFRESHAPP); + buf.putInt(index); + + return buf.array(); + } public GBDeviceCommand decodeResponse(byte[] responseData) { ByteBuffer buf = ByteBuffer.wrap(responseData);