diff --git a/CHANGELOG.md b/CHANGELOG.md index 900f61ad9..91237f346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ####Version next * Better integration with android music players +* Pebble: Implement notification and incoming call privacy modes +* Pebble: Support weather for Obisdian watchface ####Version 0.17.3 * HPlus: Improve display of new messages and phone calls diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index a045079ed..6fbcd4803 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -204,6 +204,9 @@ public class PBWReader { } app = new GBDeviceApp(appUUID, appName, appCreator, appVersion, appType); } + else if (!isFirmware) { + isValid = false; + } } } 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 745b0b8f7..e602dfc00 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -355,51 +355,51 @@ public class NotificationListener extends NotificationListenerService { MediaController c; try { c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION)); + + PlaybackState s = c.getPlaybackState(); + stateSpec.position = (int) (s.getPosition() / 1000); + stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); + stateSpec.repeat = 1; + stateSpec.shuffle = 1; + switch (s.getState()) { + case PlaybackState.STATE_PLAYING: + stateSpec.state = MusicStateSpec.STATE_PLAYING; + break; + case PlaybackState.STATE_STOPPED: + stateSpec.state = MusicStateSpec.STATE_STOPPED; + break; + case PlaybackState.STATE_PAUSED: + stateSpec.state = MusicStateSpec.STATE_PAUSED; + break; + default: + stateSpec.state = MusicStateSpec.STATE_UNKNOWN; + break; + } + + 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; + 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); + GBApplication.deviceService().onSetMusicState(stateSpec); + + return true; } catch (NullPointerException e) { return false; } - - PlaybackState s = c.getPlaybackState(); - stateSpec.position = (int) (s.getPosition() / 1000); - stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); - stateSpec.repeat = 1; - stateSpec.shuffle = 1; - switch (s.getState()) { - case PlaybackState.STATE_PLAYING: - stateSpec.state = MusicStateSpec.STATE_PLAYING; - break; - case PlaybackState.STATE_STOPPED: - stateSpec.state = MusicStateSpec.STATE_STOPPED; - break; - case PlaybackState.STATE_PAUSED: - stateSpec.state = MusicStateSpec.STATE_PAUSED; - break; - default: - stateSpec.state = MusicStateSpec.STATE_UNKNOWN; - break; - } - - 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; - 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); - GBApplication.deviceService().onSetMusicState(stateSpec); - - return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java new file mode 100644 index 000000000..0f5f783b0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java @@ -0,0 +1,160 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import android.util.Pair; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +class AppMessageHandlerObsidian extends AppMessageHandler { + + /* + "appKeys": { + "CONFIG_WEATHER_REFRESH": 35, + "CONFIG_WEATHER_UNIT_LOCAL": 31, + "MSG_KEY_WEATHER_TEMP": 100, + + "CONFIG_WEATHER_EXPIRATION": 36, + "MSG_KEY_FETCH_WEATHER": 102, + "MSG_KEY_WEATHER_ICON": 101, + "MSG_KEY_WEATHER_FAILED": 104, + "CONFIG_WEATHER_MODE_LOCAL": 30, + "CONFIG_WEATHER_APIKEY_LOCAL": 33, + "CONFIG_WEATHER_LOCAL": 28, + "CONFIG_COLOR_WEATHER": 29, + "CONFIG_WEATHER_LOCATION_LOCAL": 34, + "CONFIG_WEATHER_SOURCE_LOCAL": 32 + } + */ + + + private static final String ICON_01d = "a"; //night icons are just uppercase + private static final String ICON_02d = "b"; + private static final String ICON_03d = "c"; + private static final String ICON_04d = "d"; + private static final String ICON_09d = "e"; + private static final String ICON_10d = "f"; + private static final String ICON_11d = "g"; + private static final String ICON_13d = "h"; + private static final String ICON_50d = "i"; + + + AppMessageHandlerObsidian(UUID uuid, PebbleProtocol pebbleProtocol) { + super(uuid, pebbleProtocol); + messageKeys = new HashMap<>(); + try { + JSONObject appKeys = getAppKeys(); + Iterator appKeysIterator = appKeys.keys(); + while (appKeysIterator.hasNext()) { + String current = appKeysIterator.next(); + switch (current) { + case "CONFIG_WEATHER_REFRESH": + case "CONFIG_WEATHER_UNIT_LOCAL": + case "MSG_KEY_WEATHER_TEMP": + case "MSG_KEY_WEATHER_ICON": + messageKeys.put(current, appKeys.getInt(current)); + break; + } + } + } catch (JSONException e) { + GB.toast("There was an error accessing the timestyle watchface configuration.", Toast.LENGTH_LONG, GB.ERROR); + } catch (IOException ignore) { + } + } + + private String getIconForConditionCode(int conditionCode, boolean isNight) { + + int generalCondition = conditionCode / 100; + String iconToLoad; + // determine the correct icon + switch (generalCondition) { + case 2: //thunderstorm + iconToLoad = ICON_11d; + break; + case 3: //drizzle + iconToLoad = ICON_09d; + break; + case 5: //rain + if (conditionCode == 500) { + iconToLoad = ICON_09d; + } else if (conditionCode < 505) { + iconToLoad = ICON_10d; + } else if (conditionCode == 511) { + iconToLoad = ICON_10d; + } else { + iconToLoad = ICON_09d; + } + break; + case 6: //snow + if (conditionCode == 600 || conditionCode == 620) { + iconToLoad = ICON_13d; + } else if (conditionCode > 610 && conditionCode < 620) { + iconToLoad = ICON_13d; + } else { + iconToLoad = ICON_13d; + } + break; + case 7: // fog, dust, etc + iconToLoad = ICON_03d; + break; + case 8: // clouds + if (conditionCode == 800) { + iconToLoad = ICON_01d; + } else if (conditionCode < 803) { + iconToLoad = ICON_02d; + } else { + iconToLoad = ICON_04d; + } + break; + default: + iconToLoad = ICON_02d; + break; + } + + return (!isNight) ? iconToLoad : iconToLoad.toUpperCase(); + } + + private byte[] encodeObisdianWeather(WeatherSpec weatherSpec) { + + if (weatherSpec == null) { + return null; + } + + ArrayList> pairs = new ArrayList<>(); + boolean isNight = false; //TODO: use the night icons when night + pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_REFRESH"), (Object) 60)); + pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_UNIT_LOCAL"), (Object) 1)); //celsius + pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_ICON"), (Object) getIconForConditionCode(weatherSpec.currentConditionCode, isNight))); //celsius + pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_TEMP"), (Object) (weatherSpec.currentTemp - 273))); + + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + } + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeObisdianWeather(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeObisdianWeather(weatherSpec); + } +} \ No newline at end of file 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 14ae5f9e0..4527299a2 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 @@ -368,6 +368,7 @@ public class PebbleProtocol extends GBDeviceProtocol { private static final UUID UUID_ZALEWSZCZAK_CROWEX = UUID.fromString("a88b3151-2426-43c6-b1d0-9b288b3ec47e"); private static final UUID UUID_ZALEWSZCZAK_FANCY = UUID.fromString("014e17bf-5878-4781-8be1-8ef998cee1ba"); private static final UUID UUID_ZALEWSZCZAK_TALLY = UUID.fromString("abb51965-52e2-440a-b93c-843eeacb697d"); + private static final UUID UUID_OBSIDIAN = UUID.fromString("ef42caba-0c65-4879-ab23-edd2bde68824"); private static final UUID UUID_ZERO = new UUID(0, 0); @@ -390,6 +391,7 @@ public class PebbleProtocol extends GBDeviceProtocol { mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this)); + mAppMessageHandlers.put(UUID_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this)); } private final HashMap mDatalogSessions = new HashMap<>(); 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 122f6da59..e4f59e852 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 @@ -12,6 +12,7 @@ import java.util.Iterator; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; @@ -113,6 +114,16 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { + String currentPrivacyMode = GBApplication.getPrefs().getString("pebble_pref_privacy_mode", getContext().getString(R.string.p_pebble_privacy_mode_off)); + if (getContext().getString(R.string.p_pebble_privacy_mode_complete).equals(currentPrivacyMode)) { + notificationSpec.body = null; + notificationSpec.sender = null; + notificationSpec.subject = null; + notificationSpec.title = null; + notificationSpec.phoneNumber = null; + } else if (getContext().getString(R.string.p_pebble_privacy_mode_content).equals(currentPrivacyMode)) { + notificationSpec.sender = "\n\n\n\n\n" + notificationSpec.sender; + } if (reconnect()) { super.onNotification(notificationSpec); } @@ -120,6 +131,14 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { @Override public void onSetCallState(CallSpec callSpec) { + String currentPrivacyMode = GBApplication.getPrefs().getString("pebble_pref_privacy_mode", getContext().getString(R.string.p_pebble_privacy_mode_off)); + if (getContext().getString(R.string.p_pebble_privacy_mode_complete).equals(currentPrivacyMode)) { + callSpec.name = null; + callSpec.number = null; + } else if (getContext().getString(R.string.p_pebble_privacy_mode_content).equals(currentPrivacyMode)) { + callSpec.name = null; + } + if (reconnect()) { if ((callSpec.command != CallSpec.CALL_OUTGOING) || GBApplication.getPrefs().getBoolean("pebble_enable_outgoing_call", true)) { super.onSetCallState(callSpec); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java index a19d79d98..78ae076c3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.util; +import org.apache.commons.lang3.text.WordUtils; + import java.util.HashMap; import java.util.Map; import java.text.Normalizer; @@ -67,7 +69,7 @@ public class LanguageUtils { if (lowerChar != c) { - return replace.toUpperCase(); + return WordUtils.capitalize(replace); } return replace; diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 63bbb3100..da77e0018 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -113,6 +113,19 @@ 3 1 + + + @string/pref_pebble_privacy_mode_off + @string/pref_pebble_privacy_mode_content + @string/pref_pebble_privacy_mode_complete + + + + @string/p_pebble_privacy_mode_off + @string/p_pebble_privacy_mode_content + @string/p_pebble_privacy_mode_complete + + @string/dateformat_time @string/dateformat_date_time diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d72def7f6..fbfd13a96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,11 @@ Autoremove dismissed Notifications Notifications are automatically removed from the Pebble when dismissed from the Android device + Privacy mode + Normal notifications and incoming calls display. + Shift the notification text off-screen. Hide the caller\'s name on incoming calls. + Show only the notification icon. Hide the caller\'s name and number on incoming calls. + Location Acquire Location Latitude diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index d8a8eff79..ef1cd4a3b 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -18,4 +18,8 @@ 24h am/pm + off + content + complete + diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 5187fa13e..abf9e99fc 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -2,8 +2,9 @@ Better integration with android music players + Pebble: Implement notification and incoming call privacy modes + Pebble: Support weather for Obisdian watchface - HPlus: Improve display of new messages and phone calls HPlus: Fix bug related to steps and heart rate diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e9df02ac7..044d5faa9 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -184,6 +184,13 @@ android:key="autoremove_notifications" android:summary="@string/pref_summary_autoremove_notifications" android:title="@string/pref_title_autoremove_notifications" /> +