From 389a143bdbcb7064c50af8e09459f3bf3d8a95a9 Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Tue, 7 Jun 2016 19:34:37 +0200 Subject: [PATCH 01/22] Set music info for PocketCasts PocketCasts tells about its current media state via notifications. This patch tries to parse incoming notifications from PocketCasts and if successful tells the device about it. Currently supported are track and artist. --- .../externalevents/NotificationListener.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index a3c3d37c4..c12b20c60 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; @@ -182,6 +183,11 @@ public class NotificationListener extends NotificationListenerService { String source = sbn.getPackageName(); Notification notification = sbn.getNotification(); + if (source.equals("au.com.shiftyjelly.pocketcasts")) { + if (handlePocketCastNotification(notification)) + return; + } + if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { return; } @@ -311,6 +317,36 @@ public class NotificationListener extends NotificationListenerService { return false; } + /** + * Try to handle pocket cast notifications that tell info about the current play state. + * @param notification The notification to handle. + * @return true if notification was handled, false otherwise + */ + public boolean handlePocketCastNotification(Notification notification) { + MusicSpec musicSpec = new MusicSpec(); + + Bundle extras = notification.extras; + if (extras == null) + return false; + + CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); + if (title != null) + musicSpec.artist = title.toString(); + if (extras.containsKey(Notification.EXTRA_TEXT)) { + CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT); + if (contentCS != null) + musicSpec.track = contentCS.toString(); + } + + musicSpec.album = "unknown"; + + LOG.info("handlePocketCastsNotification: artist " + musicSpec.artist + ", track " + musicSpec.track); + + // finally, tell the device about it + GBApplication.deviceService().onSetMusicInfo(musicSpec); + return true; + } + @Override public void onNotificationRemoved(StatusBarNotification sbn) { From 32429df7bc81a817b841ba7a3811b551ccb22af9 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 7 Jun 2016 22:51:14 +0200 Subject: [PATCH 02/22] Pebble: allow to enable or disable sync for each activity tracker in settings This is useful if you have multiple phones and do not want to have your data synced to one of them --- .../pebble/AppMessageHandlerMisfit.java | 4 +--- .../pebble/AppMessageHandlerMorpheuz.java | 3 +-- .../pebble/DatalogSessionPebbleHealth.java | 4 +--- app/src/main/res/values/strings.xml | 6 +++++ app/src/main/res/xml/preferences.xml | 24 +++++++++++++++---- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java index 2e33b4a5d..1a7e4b97c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMisfit.java @@ -17,7 +17,6 @@ import nodomain.freeyourgadget.gadgetbridge.GBException; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; -import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider; import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; @@ -46,8 +45,7 @@ public class AppMessageHandlerMisfit extends AppMessageHandler { @Override public boolean isEnabled() { Prefs prefs = GBApplication.getPrefs(); - int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); - return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MISFIT); + return prefs.getBoolean("pebble_sync_misfit", true); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java index a9f1d9cb3..62f4d39a1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java @@ -60,8 +60,7 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler { @Override public boolean isEnabled() { Prefs prefs = GBApplication.getPrefs(); - int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); - return (activityTracker == SampleProvider.PROVIDER_PEBBLE_MORPHEUZ); + return prefs.getBoolean("pebble_sync_morpheuz", true); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java index acbbe9f7d..6df7a7514 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java @@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; abstract class DatalogSessionPebbleHealth extends DatalogSession { @@ -14,7 +13,6 @@ abstract class DatalogSessionPebbleHealth extends DatalogSession { protected boolean isPebbleHealthEnabled() { Prefs prefs = GBApplication.getPrefs(); - int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH); - return (activityTracker == SampleProvider.PROVIDER_PEBBLE_HEALTH); + return prefs.getBoolean("pebble_sync_health", true); } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 988e59050..35c8c70fe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,7 +74,13 @@ Mi Band address Pebble Settings + + Activity Trackers Preferred Activitytracker + Sync Pebble Health + Sync Misfit + Sync Morpheuz + Allow 3rd Party Android App Access Enable experimental support for Android Apps using PebbleKit diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index aa7843311..7c5723b6b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -215,17 +215,31 @@ android:key="pebble_reconnect_attempts" android:maxLength="4" android:title="@string/pref_title_pebble_reconnect_attempts" /> + + + + android:summary="%s" + android:title="@string/pref_title_pebble_activitytracker" /> + android:defaultValue="true" + android:key="pebble_sync_health" + android:title="@string/pref_title_pebble_sync_health" /> + > + From 0470731e4bbd2737e0f5a3dfd1562ea4efa4e199 Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:16:28 +0200 Subject: [PATCH 03/22] PebbleProtocol: Do not call encodeSetMusicState in encodeSetMusicInfo encodeSetMusicState will be accessible on its own. If it was used to set the music state, a call to encodeSetMusicInfo must not reset this info arbitrarily. --- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 6938c45cf..333971451 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 @@ -1126,7 +1126,6 @@ public class PebbleProtocol extends GBDeviceProtocol { if (duration == 0) { return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts); } else { - byte[] stateMessage = encodeSetMusicState(MUSICCONTROL_STATE_PLAYING, 0, 100, (byte) 1, (byte) 1); // Calculate length first int length = LENGTH_PREFIX + 9; if (parts != null) { @@ -1140,7 +1139,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } // Encode Prefix - ByteBuffer buf = ByteBuffer.allocate(length + stateMessage.length); + ByteBuffer buf = ByteBuffer.allocate(length); buf.order(ByteOrder.BIG_ENDIAN); buf.putShort((short) (length - LENGTH_PREFIX)); buf.putShort(ENDPOINT_MUSICCONTROL); @@ -1164,8 +1163,6 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.putShort((short) (trackCount & 0xffff)); buf.putShort((short) (trackNr & 0xffff)); - buf.put(stateMessage); - return buf.array(); } } From 1d5c8bae9d7a0070185fbcb1026a04d3350a9dc7 Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:22:05 +0200 Subject: [PATCH 04/22] MusicStateSpec: introduce new class describing the music state Contains: - state - position - playRate - shuffle - repeat This is close to what PebbleProtocol currently supports. --- .../gadgetbridge/model/MusicStateSpec.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java new file mode 100644 index 000000000..59ec114f7 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -0,0 +1,12 @@ +package nodomain.freeyourgadget.gadgetbridge.model; + +/** + * Created by steffen on 07.06.16. + */ +public class MusicStateSpec { + public byte state; + public int position; + public int playRate; + public byte shuffle; + public byte repeat; +} From e386d6da439c38340818ef11922349f9fbb1396d Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:27:25 +0200 Subject: [PATCH 05/22] Add onSetMusicState(MusicStateSpec stateSpec) This commit contains the infrastructure needed for the NotificationHandler to send music state information to the device. That is, it introduces a call onSetMusicState(MusicStateSpec stateSpec), that in turn sets up an intent to the service, which will then call the encodeSetMusicState() function of the device. encodeSetMusicState is available for pebble only. There are empty stubs for other devices. --- .../gadgetbridge/devices/EventHandler.java | 3 +++ .../gadgetbridge/impl/GBDeviceService.java | 12 ++++++++++++ .../gadgetbridge/model/DeviceService.java | 6 ++++++ .../service/DeviceCommunicationService.java | 16 ++++++++++++++++ .../service/ServiceDeviceSupport.java | 9 +++++++++ .../service/devices/miband/MiBandSupport.java | 6 ++++++ .../service/devices/pebble/PebbleSupport.java | 8 ++++++++ .../serial/AbstractSerialDeviceSupport.java | 7 +++++++ .../service/serial/GBDeviceProtocol.java | 4 ++++ .../gadgetbridge/service/TestDeviceSupport.java | 6 ++++++ 10 files changed, 77 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index 602ca5c85..a275ace51 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -9,6 +9,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; /** @@ -25,6 +26,8 @@ public interface EventHandler { void onSetCallState(CallSpec callSpec); + void onSetMusicState(MusicStateSpec stateSpec); + void onSetMusicInfo(MusicSpec musicSpec); void onEnableRealtimeSteps(boolean enable); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 2e598eacf..60ff7b526 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -14,6 +14,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; @@ -125,6 +126,17 @@ public class GBDeviceService implements DeviceService { invokeService(intent); } + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + Intent intent = createIntent().setAction(ACTION_SETMUSICSTATE) + .putExtra(EXTRA_MUSIC_REPEAT, stateSpec.repeat) + .putExtra(EXTRA_MUSIC_RATE, stateSpec.playRate) + .putExtra(EXTRA_MUSIC_STATE, stateSpec.state) + .putExtra(EXTRA_MUSIC_SHUFFLE, stateSpec.shuffle) + .putExtra(EXTRA_MUSIC_POSITION, stateSpec.position); + invokeService(intent); + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { Intent intent = createIntent().setAction(ACTION_SETMUSICINFO) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index aaf02798b..b3921b1b8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -18,6 +18,7 @@ public interface DeviceService extends EventHandler { String ACTION_CALLSTATE = PREFIX + ".action.callstate"; String ACTION_SETTIME = PREFIX + ".action.settime"; String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo"; + String ACTION_SETMUSICSTATE = PREFIX + ".action.setmusicstate"; String ACTION_REQUEST_DEVICEINFO = PREFIX + ".action.request_deviceinfo"; String ACTION_REQUEST_APPINFO = PREFIX + ".action.request_appinfo"; String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot"; @@ -57,6 +58,11 @@ public interface DeviceService extends EventHandler { String EXTRA_MUSIC_DURATION = "music_duration"; String EXTRA_MUSIC_TRACKNR = "music_tracknr"; String EXTRA_MUSIC_TRACKCOUNT = "music_trackcount"; + String EXTRA_MUSIC_STATE = "music_state"; + String EXTRA_MUSIC_SHUFFLE = "music_shuffle"; + String EXTRA_MUSIC_REPEAT = "music_repeat"; + String EXTRA_MUSIC_POSITION = "music_position"; + String EXTRA_MUSIC_RATE = "music_rate"; String EXTRA_APP_UUID = "app_uuid"; String EXTRA_APP_START = "app_start"; String EXTRA_APP_CONFIG = "app_config"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index fdafa5265..7903ff22a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; @@ -65,6 +66,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_RE import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_DEVICEINFO; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_REQUEST_SCREENSHOT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICINFO; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETMUSICSTATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START; @@ -87,6 +89,11 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIN import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_POSITION; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_RATE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_REPEAT; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_SHUFFLE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_STATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACK; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKCOUNT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_TRACKNR; @@ -351,6 +358,15 @@ public class DeviceCommunicationService extends Service implements SharedPrefere musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0); mDeviceSupport.onSetMusicInfo(musicSpec); break; + case ACTION_SETMUSICSTATE: + MusicStateSpec stateSpec = new MusicStateSpec(); + stateSpec.shuffle = intent.getByteExtra(EXTRA_MUSIC_SHUFFLE, (byte)0); + stateSpec.repeat = intent.getByteExtra(EXTRA_MUSIC_REPEAT, (byte)0); + stateSpec.position = intent.getIntExtra(EXTRA_MUSIC_POSITION, 0); + stateSpec.playRate = intent.getIntExtra(EXTRA_MUSIC_RATE, 0); + stateSpec.state = intent.getByteExtra(EXTRA_MUSIC_STATE, (byte)0); + mDeviceSupport.onSetMusicState(stateSpec); + break; case ACTION_REQUEST_APPINFO: mDeviceSupport.onAppInfoReq(); break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index 019722b54..e284f83d8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -16,6 +16,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; /** @@ -150,6 +151,14 @@ public class ServiceDeviceSupport implements DeviceSupport { delegate.onSetCallState(callSpec); } + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + if (checkBusy("set music state")) { + return; + } + delegate.onSetMusicState(stateSpec); + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { if (checkBusy("set music info")) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index fd00d02ac..165e72e50 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -38,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; @@ -599,6 +600,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { return telephoneRinging; } + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + // not supported + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { // not supported diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index b0bc3b47d..7233a2be8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -15,6 +15,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; @@ -108,6 +109,13 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { } } + @Override + public void onSetMusicState(MusicStateSpec musicStateSpec) { + if (reconnect()) { + super.onSetMusicState(musicStateSpec); + } + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { if (reconnect()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index 78e15667d..e963155eb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -11,6 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; @@ -123,6 +124,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport sendToDevice(bytes); } + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + byte[] bytes = gbDeviceProtocol.encodeSetMusicState(stateSpec.state, stateSpec.position, stateSpec.playRate, stateSpec.shuffle, stateSpec.repeat); + sendToDevice(bytes); + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index a226c0947..2492f2773 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -24,6 +24,10 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) { + return null; + } + public byte[] encodeFirmwareVersionReq() { return null; } diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java index 8070cb1eb..42b4dd5ce 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java @@ -12,6 +12,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; public class TestDeviceSupport extends AbstractDeviceSupport { @@ -66,6 +67,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport { } + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + @Override public void onSetMusicInfo(MusicSpec musicSpec) { From 73fbaf0a54f7ea817e406c7cc6e4ff2c0b1fabbe Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:32:32 +0200 Subject: [PATCH 06/22] Restore previous working of the debug activity The previous commits broke the debug activity's setting of the music info. This commit restores this functionality. --- .../gadgetbridge/activities/DebugActivity.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index c832148f4..3922505ef 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; @@ -222,6 +223,15 @@ public class DebugActivity extends GBActivity { musicSpec.trackNr = 2; GBApplication.deviceService().onSetMusicInfo(musicSpec); + + MusicStateSpec stateSpec = new MusicStateSpec(); + stateSpec.position = 0; + stateSpec.state = 0x01; // playing + stateSpec.playRate = 100; + stateSpec.repeat = 1; + stateSpec.shuffle = 1; + + GBApplication.deviceService().onSetMusicState(stateSpec); } }); From fb71cdf55b2ab539b9c13ca7595565279f98ff25 Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:33:20 +0200 Subject: [PATCH 07/22] Add handling for media session notifications Since Android 5.0, media players can have interactive notifications that reside in the notification area, and offer up to 5 control buttons (play/pause, next, previous, etc), and information about the currentlu playing media file. We use these notifications to get information about the currently playing media file such as: - artist - track (title) - album - duration (length of the media file) - play state (playing, paused, stopped) - position - play rate (how fast is the media file being played) We then send this information up to the device. On Pebble, the music app will display the title and the artist, as well as a progress bar showing the current position. The progress bar is animated when the media file is being played, and if it is being paused, it displays a pause symbol. This code will be skipped when GadgetBridge is run on a device with Android version older than 5.0 (lollipop). --- .../externalevents/NotificationListener.java | 72 +++++++++++++++---- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index c12b20c60..a6abd06fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -11,8 +11,14 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.media.MediaMetadata; +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.os.Build; import android.os.Bundle; import android.os.PowerManager; +import android.provider.MediaStore; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.v4.app.NotificationCompat; @@ -26,6 +32,7 @@ import java.util.List; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; @@ -183,10 +190,8 @@ public class NotificationListener extends NotificationListenerService { String source = sbn.getPackageName(); Notification notification = sbn.getNotification(); - if (source.equals("au.com.shiftyjelly.pocketcasts")) { - if (handlePocketCastNotification(notification)) - return; - } + if (handleMediaSessionNotification(notification)) + return; if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { return; @@ -318,32 +323,69 @@ public class NotificationListener extends NotificationListenerService { } /** - * Try to handle pocket cast notifications that tell info about the current play state. + * Try to handle media session notifications that tell info about the current play state. + * * @param notification The notification to handle. * @return true if notification was handled, false otherwise */ - public boolean handlePocketCastNotification(Notification notification) { + public boolean handleMediaSessionNotification(Notification notification) { + + // this code requires Android 5.0 or newer + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return false; + } + MusicSpec musicSpec = new MusicSpec(); + MusicStateSpec stateSpec = new MusicStateSpec(); Bundle extras = notification.extras; if (extras == null) return false; - CharSequence title = extras.getCharSequence(Notification.EXTRA_TITLE); - if (title != null) - musicSpec.artist = title.toString(); - if (extras.containsKey(Notification.EXTRA_TEXT)) { - CharSequence contentCS = extras.getCharSequence(Notification.EXTRA_TEXT); - if (contentCS != null) - musicSpec.track = contentCS.toString(); + if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null) + return false; + + MediaController c; + try { + c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION)); + } catch (NullPointerException e) { + return false; } - musicSpec.album = "unknown"; + PlaybackState s = c.getPlaybackState(); + stateSpec.position = (int)s.getPosition(); + stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); + stateSpec.repeat = 1; + stateSpec.shuffle = 1; + switch (s.getState()) { + case PlaybackState.STATE_PLAYING: + stateSpec.state = 0x01; + break; + case PlaybackState.STATE_STOPPED: + case PlaybackState.STATE_PAUSED: + stateSpec.state = 0x00; + break; + default: + stateSpec.state = 0x04; + break; + } - LOG.info("handlePocketCastsNotification: artist " + musicSpec.artist + ", track " + musicSpec.track); + MediaMetadata d = c.getMetadata(); + if (d == null) + return false; + if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) + musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST); + if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) + musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM); + if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) + musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); + if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) + musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; // finally, tell the device about it GBApplication.deviceService().onSetMusicInfo(musicSpec); + GBApplication.deviceService().onSetMusicState(stateSpec); + return true; } From 204748c518c0477689c05730c30ab57859ef89fd Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Wed, 8 Jun 2016 20:43:46 +0200 Subject: [PATCH 08/22] "duration" parameter in onSetMusicInfo uses microseconds This is in line with Android, and saves some calculations (and thereby a tiny little bit of battery life). --- .../freeyourgadget/gadgetbridge/activities/DebugActivity.java | 2 +- .../gadgetbridge/externalevents/NotificationListener.java | 2 +- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index 3922505ef..feea0e95c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -218,7 +218,7 @@ public class DebugActivity extends GBActivity { musicSpec.artist = editContent.getText().toString() + "(artist)"; musicSpec.album = editContent.getText().toString() + "(album)"; musicSpec.track = editContent.getText().toString() + "(track)"; - musicSpec.duration = 10; + musicSpec.duration = 10 * 1000; musicSpec.trackCount = 5; musicSpec.trackNr = 2; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index a6abd06fb..369d18bac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -380,7 +380,7 @@ public class NotificationListener extends NotificationListenerService { if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) - musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; + musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION); // finally, tell the device about it GBApplication.deviceService().onSetMusicInfo(musicSpec); 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 333971451..ffa2ce873 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 @@ -1159,7 +1159,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(duration * 1000); + buf.putInt(duration); buf.putShort((short) (trackCount & 0xffff)); buf.putShort((short) (trackNr & 0xffff)); From 91f374edec601c001288f9018830f2173a11a06c Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Thu, 9 Jun 2016 20:02:01 +0200 Subject: [PATCH 09/22] Revert ""duration" parameter in onSetMusicInfo uses microseconds" The decision on granularity of APIs is up to the maintainers. This reverts commit 204748c518c0477689c05730c30ab57859ef89fd. --- .../freeyourgadget/gadgetbridge/activities/DebugActivity.java | 2 +- .../gadgetbridge/externalevents/NotificationListener.java | 2 +- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java index feea0e95c..3922505ef 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -218,7 +218,7 @@ public class DebugActivity extends GBActivity { musicSpec.artist = editContent.getText().toString() + "(artist)"; musicSpec.album = editContent.getText().toString() + "(album)"; musicSpec.track = editContent.getText().toString() + "(track)"; - musicSpec.duration = 10 * 1000; + musicSpec.duration = 10; musicSpec.trackCount = 5; musicSpec.trackNr = 2; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 369d18bac..a6abd06fb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -380,7 +380,7 @@ public class NotificationListener extends NotificationListenerService { if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) - musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION); + musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; // finally, tell the device about it GBApplication.deviceService().onSetMusicInfo(musicSpec); 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 ffa2ce873..333971451 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 @@ -1159,7 +1159,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putInt(duration); + buf.putInt(duration * 1000); buf.putShort((short) (trackCount & 0xffff)); buf.putShort((short) (trackNr & 0xffff)); From c5262869d94922123ee88776c9105fb781b22a9a Mon Sep 17 00:00:00 2001 From: Steffen Liebergeld Date: Thu, 9 Jun 2016 20:00:14 +0200 Subject: [PATCH 10/22] Use names for playstates These names need to be mapped to device specific constants in the device code. --- .../externalevents/NotificationListener.java | 8 +++++--- .../gadgetbridge/model/MusicStateSpec.java | 5 +++++ .../service/devices/pebble/PebbleProtocol.java | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index a6abd06fb..16647ff61 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -359,14 +359,16 @@ public class NotificationListener extends NotificationListenerService { stateSpec.shuffle = 1; switch (s.getState()) { case PlaybackState.STATE_PLAYING: - stateSpec.state = 0x01; + stateSpec.state = MusicStateSpec.STATE_PLAYING; break; case PlaybackState.STATE_STOPPED: + stateSpec.state = MusicStateSpec.STATE_STOPPED; + break; case PlaybackState.STATE_PAUSED: - stateSpec.state = 0x00; + stateSpec.state = MusicStateSpec.STATE_PAUSED; break; default: - stateSpec.state = 0x04; + stateSpec.state = MusicStateSpec.STATE_UNKNOWN; break; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java index 59ec114f7..ee5510fbd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -4,6 +4,11 @@ package nodomain.freeyourgadget.gadgetbridge.model; * Created by steffen on 07.06.16. */ public class MusicStateSpec { + public static final int STATE_PLAYING = 0; + public static final int STATE_PAUSED = 1; + public static final int STATE_STOPPED = 2; + public static final int STATE_UNKNOWN = 3; + public byte state; public int position; public int playRate; 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 333971451..11decdc95 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 @@ -35,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; @@ -1102,6 +1103,20 @@ public class PebbleProtocol extends GBDeviceProtocol { } public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) { + byte playState; + + switch (state) { + case MusicStateSpec.STATE_PLAYING: + playState = MUSICCONTROL_STATE_PLAYING; + break; + case MusicStateSpec.STATE_PAUSED: + playState = MUSICCONTROL_STATE_PAUSED; + break; + default: + playState = MUSICCONTROL_STATE_UNKNOWN; + break; + } + int length = LENGTH_PREFIX + 12; // Encode Prefix ByteBuffer buf = ByteBuffer.allocate(length); @@ -1111,7 +1126,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.order(ByteOrder.LITTLE_ENDIAN); buf.put(MUSICCONTROL_SETPLAYSTATE); - buf.put(state); + buf.put(playState); buf.putInt(position); buf.putInt(playRate); buf.put(shuffle); From b76619bb5bd4e9dde1c00e0c68177d73e288bacc Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 9 Jun 2016 19:55:36 +0200 Subject: [PATCH 11/22] Pebble: implement app reordering in PebbleProtocol Not yet used. --- .../devices/pebble/PebbleProtocol.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) 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 11decdc95..7672c5fbd 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 @@ -70,6 +70,7 @@ public class PebbleProtocol extends GBDeviceProtocol { static final short ENDPOINT_RUNKEEPER = 7000; static final short ENDPOINT_SCREENSHOT = 8000; static final short ENDPOINT_NOTIFICATIONACTION = 11440; // 3.x only, TODO: find a better name + static final short ENDPOINT_APPREORDER = (short) 0xabcd; // 3.x only static final short ENDPOINT_BLOBDB = (short) 45531; // 3.x only static final short ENDPOINT_PUTBYTES = (short) 48879; @@ -1292,6 +1293,22 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeSimpleMessage(ENDPOINT_SCREENSHOT, SCREENSHOT_TAKE); } + public byte[] encodeAppReoder(UUID[] uuids) { + int length = 2 + uuids.length * LENGTH_UUID; + ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); + buf.order(ByteOrder.BIG_ENDIAN); + buf.putShort((short) length); + buf.putShort(ENDPOINT_APPREORDER); + buf.put((byte) 0x01); + buf.put((byte) uuids.length); + for (UUID uuid : uuids) { + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits()); + } + + return buf.array(); + } + /* pebble specific install methods */ public byte[] encodeUploadStart(byte type, int app_id, int size, String filename) { short length; @@ -1947,6 +1964,16 @@ public class PebbleProtocol extends GBDeviceProtocol { return sendBytes; } + private GBDeviceEvent decodeAppReorder(ByteBuffer buf) { + byte status = buf.get(); + if (status == 1) { + LOG.info("app reordering successful"); + } else { + LOG.info("app reordering returned status " + status); + } + return null; + } + @Override public GBDeviceEvent[] decodeResponse(byte[] responseData) { ByteBuffer buf = ByteBuffer.wrap(responseData); @@ -2197,6 +2224,8 @@ public class PebbleProtocol extends GBDeviceProtocol { case ENDPOINT_BLOBDB: devEvts = new GBDeviceEvent[]{decodeBlobDb(buf)}; break; + case ENDPOINT_APPREORDER: + devEvts = new GBDeviceEvent[]{decodeAppReorder(buf)}; default: break; } From 66b5a21cf27d7d7dc4d65c18d08669dd00ba0a53 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 9 Jun 2016 23:39:00 +0200 Subject: [PATCH 12/22] also gather music info from notifications when screen is off --- .../externalevents/NotificationListener.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 16647ff61..d8e0f0214 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -169,13 +169,6 @@ public class NotificationListener extends NotificationListenerService { return; } - Prefs prefs = GBApplication.getPrefs(); - if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) { - PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); - if (powermanager.isScreenOn()) { - return; - } - } switch (GBApplication.getGrantedInterruptionFilter()) { case NotificationManager.INTERRUPTION_FILTER_ALL: break; @@ -193,6 +186,14 @@ public class NotificationListener extends NotificationListenerService { if (handleMediaSessionNotification(notification)) return; + Prefs prefs = GBApplication.getPrefs(); + if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) { + PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE); + if (powermanager.isScreenOn()) { + return; + } + } + if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) { return; } From 243250f41fb43049ddca57aedbd1cadc3d27a08a Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 00:08:00 +0200 Subject: [PATCH 13/22] update translations from transifex (thanks!) --- app/src/main/res/values-it/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 6 ++++++ app/src/main/res/values-uk/strings.xml | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6ca7ee3b6..3f0dc77a6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -238,5 +238,4 @@ Firmware non inviato Battito cardiaco Battito cardiaco - Offset orologio del dispositivo in ore (per l\'identificazione del sonno dei lavoratori a turni) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ccdfc41a4..2ec72ca73 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -37,6 +37,7 @@ テーマ ライト ダーク + 言語 通知 繰り返し 電話通知 @@ -57,7 +58,11 @@ 開発者用設定 Mi Bandのアドレス Pebbleの設定 + アクティビティ トラッカー お好みのアクティビティ トラッカー + Pebble Health 同期 + Misfit 同期 + Morpheuz 同期 第三者のアンドロイドアップにアクセス権利を与える PebbleKitを使用してAndroidアプリ用の実験的なサポートを有効にします 日の出と日の入り @@ -214,6 +219,7 @@ このファームウェアは、デバイスと互換性がありません 今後のイベントのために予約するアラーム 睡眠の検出を改善するために心拍センサーを使用する + デバイスの時刻オフセット時間 (交代勤務の睡眠検知用) 再接続の待機中 再インストール あなたについて diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7f79179f6..766b99bb8 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -34,6 +34,7 @@ Тема Світла Темна + Мова Сповіщення Повтори Виклики @@ -203,4 +204,6 @@ Вимкнути Конфігурація Додати віджет + Пристрій: %1$s + ПЗ: %1$s From 6de002c88bec8ef8813b39de7504ba1c6296ebc9 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 22:20:55 +0200 Subject: [PATCH 14/22] also try to get track number and number of tracks from notifications --- .../gadgetbridge/externalevents/NotificationListener.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index d8e0f0214..1d39d552b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -384,6 +384,10 @@ public class NotificationListener extends NotificationListenerService { musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; + if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + musicSpec.trackCount = (int)d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS); + if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + musicSpec.trackNr = (int)d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); // finally, tell the device about it GBApplication.deviceService().onSetMusicInfo(musicSpec); From 26ca526fdd81d976653cd1b16c14492857c3bffe Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 22:23:06 +0200 Subject: [PATCH 15/22] update gradle plugin --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 819874a7d..86fd2bad3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 771ff7b2bed49ff1d76afa829ff44972c6159c15 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 22:39:00 +0200 Subject: [PATCH 16/22] bump version, update changelog --- CHANGELOG.md | 7 +++++++ app/build.gradle | 4 ++-- app/src/main/res/xml/changelog_master.xml | 7 +++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34cdbdc9..71829f4d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ ###Changelog +####Version 0.10.1 +* Pebble: set extended music info by dissecting notifications on Android 5.0+ +* Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble) +* Mi Band: support for shifting the device time by N hours (for people who sleep at daytime) +* Mi Band: initial and untested support for Mi Band 2 +* Allow setting the application language + ####Version 0.10.0 * Pebble: option to send sunrise and sunset events to timeline * Pebble: fix problems with unknown app keys while configuring watchfaces diff --git a/app/build.gradle b/app/build.gradle index f6cd378ff..9cfc069a2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.10.0" - versionCode 53 + versionName "0.10.1" + versionCode 54 } buildTypes { release { diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index a49185489..a92e5fb7a 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,5 +1,12 @@ + + Pebble: set extended music info by dissecting notifications on Android 5.0+ + Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble) + Mi Band: support for shifting the device time by N hours (for people who sleep at daytime) + Mi Band: initial and untested support for Mi Band 2 + Allow setting the application language + Pebble: option to send sunrise and sunset events to timeline Pebble: fix problems with unknown app keys while configuring watchfaces From 8d3bd494b4600bb486e1efe740ecde7cb38c8501 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 22:45:38 +0200 Subject: [PATCH 17/22] fix broken xml tag --- app/src/main/res/xml/changelog_master.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index a92e5fb7a..e8e55dea3 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -5,7 +5,7 @@ Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble) Mi Band: support for shifting the device time by N hours (for people who sleep at daytime) Mi Band: initial and untested support for Mi Band 2 - Allow setting the application language + Allow setting the application language Pebble: option to send sunrise and sunset events to timeline From d1a62968f69ebb7dd6dd7cb273a06aa0761681a4 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Fri, 10 Jun 2016 23:13:33 +0200 Subject: [PATCH 18/22] Do not send new audio metadata to device if nothing has changed This prevents some players which send a metadata changed intent every second to drain the battery --- .../externalevents/MusicPlaybackReceiver.java | 23 ++++++++--------- .../gadgetbridge/model/MusicSpec.java | 25 ++++++++++++++++--- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index 67c7e8e70..2036b4c27 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,20 +12,20 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; public class MusicPlaybackReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); + private static MusicSpec lastMusicSpec = new MusicSpec(); @Override public void onReceive(Context context, Intent intent) { - String artist = intent.getStringExtra("artist"); - String album = intent.getStringExtra("album"); - String track = intent.getStringExtra("track"); - - LOG.info("Current track: " + artist + ", " + album + ", " + track); - MusicSpec musicSpec = new MusicSpec(); - musicSpec.artist = artist; - musicSpec.album = album; - musicSpec.track = track; - - GBApplication.deviceService().onSetMusicInfo(musicSpec); + musicSpec.artist = intent.getStringExtra("artist"); + musicSpec.album = intent.getStringExtra("album"); + musicSpec.track = intent.getStringExtra("track"); + if (!lastMusicSpec.equals(musicSpec)) { + lastMusicSpec = musicSpec; + LOG.info("Update Music Info: " + musicSpec.artist + " / " + musicSpec.album + " / " + musicSpec.track); + GBApplication.deviceService().onSetMusicInfo(musicSpec); + } else { + LOG.info("got metadata changed intent, but nothing changed, ignoring."); + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java index af52f02f1..b941514fa 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -8,10 +8,29 @@ public class MusicSpec { public static final int MUSIC_NEXT = 4; public static final int MUSIC_PREVIOUS = 5; - public String artist; - public String album; - public String track; + public String artist = ""; + public String album = ""; + public String track = ""; public int duration; public int trackCount; public int trackNr; + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof MusicSpec)) { + return false; + } + MusicSpec musicSpec = (MusicSpec) obj; + + return this.artist.equals(musicSpec.artist) && + this.album.equals(musicSpec.album) && + this.track.equals(musicSpec.track) && + this.duration == musicSpec.duration && + this.trackCount == musicSpec.trackCount && + this.trackNr == musicSpec.trackNr; + + } } From 2d080cabb2689b56b720f05d6defd6f88d568d80 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 11 Jun 2016 22:32:38 +0200 Subject: [PATCH 19/22] fix NPE by using Objects.equals() --- .../gadgetbridge/model/MusicSpec.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java index b941514fa..aa9f67ca9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.model; +import java.util.Objects; + public class MusicSpec { public static final int MUSIC_UNDEFINED = 0; public static final int MUSIC_PLAY = 1; @@ -8,9 +10,9 @@ public class MusicSpec { public static final int MUSIC_NEXT = 4; public static final int MUSIC_PREVIOUS = 5; - public String artist = ""; - public String album = ""; - public String track = ""; + public String artist; + public String album; + public String track; public int duration; public int trackCount; public int trackNr; @@ -25,9 +27,9 @@ public class MusicSpec { } MusicSpec musicSpec = (MusicSpec) obj; - return this.artist.equals(musicSpec.artist) && - this.album.equals(musicSpec.album) && - this.track.equals(musicSpec.track) && + return Objects.equals(this.artist, musicSpec.artist) && + Objects.equals(this.album, musicSpec.album) && + Objects.equals(this.track, musicSpec.track) && this.duration == musicSpec.duration && this.trackCount == musicSpec.trackCount && this.trackNr == musicSpec.trackNr; From f812fb1b1f4eef52118909dfe3f16e19f0bed079 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 11 Jun 2016 23:37:03 +0200 Subject: [PATCH 20/22] Improvements to MusicPlayback receiver - Also send duration if "duration" extra is present - If "playing" and "postion" extras are present send a music state update treat previous state and current state as equal if position delta is <=2 seconds (Neccessary for some players which update every second - the pebble however counts by itself) --- .../externalevents/MusicPlaybackReceiver.java | 25 +++++++++++++++++++ .../externalevents/NotificationListener.java | 3 +-- .../gadgetbridge/model/MusicStateSpec.java | 17 +++++++++++++ .../devices/pebble/PebbleProtocol.java | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index 2036b4c27..3edfb8815 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -9,17 +9,29 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; public class MusicPlaybackReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); private static MusicSpec lastMusicSpec = new MusicSpec(); + private static MusicStateSpec lastStatecSpec = new MusicStateSpec(); @Override public void onReceive(Context context, Intent intent) { + /* + Bundle bundle = intent.getExtras(); + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + LOG.info(String.format("%s %s (%s)", key, + value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class")); + } + */ MusicSpec musicSpec = new MusicSpec(); musicSpec.artist = intent.getStringExtra("artist"); musicSpec.album = intent.getStringExtra("album"); musicSpec.track = intent.getStringExtra("track"); + musicSpec.duration = intent.getIntExtra("duration", 0) / 1000; + if (!lastMusicSpec.equals(musicSpec)) { lastMusicSpec = musicSpec; LOG.info("Update Music Info: " + musicSpec.artist + " / " + musicSpec.album + " / " + musicSpec.track); @@ -27,5 +39,18 @@ public class MusicPlaybackReceiver extends BroadcastReceiver { } else { LOG.info("got metadata changed intent, but nothing changed, ignoring."); } + + if (intent.hasExtra("position") && intent.hasExtra("playing")) { + MusicStateSpec stateSpec = new MusicStateSpec(); + stateSpec.position = intent.getIntExtra("position", 0) / 1000; + stateSpec.state = (byte) (intent.getBooleanExtra("playing", true) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED); + if (!lastStatecSpec.equals(stateSpec)) { + LOG.info("Update Music State: state=" + stateSpec.state + ", position= " + stateSpec.position); + GBApplication.deviceService().onSetMusicState(stateSpec); + } else { + LOG.info("got state changed intent, but not enough has changed, ignoring."); + } + lastStatecSpec = stateSpec; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 1d39d552b..cf74be6a4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -18,7 +18,6 @@ import android.media.session.PlaybackState; import android.os.Build; import android.os.Bundle; import android.os.PowerManager; -import android.provider.MediaStore; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.v4.app.NotificationCompat; @@ -354,7 +353,7 @@ public class NotificationListener extends NotificationListenerService { } PlaybackState s = c.getPlaybackState(); - stateSpec.position = (int)s.getPosition(); + stateSpec.position = (int) (s.getPosition() / 1000); stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); stateSpec.repeat = 1; stateSpec.shuffle = 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java index ee5510fbd..fb15948d7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -14,4 +14,21 @@ public class MusicStateSpec { public int playRate; public byte shuffle; public byte repeat; + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof MusicStateSpec)) { + return false; + } + MusicStateSpec stateSpec = (MusicStateSpec) obj; + + return this.state == stateSpec.state && + Math.abs(this.position - stateSpec.position)<=2 && + this.playRate == stateSpec.playRate && + this.shuffle == stateSpec.shuffle && + this.repeat == stateSpec.repeat; + } } 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 7672c5fbd..1862f3c1f 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 @@ -1128,7 +1128,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.order(ByteOrder.LITTLE_ENDIAN); buf.put(MUSICCONTROL_SETPLAYSTATE); buf.put(playState); - buf.putInt(position); + buf.putInt(position * 1000); buf.putInt(playRate); buf.put(shuffle); buf.put(repeat); From f20b659b8620447befaa156ec34d80e915a79f85 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 11 Jun 2016 23:45:14 +0200 Subject: [PATCH 21/22] update changelog again --- CHANGELOG.md | 1 + app/src/main/res/xml/changelog_master.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71829f4d9..19ab2fbbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ###Changelog ####Version 0.10.1 * Pebble: set extended music info by dissecting notifications on Android 5.0+ +* Pebble: various other improvemnts to music playback * Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble) * Mi Band: support for shifting the device time by N hours (for people who sleep at daytime) * Mi Band: initial and untested support for Mi Band 2 diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index e8e55dea3..30d19573d 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -2,6 +2,7 @@ Pebble: set extended music info by dissecting notifications on Android 5.0+ + Pebble: various other improvemnts to music playback Pebble: allow ignoring activity trackers indiviually (to keep the data on the pebble) Mi Band: support for shifting the device time by N hours (for people who sleep at daytime) Mi Band: initial and untested support for Mi Band 2 From 98999993e58260afb770f5373f74649c673a44c2 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 12 Jun 2016 01:20:12 +0200 Subject: [PATCH 22/22] Pebble: In AppManager allow moving apps on the device to the top (context menu) --- .../gadgetbridge/activities/AppManagerActivity.java | 6 ++++++ .../gadgetbridge/devices/EventHandler.java | 2 ++ .../gadgetbridge/impl/GBDeviceService.java | 9 +++++++++ .../freeyourgadget/gadgetbridge/model/DeviceService.java | 1 + .../gadgetbridge/service/DeviceCommunicationService.java | 7 +++++++ .../gadgetbridge/service/ServiceDeviceSupport.java | 8 ++++++++ .../service/devices/miband/MiBandSupport.java | 5 +++++ .../service/devices/pebble/PebbleProtocol.java | 3 ++- .../service/serial/AbstractSerialDeviceSupport.java | 6 ++++++ .../gadgetbridge/service/serial/GBDeviceProtocol.java | 4 ++++ app/src/main/res/menu/appmanager_context.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 +++- .../gadgetbridge/service/TestDeviceSupport.java | 5 +++++ 13 files changed, 62 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java index f66bd7310..db758d3b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java @@ -193,6 +193,9 @@ public class AppManagerActivity extends GBActivity { if (!selectedApp.isConfigurable()) { menu.removeItem(R.id.appmanager_app_configure); } + if (mGBDevice != null && !mGBDevice.getFirmwareVersion().startsWith("v3")) { + menu.removeItem(R.id.appmanager_app_move_to_top); + } menu.setHeaderTitle(selectedApp.getName()); } @@ -256,6 +259,9 @@ public class AppManagerActivity extends GBActivity { startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice); startActivity(startIntent); return true; + case R.id.appmanager_app_move_to_top: + GBApplication.deviceService().onAppReorder(new UUID[]{selectedApp.getUUID()}); + return true; default: return super.onContextItemSelected(item); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index a275ace51..97e0f8f6a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -42,6 +42,8 @@ public interface EventHandler { void onAppConfiguration(UUID appUuid, String config); + void onAppReorder(UUID uuids[]); + void onFetchActivityData(); void onReboot(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 60ff7b526..1aa83c9c3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -18,6 +18,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; +//import java.util.UUID; + public class GBDeviceService implements DeviceService { protected final Context mContext; protected final Class mServiceClass; @@ -185,6 +187,13 @@ public class GBDeviceService implements DeviceService { invokeService(intent); } + @Override + public void onAppReorder(UUID[] uuids) { + Intent intent = createIntent().setAction(ACTION_APP_REORDER) + .putExtra(EXTRA_APP_UUID, uuids); + invokeService(intent); + } + @Override public void onFetchActivityData() { Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index b3921b1b8..59518f7ab 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -25,6 +25,7 @@ public interface DeviceService extends EventHandler { String ACTION_STARTAPP = PREFIX + ".action.startapp"; String ACTION_DELETEAPP = PREFIX + ".action.deleteapp"; String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure"; + String ACTION_APP_REORDER = PREFIX + ".action.app_reorder"; String ACTION_INSTALL = PREFIX + ".action.install"; String ACTION_REBOOT = PREFIX + ".action.reboot"; String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 7903ff22a..f66ed5041 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -48,6 +48,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ADD_CALENDAREVENT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_REORDER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP; @@ -388,6 +389,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); String config = intent.getStringExtra(EXTRA_APP_CONFIG); mDeviceSupport.onAppConfiguration(uuid, config); + break; + } + case ACTION_APP_REORDER: { + UUID[] uuids = (UUID[]) intent.getSerializableExtra(EXTRA_APP_UUID); + mDeviceSupport.onAppReorder(uuids); + break; } case ACTION_INSTALL: Uri uri = intent.getParcelableExtra(EXTRA_URI); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index e284f83d8..bf43077be 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -207,6 +207,14 @@ public class ServiceDeviceSupport implements DeviceSupport { delegate.onAppConfiguration(uuid, config); } + @Override + public void onAppReorder(UUID[] uuids) { + if (checkBusy("app reorder")) { + return; + } + delegate.onAppReorder(uuids); + } + @Override public void onFetchActivityData() { if (checkBusy("fetch activity data")) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 165e72e50..fbb73cc5d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -767,6 +767,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { // not supported } + @Override + public void onAppReorder(UUID[] uuids) { + // not supported + } + @Override public void onScreenshotReq() { // not supported 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 1862f3c1f..c350bd762 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 @@ -1293,7 +1293,8 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeSimpleMessage(ENDPOINT_SCREENSHOT, SCREENSHOT_TAKE); } - public byte[] encodeAppReoder(UUID[] uuids) { + @Override + public byte[] encodeAppReorder(UUID[] uuids) { int length = 2 + uuids.length * LENGTH_UUID; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length); buf.order(ByteOrder.BIG_ENDIAN); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java index e963155eb..0e4f5bbff 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java @@ -154,6 +154,12 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport sendToDevice(bytes); } + @Override + public void onAppReorder(UUID[] uuids) { + byte[] bytes = gbDeviceProtocol.encodeAppReorder(uuids); + sendToDevice(bytes); + } + @Override public void onFetchActivityData() { byte[] bytes = gbDeviceProtocol.encodeSynchronizeActivityData(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java index 2492f2773..0154bbf84 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java @@ -48,6 +48,10 @@ public abstract class GBDeviceProtocol { return null; } + public byte[] encodeAppReorder(UUID[] uuids) { + return null; + } + public byte[] encodeSynchronizeActivityData() { return null; } diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml index 873e0c246..72cb29300 100644 --- a/app/src/main/res/menu/appmanager_context.xml +++ b/app/src/main/res/menu/appmanager_context.xml @@ -18,4 +18,8 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 35c8c70fe..3bc390d94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -257,9 +257,11 @@ Weight in kg Activate Deactivate + Configure + Move to top + authenticating authentication required - Configure Zzz Add widget diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java index 42b4dd5ce..241193f7a 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java @@ -102,6 +102,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport { } + @Override + public void onAppReorder(UUID[] uuids) { + + } + @Override public void onFetchActivityData() {