From e28d6fa7cbe5938473e3cab6c64373857bb76494 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 14 Aug 2015 12:50:44 +0200 Subject: [PATCH] Pebble: try to install app metadata on FW 3.x (untested) --- .../devices/pebble/PBWInstallHandler.java | 2 +- .../devices/pebble/PBWReader.java | 44 ++++++++++++- .../devices/pebble/PebbleIoThread.java | 15 +++-- .../devices/pebble/PebbleProtocol.java | 62 +++++++++++++++++-- 4 files changed, 108 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java index e2722f466..3fe5fadaa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java @@ -70,7 +70,7 @@ public class PBWInstallHandler implements InstallHandler { } GBDeviceApp app = mPBWReader.getGBDeviceApp(); - File pbwFile = new File(mPBWReader.getUri().getPath()); + File pbwFile = new File(mUri.getPath()); try { File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); destDir.mkdirs(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index 38fad20b0..60edaf553 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -14,6 +14,8 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -50,6 +52,10 @@ public class PBWReader { private boolean isFirmware = false; private boolean isValid = false; private String hwRevision = null; + private short mSdkVersion; + private short mAppVersion; + private int mIconId; + private int mFlags; public PBWReader(Uri uri, Context context, String platform) { String platformDir = ""; @@ -70,7 +76,7 @@ public class PBWReader { } ZipInputStream zis = new ZipInputStream(fin); ZipEntry ze; - pebbleInstallables = new ArrayList(); + pebbleInstallables = new ArrayList<>(); byte[] buffer = new byte[1024]; int count; try { @@ -149,6 +155,26 @@ public class PBWReader { e.printStackTrace(); break; } + } else if (fileName.equals(platformDir + "pebble-app.bin")) { + zis.read(buffer, 0, 108); + byte[] tmp_buf = new byte[32]; + ByteBuffer buf = ByteBuffer.wrap(buffer); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.getLong(); // header, TODO: verifiy + buf.getShort(); // struct version, TODO: verify + mSdkVersion = buf.getShort(); + mAppVersion = buf.getShort(); + buf.getShort(); // size + buf.getInt(); // offset + buf.getInt(); // crc + buf.get(tmp_buf, 0, 32); // app name + buf.get(tmp_buf, 0, 32); // author + mIconId = buf.getInt(); + LOG.info("got icon id from pebble-app.bin: " + mIconId); + buf.getInt(); // symbol table addr + mFlags = buf.getInt(); + LOG.info("got flags from pebble-app.bin: " + mFlags); + // more follows but, not interesting for us } } zis.close(); @@ -206,7 +232,19 @@ public class PBWReader { return hwRevision; } - public Uri getUri() { - return uri; + public short getSdkVersion() { + return mSdkVersion; + } + + public short getAppVersion() { + return mAppVersion; + } + + public int getFlags() { + return mFlags; + } + + public int getIconId() { + return mIconId; } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 54e63c01e..e4812727c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -26,6 +26,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagem import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.service.bt.GBDeviceIoThread; import nodomain.freeyourgadget.gadgetbridge.service.bt.GBDeviceProtocol; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -42,8 +43,7 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean mIsConnected = false; private boolean mIsInstalling = false; private int mConnectionAttempts = 0; - /* app installation */ - private Uri mInstallURI = null; + private boolean mForceUntested = false; private PBWReader mPBWReader = null; private int mAppInstallToken = -1; private ZipInputStream mZis = null; @@ -84,7 +84,7 @@ public class PebbleIoThread extends GBDeviceIoThread { SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext()); mPebbleProtocol.setForceProtocol(sharedPrefs.getBoolean("pebble_force_protocol", false)); - + mForceUntested = sharedPrefs.getBoolean("pebble_force_untested", false); gbDevice.setState(GBDevice.State.CONNECTED); gbDevice.sendDeviceUpdateIntent(getContext()); @@ -375,9 +375,8 @@ public class PebbleIoThread extends GBDeviceIoThread { return; } mIsInstalling = true; - mInstallURI = uri; - mPBWReader = new PBWReader(mInstallURI, getContext(), gbDevice.getHardwareVersion().equals("dvt") ? "basalt" : "aplite"); + mPBWReader = new PBWReader(uri, getContext(), gbDevice.getHardwareVersion().equals("dvt") ? "basalt" : "aplite"); mPebbleInstallables = mPBWReader.getPebbleInstallables(); mCurrentInstallableIndex = 0; @@ -399,8 +398,12 @@ public class PebbleIoThread extends GBDeviceIoThread { mInstallSlot = 0; mInstallState = PebbleAppInstallState.START_INSTALL; } else { - writeInstallApp(mPebbleProtocol.encodeAppDelete(mPBWReader.getGBDeviceApp().getUUID())); + GBDeviceApp app = mPBWReader.getGBDeviceApp(); + writeInstallApp(mPebbleProtocol.encodeAppDelete(app.getUUID())); mInstallState = PebbleAppInstallState.WAIT_SLOT; + if (mPebbleProtocol.isFw3x && mForceUntested) { + writeInstallApp(mPebbleProtocol.encodeInstallMetadata(app.getUUID(), app.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion())); + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 15cda706b..225243b3b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -61,6 +61,15 @@ public class PebbleProtocol extends GBDeviceProtocol { static final byte APPRUNSTATE_START = 1; + static final byte BLOBDB_INSERT = 1; + static final byte BLOBDB_DELETE = 4; + static final byte BLOBDB_CLEAR = 5; + + static final byte BLOBDB_PIN = 1; + static final byte BLOBDB_APP = 2; + static final byte BLOBDB_REMINDER = 3; + static final byte BLOBDB_NOTIFICATION = 4; + static final byte NOTIFICATION_EMAIL = 0; static final byte NOTIFICATION_SMS = 1; static final byte NOTIFICATION_TWITTER = 2; @@ -179,6 +188,8 @@ public class PebbleProtocol extends GBDeviceProtocol { static final short LENGTH_UPLOADCOMPLETE = 5; static final short LENGTH_UPLOADCANCEL = 5; + static final byte LENGTH_UUID = 16; + private static final String[] hwRevisions = {"unknown", "ev1", "ev2", "ev2_3", "ev2_4", "v1_5", "v2_0", "evt2", "dvt"}; private static Random mRandom = new Random(); @@ -406,11 +417,11 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.order(ByteOrder.LITTLE_ENDIAN); // blobdb - 23 bytes - buf.put((byte) 0x01); // insert + buf.put(BLOBDB_INSERT); buf.putShort((short) mRandom.nextInt()); // token - buf.put((byte) 0x04); // db id (0x04 = notification) - buf.put((byte) 16); // uuid length - byte[] uuid_buf = new byte[16]; + buf.put(BLOBDB_NOTIFICATION); + buf.put(LENGTH_UUID); // uuid length + byte[] uuid_buf = new byte[LENGTH_UUID]; mRandom.nextBytes(uuid_buf); buf.put(uuid_buf); // random UUID buf.putShort(pin_length); // length of the encapsulated data @@ -458,6 +469,47 @@ public class PebbleProtocol extends GBDeviceProtocol { return buf.array(); } + public byte[] encodeInstallMetadata(UUID uuid, String appName, short appVersion, short sdkVersion) { + // Calculate length first + final short BLOBDB_LENGTH = 23; + final short METADATA_LENGTH = 126; + + final short length = (short) (BLOBDB_LENGTH + METADATA_LENGTH); + + byte[] name_buf = new byte[96]; + System.arraycopy(appName.getBytes(), 0, name_buf, 0, appName.length()); + ByteBuffer buf = ByteBuffer.allocate(length + LENGTH_PREFIX); + + // Encode Prefix + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort(length); + buf.putShort(ENDPOINT_BLOBDB); + + buf.order(ByteOrder.LITTLE_ENDIAN); + // blobdb - 23 bytes + buf.put(BLOBDB_INSERT); // insert + buf.putShort((short) mRandom.nextInt()); // token + buf.put(BLOBDB_APP); + buf.put(LENGTH_UUID); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putLong(uuid.getMostSignificantBits()); // watchapp uuid + buf.putLong(uuid.getLeastSignificantBits()); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putShort(METADATA_LENGTH); // length of the encapsulated data + buf.order(ByteOrder.BIG_ENDIAN); + buf.putLong(uuid.getMostSignificantBits()); // watchapp uuid + buf.putLong(uuid.getLeastSignificantBits()); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putInt(1); // icon_id + buf.putShort(appVersion); + buf.putShort(sdkVersion); + buf.put((byte) 0); // app_face_bgcolor + buf.put((byte) 0); // app_face_template_id + buf.put(name_buf); // 96 bytes + + return buf.array(); + } + public byte[] encodeGetTime() { return encodeSimpleMessage(ENDPOINT_TIME, TIME_GETTIME); } @@ -762,7 +814,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs) { - int length = 16 + 3; // UUID + (PUSH + id + length of dict) + int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict) for (Pair pair : pairs) { length += 7; // key + type + length if (pair.second instanceof Integer) {