Pebble: Remove firmware 1.x and 2.x support

All pebbles ever released support Firmware >=3.x
And it got really old by now
This commit is contained in:
Andreas Shimokawa
2025-10-31 13:58:28 +01:00
parent 25cc9294be
commit 75035f26c3
7 changed files with 112 additions and 292 deletions
@@ -1831,7 +1831,6 @@ public class GBApplication extends Application {
deviceSharedPrefsEdit.putBoolean("pebble_health_store_raw", sharedPrefs.getBoolean("pebble_health_store_raw", true)); deviceSharedPrefsEdit.putBoolean("pebble_health_store_raw", sharedPrefs.getBoolean("pebble_health_store_raw", true));
deviceSharedPrefsEdit.putBoolean("pebble_sync_misfit", sharedPrefs.getBoolean("pebble_sync_misfit", true)); deviceSharedPrefsEdit.putBoolean("pebble_sync_misfit", sharedPrefs.getBoolean("pebble_sync_misfit", true));
deviceSharedPrefsEdit.putBoolean("pebble_sync_morpheuz", sharedPrefs.getBoolean("pebble_sync_morpheuz", true)); deviceSharedPrefsEdit.putBoolean("pebble_sync_morpheuz", sharedPrefs.getBoolean("pebble_sync_morpheuz", true));
deviceSharedPrefsEdit.putBoolean("pebble_force_protocol", sharedPrefs.getBoolean("pebble_force_protocol", false));
deviceSharedPrefsEdit.putBoolean("pebble_force_untested", sharedPrefs.getBoolean("pebble_force_untested", false)); deviceSharedPrefsEdit.putBoolean("pebble_force_untested", sharedPrefs.getBoolean("pebble_force_untested", false));
deviceSharedPrefsEdit.putBoolean("pebble_force_le", sharedPrefs.getBoolean("pebble_force_le", false)); deviceSharedPrefsEdit.putBoolean("pebble_force_le", sharedPrefs.getBoolean("pebble_force_le", false));
deviceSharedPrefsEdit.putString("pebble_mtu_limit", sharedPrefs.getString("pebble_mtu_limit", "512")); deviceSharedPrefsEdit.putString("pebble_mtu_limit", sharedPrefs.getString("pebble_mtu_limit", "512"));
@@ -157,18 +157,12 @@ public class PebbleCoordinator extends AbstractBLClassicDeviceCoordinator {
@Override @Override
public boolean supportsAppListFetching(final GBDevice device) { public boolean supportsAppListFetching(final GBDevice device) {
if (device.getFirmwareVersion() != null) {
return PebbleUtils.getFwMajor(device.getFirmwareVersion()) < 3;
}
return false; return false;
} }
@Override @Override
public boolean supportsAppReordering(final GBDevice device) { public boolean supportsAppReordering(final GBDevice device) {
if (device.getFirmwareVersion() != null) { return true;
return PebbleUtils.getFwMajor(device.getFirmwareVersion()) >= 3;
}
return false;
} }
@Override @Override
@@ -84,9 +84,6 @@ class AppMessageHandlerMisfit extends AppMessageHandler {
break; break;
} }
if (mPebbleProtocol.mFwMajor < 3) {
timestamp -= SimpleTimeZone.getDefault().getOffset(timestamp * 1000L) / 1000;
}
Date startDate = new Date((long) timestamp * 1000L); Date startDate = new Date((long) timestamp * 1000L);
Date endDate = new Date((long) (timestamp + samples * 60) * 1000L); Date endDate = new Date((long) (timestamp + samples * 60) * 1000L);
LOG.info("got data from " + startDate + " to " + endDate); LOG.info("got data from " + startDate + " to " + endDate);
@@ -151,9 +151,6 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler {
ctrl_message |= CTRL_VERSION_DONE; ctrl_message |= CTRL_VERSION_DONE;
} else if (pair.first.equals(keyBase)) { } else if (pair.first.equals(keyBase)) {
recording_base_timestamp = (int) pair.second; recording_base_timestamp = (int) pair.second;
if (mPebbleProtocol.mFwMajor < 3) {
recording_base_timestamp -= SimpleTimeZone.getDefault().getOffset(recording_base_timestamp * 1000L) / 1000;
}
LOG.info("got base: " + recording_base_timestamp); LOG.info("got base: " + recording_base_timestamp);
ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT; ctrl_message |= CTRL_SET_LAST_SENT | CTRL_DO_NEXT;
} else if (pair.first.equals(keyAutoReset)) { } else if (pair.first.equals(keyAutoReset)) {
@@ -236,8 +236,6 @@ class PebbleIoThread extends GBDeviceIoThread {
return false; return false;
} }
mPebbleProtocol.setForceProtocol(devicePrefs.getBoolean("pebble_force_protocol", false));
mIsConnected = true; mIsConnected = true;
write(mPebbleProtocol.encodeFirmwareVersionReq()); write(mPebbleProtocol.encodeFirmwareVersionReq());
gbDevice.setUpdateState(GBDevice.State.CONNECTED, getContext()); gbDevice.setUpdateState(GBDevice.State.CONNECTED, getContext());
@@ -337,12 +335,8 @@ class PebbleIoThread extends GBDeviceIoThread {
case APP_REFRESH: case APP_REFRESH:
if (mPBWReader.isFirmware()) { if (mPBWReader.isFirmware()) {
writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete()); writeInstallApp(mPebbleProtocol.encodeInstallFirmwareComplete());
finishInstall(false);
} else if (mPBWReader.isLanguage() || mPebbleProtocol.mFwMajor >= 3) {
finishInstall(false); // FIXME: don't know yet how to detect success
} else {
writeInstallApp(mPebbleProtocol.encodeAppRefresh(mInstallSlot));
} }
finishInstall(false); // FIXME: don't know yet how to detect success
break; break;
default: default:
break; break;
@@ -469,8 +463,8 @@ class PebbleIoThread extends GBDeviceIoThread {
if (bytes == null) { if (bytes == null) {
return; return;
} }
// on FW < 3.0 block writes if app installation in in progress
if (!mIsConnected || (mPebbleProtocol.mFwMajor < 3 && mIsInstalling && mInstallState != PebbleAppInstallState.WAIT_SLOT)) { if (!mIsConnected) {
return; return;
} }
write_real(bytes); write_real(bytes);
@@ -649,7 +643,7 @@ class PebbleIoThread extends GBDeviceIoThread {
writeInstallApp(mPebbleProtocol.encodeGetTime()); writeInstallApp(mPebbleProtocol.encodeGetTime());
} else { } else {
mCurrentlyInstallingApp = mPBWReader.getGBDeviceApp(); mCurrentlyInstallingApp = mPBWReader.getGBDeviceApp();
if (mPebbleProtocol.mFwMajor >= 3 && !mPBWReader.isLanguage()) { if (!mPBWReader.isLanguage()) {
if (appId == 0) { if (appId == 0) {
// only install metadata - not the binaries // only install metadata - not the binaries
write(mPebbleProtocol.encodeInstallMetadata(mCurrentlyInstallingApp.getUUID(), mCurrentlyInstallingApp.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion(), mPBWReader.getFlags(), mPBWReader.getIconId())); write(mPebbleProtocol.encodeInstallMetadata(mCurrentlyInstallingApp.getUUID(), mCurrentlyInstallingApp.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion(), mPBWReader.getFlags(), mPBWReader.getIconId()));
@@ -697,19 +691,19 @@ class PebbleIoThread extends GBDeviceIoThread {
GB.updateInstallNotification(getContext().getString(R.string.installation_failed_), false, 0, getContext()); GB.updateInstallNotification(getContext().getString(R.string.installation_failed_), false, 0, getContext());
} else { } else {
GB.updateInstallNotification(getContext().getString(R.string.installation_successful), false, 0, getContext()); GB.updateInstallNotification(getContext().getString(R.string.installation_successful), false, 0, getContext());
if (mPebbleProtocol.mFwMajor >= 3) {
String filenameSuffix; String filenameSuffix;
if (mCurrentlyInstallingApp != null) { if (mCurrentlyInstallingApp != null) {
if (mCurrentlyInstallingApp.getType() == GBDeviceApp.Type.WATCHFACE) { if (mCurrentlyInstallingApp.getType() == GBDeviceApp.Type.WATCHFACE) {
filenameSuffix = ".watchfaces"; filenameSuffix = ".watchfaces";
} else { } else {
filenameSuffix = ".watchapps"; filenameSuffix = ".watchapps";
}
AppManagerActivity.addToAppOrderFile(gbDevice.getAddress() + filenameSuffix, mCurrentlyInstallingApp.getUUID());
Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
} }
AppManagerActivity.addToAppOrderFile(gbDevice.getAddress() + filenameSuffix, mCurrentlyInstallingApp.getUUID());
Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
} }
} }
mInstallState = PebbleAppInstallState.UNKNOWN; mInstallState = PebbleAppInstallState.UNKNOWN;
@@ -65,7 +65,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec.Action; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec.Action;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather; import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.weather.WeatherMapper; import nodomain.freeyourgadget.gadgetbridge.model.weather.WeatherMapper;
@@ -235,8 +234,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
private static final byte PHONEVERSION_APPVERSION_MAJOR = 2; private static final byte PHONEVERSION_APPVERSION_MAJOR = 2;
private static final byte PHONEVERSION_APPVERSION_MINOR = 3; private static final byte PHONEVERSION_APPVERSION_MINOR = 3;
private static final byte PHONEVERSION_APPVERSION_PATCH = 0; private static final byte PHONEVERSION_APPVERSION_PATCH = 0;
private static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000; private static final int PHONEVERSION_SESSION_CAPS_GAMMARAY = 0x80000000;
private static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010; private static final int PHONEVERSION_REMOTE_CAPS_TELEPHONY = 0x00000010;
@@ -308,8 +305,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
int mFwMajor = 3; int mFwMajor = 3;
boolean mEnablePebbleKit = false; boolean mEnablePebbleKit = false;
boolean mAlwaysACKPebbleKit = false; boolean mAlwaysACKPebbleKit = false;
private boolean mForceProtocol = false;
private byte[] screenshotData = null; private byte[] screenshotData = null;
private int screenshotWidth; private int screenshotWidth;
private int screenshotHeight; private int screenshotHeight;
@@ -446,10 +441,10 @@ public class PebbleProtocol extends GBDeviceProtocol {
super(device); super(device);
mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_WEATHER, new AppMessageHandler(UUID_WEATHER, PebbleProtocol.this){ mAppMessageHandlers.put(UUID_WEATHER, new AppMessageHandler(UUID_WEATHER, PebbleProtocol.this) {
@Override @Override
public GBDeviceEvent[] onAppStart() { public GBDeviceEvent[] onAppStart() {
return new GBDeviceEvent[] {new GBDeviceEventSendBytes(encodeSendWeather())}; return new GBDeviceEvent[]{new GBDeviceEventSendBytes(encodeSendWeather())};
} }
}); });
if (!((PebbleCoordinator) device.getDeviceCoordinator()).isBackgroundJsEnabled(device)) { if (!((PebbleCoordinator) device.getDeviceCoordinator()).isBackgroundJsEnabled(device)) {
@@ -545,22 +540,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
} }
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
if (mFwMajor < 3) {
ts += (SimpleTimeZone.getDefault().getOffset(ts));
}
ts /= 1000; ts /= 1000;
if (mFwMajor >= 3 || mForceProtocol || notificationSpec.type != NotificationType.GENERIC_EMAIL) { return encodeNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body,
// 3.x notification pebbleNotification,
return encodeNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.cannedReplies, notificationSpec.attachedActions);
pebbleNotification,
notificationSpec.cannedReplies, notificationSpec.attachedActions);
} else {
// 1.x notification on FW 2.X
String[] parts = {title, notificationSpec.body, String.valueOf(ts), subtitle};
// be aware that type is at this point always NOTIFICATION_EMAIL
return encodeMessage(ENDPOINT_NOTIFICATION, NOTIFICATION_EMAIL, 0, parts);
}
} }
@Override @Override
@@ -593,7 +577,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
// For all-day events, Pebble expects the start date to match the midnight boundaries // For all-day events, Pebble expects the start date to match the midnight boundaries
// in the user's timezone. However, the calendar event will have them in the UTC timezone, // in the user's timezone. However, the calendar event will have them in the UTC timezone,
// so we need to convert it // so we need to convert it
long startTimestampMs = ((long)startTimestamp) * 1000; long startTimestampMs = ((long) startTimestamp) * 1000;
startTimestamp = (int) (DateTimeUtils.utcDateTimeToLocal(startTimestampMs) / 1000); startTimestamp = (int) (DateTimeUtils.utcDateTimeToLocal(startTimestampMs) / 1000);
} }
@@ -611,27 +595,18 @@ public class PebbleProtocol extends GBDeviceProtocol {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
long ts_offset = (SimpleTimeZone.getDefault().getOffset(ts)); long ts_offset = (SimpleTimeZone.getDefault().getOffset(ts));
ByteBuffer buf; ByteBuffer buf;
if (mFwMajor >= 3) { String timezone = SimpleTimeZone.getDefault().getID();
String timezone = SimpleTimeZone.getDefault().getID(); short length = (short) (LENGTH_SETTIME + timezone.getBytes().length + 3);
short length = (short) (LENGTH_SETTIME + timezone.getBytes().length + 3); buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN);
buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(length);
buf.putShort(length); buf.putShort(ENDPOINT_TIME);
buf.putShort(ENDPOINT_TIME); buf.put(TIME_SETTIME_UTC);
buf.put(TIME_SETTIME_UTC); buf.putInt((int) (ts / 1000));
buf.putInt((int) (ts / 1000)); buf.putShort((short) (ts_offset / 60000));
buf.putShort((short) (ts_offset / 60000)); buf.put((byte) timezone.getBytes().length);
buf.put((byte) timezone.getBytes().length); buf.put(timezone.getBytes());
buf.put(timezone.getBytes()); LOG.info(timezone);
LOG.info(timezone);
} else {
buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SETTIME);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_SETTIME);
buf.putShort(ENDPOINT_TIME);
buf.put(TIME_SETTIME);
buf.putInt((int) ((ts + ts_offset) / 1000));
}
return buf.array(); return buf.array();
} }
@@ -884,12 +859,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
} }
} }
byte attributes_count = 0; byte attributes_count = 2;
short attributes_length = actions_length; short attributes_length = (short) (actions_length + 11);
if (mFwMajor >= 3) {
attributes_count += 2; // icon
attributes_length += 11;
}
for (String s : parts) { for (String s : parts) {
if (s == null || s.isEmpty()) { if (s == null || s.isEmpty()) {
@@ -903,48 +874,25 @@ public class PebbleProtocol extends GBDeviceProtocol {
int max_partlength; int max_partlength;
byte dismiss_action_type; byte dismiss_action_type;
ByteBuffer buf; ByteBuffer buf;
if (mFwMajor >= 3) { length = (short) (NOTIFICATION_PIN_LENGTH + attributes_length);
length = (short) (NOTIFICATION_PIN_LENGTH + attributes_length); max_partlength = 512;
max_partlength = 512; dismiss_action_type = 0x02; // generic action, dismiss did not do anything
dismiss_action_type = 0x02; // generic action, dismiss did not do anything buf = ByteBuffer.allocate(length);
buf = ByteBuffer.allocate(length);
} else {
length = (short) (21 + attributes_length);
max_partlength = 256;
dismiss_action_type = 0x04; // dismiss
buf = ByteBuffer.allocate(length + LENGTH_PREFIX);
}
buf.order(ByteOrder.BIG_ENDIAN); buf.order(ByteOrder.BIG_ENDIAN);
if (mFwMajor >= 3) { // pin - 46 bytes
// pin - 46 bytes buf.putLong(GB_UUID_MASK);
buf.putLong(GB_UUID_MASK); buf.putLong(id);
buf.putLong(id); buf.putLong(UUID_NOTIFICATIONS.getMostSignificantBits());
buf.putLong(UUID_NOTIFICATIONS.getMostSignificantBits()); buf.putLong(UUID_NOTIFICATIONS.getLeastSignificantBits());
buf.putLong(UUID_NOTIFICATIONS.getLeastSignificantBits()); buf.order(ByteOrder.LITTLE_ENDIAN);
buf.order(ByteOrder.LITTLE_ENDIAN); buf.putInt(timestamp); // 32-bit timestamp
buf.putInt(timestamp); // 32-bit timestamp buf.putShort((short) 0); // duration
buf.putShort((short) 0); // duration buf.put((byte) 0x01); // type (0x01 = notification)
buf.put((byte) 0x01); // type (0x01 = notification) buf.putShort((short) 0x0001); // flags 0x0001 = ?
buf.putShort((short) 0x0001); // flags 0x0001 = ? buf.put((byte) 0x04); // layout (0x04 = notification?)
buf.put((byte) 0x04); // layout (0x04 = notification?) buf.putShort(attributes_length); // total length of all attributes and actions in bytes
buf.putShort(attributes_length); // total length of all attributes and actions in bytes
} else {
buf.putShort(length);
buf.putShort(ENDPOINT_EXTENSIBLENOTIFS);
buf.order(ByteOrder.LITTLE_ENDIAN); // !
buf.put((byte) 0x00); // ?
buf.put((byte) 0x01); // add notifications
buf.putInt(0x00000000); // flags - ?
buf.putInt(id);
buf.putInt(0x00000000); // ANCS id
buf.putInt(timestamp);
buf.put((byte) 0x01); // layout - ?
}
buf.put(attributes_count); buf.put(attributes_count);
buf.put((byte) actions_count); buf.put((byte) actions_count);
@@ -963,18 +911,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put(s.getBytes(), 0, partlength); buf.put(s.getBytes(), 0, partlength);
} }
if (mFwMajor >= 3) { buf.put((byte) 4); // icon
buf.put((byte) 4); // icon buf.putShort((short) 4); // length of int
buf.putShort((short) 4); // length of int buf.putInt(0x80000000 | icon_id);
buf.putInt(0x80000000 | icon_id);
buf.put((byte) 28); // background_color buf.put((byte) 28); // background_color
buf.putShort((short) 1); // length of int buf.putShort((short) 1); // length of int
buf.put(backgroundColor); buf.put(backgroundColor);
}
if (attachedActions != null && !attachedActions.isEmpty()) { if (attachedActions != null && !attachedActions.isEmpty()) {
for (int ai = 0 ; ai<attachedActions.size(); ai++) { for (int ai = 0; ai < attachedActions.size(); ai++) {
Action act = attachedActions.get(ai); Action act = attachedActions.get(ai);
switch (act.type) { switch (act.type) {
case Action.TYPE_SYNTECTIC_OPEN: case Action.TYPE_SYNTECTIC_OPEN:
@@ -1022,32 +968,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
} }
} }
} }
if (mFwMajor >= 3) {
return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
} else {
return buf.array();
}
}
private byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) { return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
short length = (short) (18 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(length);
buf.putShort(ENDPOINT_EXTENSIBLENOTIFS);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(NOTIFICATIONACTION_RESPONSE);
buf.putInt(id);
buf.put(actionId);
buf.put(NOTIFICATIONACTION_ACK);
buf.put((byte) 2); //nr of attributes
buf.put((byte) 6); // icon
buf.putShort((short) 4); // length
buf.putInt(iconId);
buf.put((byte) 2); // title
buf.putShort((short) caption.getBytes().length);
buf.put(caption.getBytes());
return buf.array();
} }
private byte[] encodeWeatherPin(int timestamp, String title, String subtitle, String body, String location, int iconId) { private byte[] encodeWeatherPin(int timestamp, String title, String subtitle, String body, String location, int iconId) {
@@ -1338,10 +1260,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
@Override @Override
public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) { public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) {
if (mFwMajor < 3) {
return null;
}
byte playState = switch (state) { byte playState = switch (state) {
case MusicStateSpec.STATE_PLAYING -> MUSICCONTROL_STATE_PLAYING; case MusicStateSpec.STATE_PLAYING -> MUSICCONTROL_STATE_PLAYING;
case MusicStateSpec.STATE_PAUSED -> MUSICCONTROL_STATE_PAUSED; case MusicStateSpec.STATE_PAUSED -> MUSICCONTROL_STATE_PAUSED;
@@ -1369,7 +1287,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
@Override @Override
public byte[] encodeSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { public byte[] encodeSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
String[] parts = {artist, album, track}; String[] parts = {artist, album, track};
if (duration == 0 || mFwMajor < 3) { if (duration == 0) {
return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts); return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts);
} else { } else {
// Calculate length first // Calculate length first
@@ -1418,90 +1336,44 @@ public class PebbleProtocol extends GBDeviceProtocol {
@Override @Override
public byte[] encodeAppInfoReq() { public byte[] encodeAppInfoReq() {
if (mFwMajor >= 3) { return null; // not supported
return null; // can't do this on 3.x :(
}
return encodeSimpleMessage(ENDPOINT_APPMANAGER, APPMANAGER_GETUUIDS);
} }
@Override @Override
public byte[] encodeAppStart(UUID uuid, boolean start) { public byte[] encodeAppStart(UUID uuid, boolean start) {
if (mFwMajor >= 3) { final short LENGTH_APPRUNSTATE = 17;
final short LENGTH_APPRUNSTATE = 17; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_APPRUNSTATE); buf.order(ByteOrder.BIG_ENDIAN);
buf.order(ByteOrder.BIG_ENDIAN); buf.putShort(LENGTH_APPRUNSTATE);
buf.putShort(LENGTH_APPRUNSTATE); buf.putShort(ENDPOINT_APPRUNSTATE);
buf.putShort(ENDPOINT_APPRUNSTATE); buf.put(start ? APPRUNSTATE_START : APPRUNSTATE_STOP);
buf.put(start ? APPRUNSTATE_START : APPRUNSTATE_STOP); buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getMostSignificantBits()); buf.putLong(uuid.getLeastSignificantBits());
buf.putLong(uuid.getLeastSignificantBits()); return buf.array();
return buf.array();
} else {
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
int param = start ? 1 : 0;
pairs.add(new Pair<>(1, param));
return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs, null);
}
} }
@Override @Override
public byte[] encodeAppDelete(UUID uuid) { public byte[] encodeAppDelete(UUID uuid) {
if (mFwMajor >= 3) { if (UUID_PEBBLE_HEALTH.equals(uuid)) {
if (UUID_PEBBLE_HEALTH.equals(uuid)) { return encodeActivateHealth(false);
return encodeActivateHealth(false);
}
if (UUID_WORKOUT.equals(uuid)) {
return encodeActivateHRM(false);
}
if (UUID_WEATHER.equals(uuid)) { //TODO: probably it wasn't present in firmware 3
return encodeActivateWeather(false);
}
return encodeBlobdb(uuid, BLOBDB_DELETE, BLOBDB_APP, null);
} else {
final short LENGTH_REMOVEAPP_2X = 17;
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_REMOVEAPP_2X);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_REMOVEAPP_2X);
buf.putShort(ENDPOINT_APPMANAGER);
buf.put(APPMANAGER_REMOVEAPP);
buf.putLong(uuid.getMostSignificantBits());
buf.putLong(uuid.getLeastSignificantBits());
return buf.array();
} }
if (UUID_WORKOUT.equals(uuid)) {
return encodeActivateHRM(false);
}
if (UUID_WEATHER.equals(uuid)) { //TODO: probably it wasn't present in firmware 3
return encodeActivateWeather(false);
}
return encodeBlobdb(uuid, BLOBDB_DELETE, BLOBDB_APP, null);
} }
private byte[] encodePhoneVersion2x(byte os) { private byte[] encodePhoneVersion(byte os) {
final short LENGTH_PHONEVERSION = 17; final short LENGTH_PHONEVERSION = 25;
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION); ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION);
buf.order(ByteOrder.BIG_ENDIAN); buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_PHONEVERSION); buf.putShort(LENGTH_PHONEVERSION);
buf.putShort(ENDPOINT_PHONEVERSION); buf.putShort(ENDPOINT_PHONEVERSION);
buf.put((byte) 0x01); buf.put((byte) 0x01);
buf.putInt(-1); //0xffffffff buf.putInt(-1); //0xffffffff
if (os == PHONEVERSION_REMOTE_OS_ANDROID) {
buf.putInt(PHONEVERSION_SESSION_CAPS_GAMMARAY);
} else {
buf.putInt(0);
}
buf.putInt(PHONEVERSION_REMOTE_CAPS_SMS | PHONEVERSION_REMOTE_CAPS_TELEPHONY | os);
buf.put(PHONEVERSION_APPVERSION_MAGIC);
buf.put(PHONEVERSION_APPVERSION_MAJOR);
buf.put(PHONEVERSION_APPVERSION_MINOR);
buf.put(PHONEVERSION_APPVERSION_PATCH);
return buf.array();
}
private byte[] encodePhoneVersion3x(byte os) {
final short LENGTH_PHONEVERSION3X = 25;
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_PHONEVERSION3X);
buf.order(ByteOrder.BIG_ENDIAN);
buf.putShort(LENGTH_PHONEVERSION3X);
buf.putShort(ENDPOINT_PHONEVERSION);
buf.put((byte) 0x01);
buf.putInt(-1); //0xffffffff
buf.putInt(0); buf.putInt(0);
buf.putInt(os); buf.putInt(os);
@@ -1527,10 +1399,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
return buf.array(); return buf.array();
} }
private byte[] encodePhoneVersion(byte os) {
return encodePhoneVersion3x(os);
}
@Override @Override
public byte[] encodeReset(int flags) { public byte[] encodeReset(int flags) {
return encodeSimpleMessage(ENDPOINT_RESET, RESET_REBOOT); return encodeSimpleMessage(ENDPOINT_RESET, RESET_REBOOT);
@@ -1608,7 +1476,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
/* pebble specific install methods */ /* pebble specific install methods */
byte[] encodeUploadStart(byte type, int app_id, int size, String filename) { byte[] encodeUploadStart(byte type, int app_id, int size, String filename) {
short length; short length;
if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) { if (type != PUTBYTES_TYPE_FILE) {
length = (short) 10; length = (short) 10;
type |= (byte) 0b10000000; type |= (byte) 0b10000000;
} else { } else {
@@ -1627,7 +1495,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.putInt(size); buf.putInt(size);
buf.put(type); buf.put(type);
if (mFwMajor >= 3 && (type != PUTBYTES_TYPE_FILE)) { if (type != PUTBYTES_TYPE_FILE) {
buf.putInt(app_id); buf.putInt(app_id);
} else { } else {
// slot // slot
@@ -1874,8 +1742,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
GBDeviceEventSendBytes sendBytesAck = null; GBDeviceEventSendBytes sendBytesAck = null;
if (mAlwaysACKPebbleKit) { if (mAlwaysACKPebbleKit) {
// this is a hack we send an ack to the Pebble immediately because somebody said it helps some PebbleKit apps :P // this is a hack we send an ack to the Pebble immediately because somebody said it helps some PebbleKit apps :P
sendBytesAck = new GBDeviceEventSendBytes(); sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id);
} }
GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage(); GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage();
appMessage.appUUID = uuid; appMessage.appUUID = uuid;
@@ -2067,7 +1935,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
headerbuf.putInt(screenshotWidth); headerbuf.putInt(screenshotWidth);
headerbuf.putInt(-screenshotHeight); headerbuf.putInt(-screenshotHeight);
headerbuf.putShort((short) 1); // planes headerbuf.putShort((short) 1); // planes
headerbuf.putShort((short) screenshotBpp); headerbuf.putShort(screenshotBpp);
headerbuf.putInt(0); // compression headerbuf.putInt(0); // compression
headerbuf.putInt(0); // length of pixeldata in bytes (uncompressed=0) headerbuf.putInt(0); // length of pixeldata in bytes (uncompressed=0)
headerbuf.putInt(0); // pixels per meter (x) headerbuf.putInt(0); // pixels per meter (x)
@@ -2094,20 +1962,14 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte command = buf.get(); byte command = buf.get();
if (command == NOTIFICATIONACTION_INVOKE) { if (command == NOTIFICATIONACTION_INVOKE) {
int id; int id;
UUID uuid = new UUID(0,0); UUID uuid = getUUID(buf);
if (mFwMajor >= 3) { id = (int) (uuid.getLeastSignificantBits() & 0xffffffffL);
uuid = getUUID(buf);
id = (int) (uuid.getLeastSignificantBits() & 0xffffffffL);
} else {
id = buf.getInt();
}
byte action = buf.get(); byte action = buf.get();
if (action >= 0x00 && action <= 0xf) { if (action >= 0x00 && action <= 0xf) {
GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl(); GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
devEvtNotificationControl.handle = id; devEvtNotificationControl.handle = id;
String caption = "undefined"; String caption = "undefined";
int icon_id = 1; int icon_id = 1;
boolean needsAck2x = true;
switch (action) { switch (action) {
case 0x01: case 0x01:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN; devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN;
@@ -2118,43 +1980,41 @@ public class PebbleProtocol extends GBDeviceProtocol {
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS; devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
caption = "Dismissed"; caption = "Dismissed";
icon_id = PebbleIconID.RESULT_DISMISSED; icon_id = PebbleIconID.RESULT_DISMISSED;
needsAck2x = false;
break; break;
case 0x03: case 0x03:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL; devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL;
caption = "All dismissed"; caption = "All dismissed";
icon_id = PebbleIconID.RESULT_DISMISSED; icon_id = PebbleIconID.RESULT_DISMISSED;
needsAck2x = false;
break; break;
case 0x04: case 0x04:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE; devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
caption = "Muted"; caption = "Muted";
icon_id = PebbleIconID.RESULT_MUTE; icon_id = PebbleIconID.RESULT_MUTE;
break; break;
//TODO: 0x05 is not a special case anymore, and reply action might have an index that is higher. see default below //TODO: 0x05 is not a special case anymore, and reply action might have an index that is higher. see default below
case 0x00: case 0x00:
default: default:
boolean failed = true; boolean failed = true;
byte attribute_count = buf.get(); byte attribute_count = buf.get();
if (attribute_count > 0) { if (attribute_count > 0) {
byte attribute = buf.get(); byte attribute = buf.get();
if (attribute == 0x01) { // reply string is in attribute 0x01 if (attribute == 0x01) { // reply string is in attribute 0x01
short length = buf.getShort(); short length = buf.getShort();
if (length > 64) length = 64; if (length > 64) length = 64;
byte[] reply = new byte[length]; byte[] reply = new byte[length];
buf.get(reply); buf.get(reply);
devEvtNotificationControl.phoneNumber = null; devEvtNotificationControl.phoneNumber = null;
if (buf.remaining() > 1 && buf.get() == 0x0c) { if (buf.remaining() > 1 && buf.get() == 0x0c) {
short phoneNumberLength = buf.getShort(); short phoneNumberLength = buf.getShort();
byte[] phoneNumberBytes = new byte[phoneNumberLength]; byte[] phoneNumberBytes = new byte[phoneNumberLength];
buf.get(phoneNumberBytes); buf.get(phoneNumberBytes);
devEvtNotificationControl.phoneNumber = new String(phoneNumberBytes); devEvtNotificationControl.phoneNumber = new String(phoneNumberBytes);
}
devEvtNotificationControl.reply = new String(reply);
caption = "SENT";
icon_id = PebbleIconID.RESULT_SENT;
failed = false;
} }
devEvtNotificationControl.reply = new String(reply);
caption = "SENT";
icon_id = PebbleIconID.RESULT_SENT;
failed = false;
}
} else { } else {
icon_id = PebbleIconID.GENERIC_CONFIRMATION; icon_id = PebbleIconID.GENERIC_CONFIRMATION;
caption = "EXECUTED"; caption = "EXECUTED";
@@ -2170,14 +2030,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
break; break;
} }
GBDeviceEventSendBytes sendBytesAck = null; GBDeviceEventSendBytes sendBytesAck = null;
if (mFwMajor >= 3 || needsAck2x) { sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck = new GBDeviceEventSendBytes(); sendBytesAck.encodedBytes = encodeActionResponse(uuid, icon_id, caption);
if (mFwMajor >= 3) {
sendBytesAck.encodedBytes = encodeActionResponse(uuid, icon_id, caption);
} else {
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, caption);
}
}
return new GBDeviceEvent[]{sendBytesAck, devEvtNotificationControl}; return new GBDeviceEvent[]{sendBytesAck, devEvtNotificationControl};
} }
LOG.info("unexpected action: {}", action); LOG.info("unexpected action: {}", action);
@@ -2239,8 +2093,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (handler != null) { if (handler != null) {
currentRunningApp = uuid; currentRunningApp = uuid;
return handler.onAppStart(); return handler.onAppStart();
} } else {
else {
if (!uuid.equals(currentRunningApp)) { if (!uuid.equals(currentRunningApp)) {
currentRunningApp = uuid; currentRunningApp = uuid;
GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement(); GBDeviceEventAppManagement gbDeviceEventAppManagement = new GBDeviceEventAppManagement();
@@ -2395,8 +2248,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
byte status = buf.get(); byte status = buf.get();
if (status == 1) { if (status == 1) {
LOG.info("app reordering successful"); LOG.info("app reordering successful");
} } else {
else {
LOG.info("app reordering returned status {}", status); LOG.info("app reordering returned status {}", status);
} }
return null; return null;
@@ -2612,8 +2464,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (endpoint == ENDPOINT_APPLICATIONMESSAGE) { if (endpoint == ENDPOINT_APPLICATIONMESSAGE) {
ArrayList<Pair<Integer, Object>> dict = decodeDict(buf); ArrayList<Pair<Integer, Object>> dict = decodeDict(buf);
devEvts = handler.handleMessage(dict); devEvts = handler.handleMessage(dict);
} } else {
else {
devEvts = handler.onAppStart(); devEvts = handler.onAppStart();
} }
} else { } else {
@@ -2724,11 +2575,6 @@ public class PebbleProtocol extends GBDeviceProtocol {
return devEvts; return devEvts;
} }
void setForceProtocol(boolean force) {
LOG.info("setting force protocol to {}", force);
mForceProtocol = force;
}
void setAlwaysACKPebbleKit(boolean alwaysACKPebbleKit) { void setAlwaysACKPebbleKit(boolean alwaysACKPebbleKit) {
LOG.info("setting always ACK PebbleKit to {}", alwaysACKPebbleKit); LOG.info("setting always ACK PebbleKit to {}", alwaysACKPebbleKit);
mAlwaysACKPebbleKit = alwaysACKPebbleKit; mAlwaysACKPebbleKit = alwaysACKPebbleKit;
@@ -20,13 +20,6 @@
android:summary="@string/pref_summary_pebble_forceuntested" android:summary="@string/pref_summary_pebble_forceuntested"
android:title="@string/pref_title_pebble_forceuntested" android:title="@string/pref_title_pebble_forceuntested"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pebble_force_protocol"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_summary_pebble_forceprotocol"
android:title="@string/pref_title_pebble_forceprotocol"
app:iconSpaceReserved="false" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="pebble_gatt_clientonly" android:key="pebble_gatt_clientonly"