diff --git a/CHANGELOG.md b/CHANGELOG.md index dfb5257d..0279742f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ ###Changelog ####Next Release (probably 0.3.0) -* Fix installation problems with certain .pbw files -* Volume control for Pebble +* Pebble: Firmware installation (USE AT YOUR OWN RISK) +* Pebble: Fix installation problems with certain .pbw files +* Pebble: Volume control * Add icon for activity tracker apps (icon by xphnx) +* Let the application quit when in reconnecting state ####Version 0.2.0 * Experimental pbw installation support (watchfaces/apps) diff --git a/README.md b/README.md index 3501eeb7..fb3cc255 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ Features: * Apollo playback info (artist, album, track) * Music control: play/pause, next track, previous track, volume up, volume down * List and remove installed apps/watchfaces -* Install .pbw files (EXPERMIENTAL) +* Install .pbw files +* Install firmware from .pbz files (EXPERIMENTAL) How to use: diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0793b54..583561c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,6 +52,7 @@ + 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 03266c5f..2fabdded 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PBWReader.java @@ -14,6 +14,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -21,11 +23,28 @@ import nodomain.freeyourgadget.gadgetbridge.GBDeviceApp; public class PBWReader { private static final String TAG = PebbleIoThread.class.getSimpleName(); + private static final HashMap appFileTypesMap; + + static { + appFileTypesMap = new HashMap(); + appFileTypesMap.put("application", PebbleProtocol.PUTBYTES_TYPE_BINARY); + appFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_RESOURCES); + appFileTypesMap.put("worker", PebbleProtocol.PUTBYTES_TYPE_WORKER); + } + + private static final HashMap fwFileTypesMap; + + static { + fwFileTypesMap = new HashMap(); + fwFileTypesMap.put("firmware", PebbleProtocol.PUTBYTES_TYPE_FIRMWARE); + fwFileTypesMap.put("resources", PebbleProtocol.PUTBYTES_TYPE_SYSRESOURCES); + } - private GBDeviceApp app; private final Uri uri; private final ContentResolver cr; + private GBDeviceApp app; private ArrayList pebbleInstallables; + private boolean isFirmware = false; public PBWReader(Uri uri, Context context) { this.uri = uri; @@ -60,35 +79,32 @@ public class PBWReader { String jsonString = baos.toString(); try { JSONObject json = new JSONObject(jsonString); - JSONObject application = json.getJSONObject("application"); + String[] searchJSON; + HashMap fileTypeMap; - String name = application.getString("name"); - int size = application.getInt("size"); - long crc = application.getLong("crc"); - pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_BINARY)); - Log.i(TAG, "found app binary to install: " + name); try { - JSONObject resources = json.getJSONObject("resources"); - name = resources.getString("name"); - size = resources.getInt("size"); - crc = resources.getLong("crc"); - pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_RESOURCES)); - Log.i(TAG, "found resources to install: " + name); + json.getJSONObject("firmware"); + fileTypeMap = fwFileTypesMap; + isFirmware = true; } catch (JSONException e) { - // no resources, that is no problem + fileTypeMap = appFileTypesMap; + isFirmware = false; } - try { - JSONObject worker = json.getJSONObject("worker"); - name = worker.getString("name"); - size = worker.getInt("size"); - crc = worker.getLong("crc"); - pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, PebbleProtocol.PUTBYTES_TYPE_WORKER)); - Log.i(TAG, "found worker to install: " + name); - } catch (JSONException e) { - // no worker, that is no problem + for (Map.Entry entry : fileTypeMap.entrySet()) { + try { + JSONObject jo = json.getJSONObject(entry.getKey()); + String name = jo.getString("name"); + int size = jo.getInt("size"); + long crc = jo.getLong("crc"); + byte type = entry.getValue(); + pebbleInstallables.add(new PebbleInstallable(name, size, (int) crc, type)); + Log.i(TAG, "found file to install: " + name); + } catch (JSONException e) { + // not fatal + } } } catch (JSONException e) { - // no application, that is a problem + // no JSON at all that is a problem e.printStackTrace(); break; } @@ -126,6 +142,10 @@ public class PBWReader { } } + protected boolean isFirmware() { + return isFirmware; + } + public GBDeviceApp getGBDeviceApp() { return app; } 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 3dff5f61..f6d8aa18 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleAppInstallerActivity.java @@ -35,8 +35,13 @@ public class PebbleAppInstallerActivity extends Activity { PBWReader pbwReader = new PBWReader(uri, getApplicationContext()); GBDeviceApp app = pbwReader.getGBDeviceApp(); - if (pbwReader != null && app != null) { - debugTextView.setText("THIS IS HIGHLY EXPERIMENTAL PROCEED AT YOUR OWN RISK\n\n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n"); + if (pbwReader != null) { + if (pbwReader.isFirmware()) { + debugTextView.setText("YOUR ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK, MAKE SURE THIS FIRMWARE IS FOR YOUR PEBBLE REVISION, THERE ARE NO CHECKS.\n\n\n"); + + } else if (app != null) { + debugTextView.setText("You are about to install the following app:\n\n\n" + app.getName() + " Version " + app.getVersion() + " by " + app.getCreator() + "\n"); + } installButton.setEnabled(true); installButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleIoThread.java index 30d8a806..29aad778 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleIoThread.java @@ -121,16 +121,21 @@ public class PebbleIoThread extends GBDeviceIoThread { } break; case APP_START_INSTALL: - Log.i(TAG, "start installing app binary"); if (mPBWReader == null) { mPBWReader = new PBWReader(mInstallURI, getContext()); mPebbleInstallables = mPBWReader.getPebbleInstallables(); mCurrentInstallableIndex = 0; + if (mPBWReader.isFirmware()) { + writeInstallApp(mPebbleProtocol.encodeInstallFirmwareStart()); + mInstallSlot = 0; + Log.i(TAG, "starting firmware installation"); + } } + Log.i(TAG, "start installing app binary"); PebbleInstallable pi = mPebbleInstallables[mCurrentInstallableIndex]; mZis = mPBWReader.getInputStreamFile(pi.getFileName()); mCRC = pi.getCRC(); - int binarySize = pi.getFileSize(); // TODO: use for progrssbar + int binarySize = pi.getFileSize(); // TODO: use for progressbar writeInstallApp(mPebbleProtocol.encodeUploadStart(pi.getType(), (byte) mInstallSlot, binarySize)); mInstallState = PebbleAppInstallState.APP_WAIT_TOKEN; break; @@ -174,7 +179,12 @@ public class PebbleIoThread extends GBDeviceIoThread { } break; case APP_REFRESH: - writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot)); + if (mPBWReader.isFirmware()) { + writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete()); + finishInstall(false); + } else { + writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot)); + } break; default: break; @@ -239,7 +249,7 @@ public class PebbleIoThread extends GBDeviceIoThread { gbDevice.sendDeviceUpdateIntent(getContext()); GB.updateNotification("connection lost, trying to reconnect", getContext()); - while (mConnectionAttempts++ < 10) { + while (mConnectionAttempts++ < 10 && !mQuit) { Log.i(TAG, "Trying to reconnect (attempt " + mConnectionAttempts + ")"); mIsConnected = connect(gbDevice.getAddress()); if (mIsConnected) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleProtocol.java index 014a6246..1a3ac22c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/pebble/PebbleProtocol.java @@ -96,6 +96,10 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte PUTBYTES_TYPE_FILE = 6; public static final byte PUTBYTES_TYPE_WORKER = 7; + private final byte SYSTEMMESSAGE_FIRMWARESTART = 1; + private final byte SYSTEMMESSAGE_FIRMWARECOMPLETE = 2; + private final byte SYSTEMMESSAGE_FIRMWAREFAIL = 3; + static final byte PHONEVERSION_APPVERSION_MAGIC = 2; // increase this if pebble complains static final byte PHONEVERSION_APPVERSION_MAJOR = 2; static final byte PHONEVERSION_APPVERSION_MINOR = 3; @@ -132,6 +136,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final short LENGTH_UPLOADCOMMIT = 9; static final short LENGTH_UPLOADCOMPLETE = 5; static final short LENGTH_UPLOADCANCEL = 5; + static final short LENGTH_SYSTEMMESSAGE = 2; private static byte[] encodeMessage(short endpoint, byte type, int cookie, String[] parts) { // Calculate length first @@ -360,6 +365,30 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + private byte[] encodeSystemMessage(byte systemMessage) { + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SYSTEMMESSAGE); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort(LENGTH_SYSTEMMESSAGE); + buf.putShort(ENDPOINT_SYSTEMMESSAGE); + buf.put((byte) 0); + buf.put(systemMessage); + return buf.array(); + + } + + public byte[] encodeInstallFirmwareStart() { + return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARESTART); + } + + public byte[] encodeInstallFirmwareComplete() { + return encodeSystemMessage(SYSTEMMESSAGE_FIRMWARECOMPLETE); + } + + public byte[] encodeInstallFirmwareError() { + return encodeSystemMessage(SYSTEMMESSAGE_FIRMWAREFAIL); + } + + public byte[] encodeAppRefresh(int index) { ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REFRESHAPP); buf.order(ByteOrder.BIG_ENDIAN);