From 53fb63781e791a32afc4ee9c3acb51d5c230ece0 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 13 Dec 2015 12:03:57 +0100 Subject: [PATCH] WIP: Work towards SMS replies / canned replies - Implement the PebbleProtocol side (2.x and 3.x) - Add Preferences for canned replies This can be tested by enabling untested features in Pebble Settings It lets you see and select the replies set up in "Canned Repies" on the Pebble You will get a "NOT IMPLENTED" message on your Pebble. THIS DOES NOT ACTUALLY DO ANYTHING USEFUL YET. --- .../activities/DebugActivity.java | 2 +- .../activities/SettingsActivity.java | 16 +++- .../externalevents/SMSReceiver.java | 2 + .../gadgetbridge/model/NotificationSpec.java | 1 + .../service/DeviceCommunicationService.java | 15 ++++ .../devices/pebble/PebbleProtocol.java | 79 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 29 +++++++ 8 files changed, 134 insertions(+), 12 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 18d3b526..c260735a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java @@ -76,7 +76,7 @@ public class DebugActivity extends Activity { @Override public void onClick(View v) { NotificationSpec notificationSpec = new NotificationSpec(); - notificationSpec.sender = getResources().getText(R.string.app_name).toString(); + notificationSpec.phoneNumber = getResources().getText(R.string.app_name).toString(); notificationSpec.body = editContent.getText().toString(); notificationSpec.type = NotificationType.SMS; notificationSpec.id = -1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java index a3dd4c67..7744da5a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java @@ -50,8 +50,8 @@ public class SettingsActivity extends AbstractSettingsActivity { } }); - final Preference pebbleEmuAddr = findPreference("pebble_emu_addr"); - pebbleEmuAddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + pref = findPreference("pebble_emu_addr"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newVal) { Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST); @@ -62,8 +62,8 @@ public class SettingsActivity extends AbstractSettingsActivity { }); - final Preference pebbleEmuPort = findPreference("pebble_emu_port"); - pebbleEmuPort.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + pref = findPreference("pebble_emu_port"); + pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newVal) { Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST); @@ -110,6 +110,14 @@ public class SettingsActivity extends AbstractSettingsActivity { "pebble_emu_addr", "pebble_emu_port", "pebble_reconnect_attempts", + "canned_reply_1", + "canned_reply_2", + "canned_reply_3", + "canned_reply_4", + "canned_reply_5", + "canned_reply_6", + "canned_reply_7", + "canned_reply_8", }; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java index a2dd09a3..664e44e9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/SMSReceiver.java @@ -9,6 +9,8 @@ import android.os.PowerManager; import android.preference.PreferenceManager; import android.telephony.SmsMessage; +import java.util.ArrayList; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java index e29a30d8..fde544d5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationSpec.java @@ -9,4 +9,5 @@ public class NotificationSpec { public String body; public NotificationType type; public String sourceName; + public String[] cannedReplies; } 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 41f7df33..847a035d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -217,6 +217,21 @@ public class DeviceCommunicationService extends Service { notificationSpec.sourceName = intent.getStringExtra(EXTRA_NOTIFICATION_SOURCENAME); if (notificationSpec.type == NotificationType.SMS && notificationSpec.phoneNumber != null) { notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber); + + // NOTE: maybe not where it belongs + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + if (sharedPrefs.getBoolean("pebble_force_untested", false)) { + // I would rather like to save that as an array in ShadredPreferences + // this would work but I dont know how to do the same in the Settings Activity's xml + ArrayList replies = new ArrayList<>(); + for (int i = 1; i <= 8; i++) { + String reply = sharedPrefs.getString("canned_reply_" + i, null); + if (reply != null && !reply.equals("")) { + replies.add(reply); + } + } + notificationSpec.cannedReplies = replies.toArray(new String[replies.size()]); + } } mDeviceSupport.onNotification(notificationSpec); break; 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 7b52a372..f938b85c 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 @@ -421,10 +421,10 @@ public class PebbleProtocol extends GBDeviceProtocol { if (isFw3x) { // 3.x notification //return encodeTimelinePin(id, (int) ((ts + 600) & 0xffffffffL), (short) 90, PebbleIconID.TIMELINE_CALENDAR, title); // really, this is just for testing - return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type); + return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type, notificationSpec.cannedReplies); } else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) { // 2.x notification - return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle); + return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.cannedReplies); } else { // 1.x notification on FW 2.X String[] parts = {title, notificationSpec.body, ts.toString(), subtitle}; @@ -467,7 +467,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeSetCallState("Where are you?", "Gadgetbridge", start ? ServiceCommand.CALL_INCOMING : ServiceCommand.CALL_END); } - private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle) { + private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) { final short ACTION_LENGTH_MIN = 10; String[] parts = {title, subtitle, body}; @@ -478,6 +478,7 @@ public class PebbleProtocol extends GBDeviceProtocol { String dismiss_string; String open_string = "Open on phone"; String mute_string = "Mute"; + String reply_string = "Reply"; if (sourceName != null) { mute_string += " " + sourceName; } @@ -496,6 +497,15 @@ public class PebbleProtocol extends GBDeviceProtocol { actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length); } + int replies_length = -1; + if (cannedReplies != null) { + actions_count++; + for (String reply : cannedReplies) { + replies_length += reply.getBytes().length + 1; + } + actions_length += ACTION_LENGTH_MIN + reply_string.getBytes().length + replies_length + 3; // 3 = attribute id (byte) + length(short) + } + byte attributes_count = 0; int length = 21 + 10 + actions_length; @@ -554,7 +564,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.putShort((short) dismiss_string.getBytes().length); buf.put(dismiss_string.getBytes()); - // open action + // open and mute actions if (hasHandle) { buf.put((byte) 0x01); buf.put((byte) 0x02); // generic @@ -572,6 +582,23 @@ public class PebbleProtocol extends GBDeviceProtocol { } + if (cannedReplies != null) { + buf.put((byte) 0x05); + buf.put((byte) 0x03); // reply action + buf.put((byte) 0x02); // number attributes + buf.put((byte) 0x01); // title + buf.putShort((short) reply_string.getBytes().length); + buf.put(reply_string.getBytes()); + buf.put((byte) 0x08); // canned replies + buf.putShort((short) replies_length); + for (int i = 0; i < cannedReplies.length - 1; i++) { + buf.put(cannedReplies[i].getBytes()); + buf.put((byte) 0x00); + } + // last one must not be zero terminated, else we get an additional emply reply + buf.put(cannedReplies[cannedReplies.length - 1].getBytes()); + } + return buf.array(); } @@ -645,7 +672,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array()); } - private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType) { + private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType, String[] cannedReplies) { final short NOTIFICATION_PIN_LENGTH = 46; final short ACTION_LENGTH_MIN = 10; @@ -697,6 +724,7 @@ public class PebbleProtocol extends GBDeviceProtocol { String dismiss_string; String open_string = "Open on phone"; String mute_string = "Mute"; + String reply_string = "Reply"; if (sourceName != null) { mute_string += " " + sourceName; } @@ -714,6 +742,15 @@ public class PebbleProtocol extends GBDeviceProtocol { actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length); } + int replies_length = -1; + if (cannedReplies != null) { + actions_count++; + for (String reply : cannedReplies) { + replies_length += reply.getBytes().length + 1; + } + actions_length += ACTION_LENGTH_MIN + reply_string.getBytes().length + replies_length + 3; // 3 = attribute id (byte) + length(short) + } + byte attributes_count = 2; // icon short attributes_length = (short) (11 + actions_length); if (parts != null) { @@ -798,6 +835,24 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.putShort((short) mute_string.getBytes().length); buf.put(mute_string.getBytes()); } + + if (cannedReplies != null) { + buf.put((byte) 0x05); + buf.put((byte) 0x03); // reply action + buf.put((byte) 0x02); // number attributes + buf.put((byte) 0x01); // title + buf.putShort((short) reply_string.getBytes().length); + buf.put(reply_string.getBytes()); + buf.put((byte) 0x08); // canned replies + buf.putShort((short) replies_length); + for (int i = 0; i < cannedReplies.length - 1; i++) { + buf.put(cannedReplies[i].getBytes()); + buf.put((byte) 0x00); + } + // last one must not be zero terminated, else we get an additional emply reply + buf.put(cannedReplies[cannedReplies.length - 1].getBytes()); + } + return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array()); } @@ -1436,7 +1491,7 @@ public class PebbleProtocol extends GBDeviceProtocol { if (command == 0x02) { int id = buf.getInt(); byte action = buf.get(); - if (action >= 0x01 && action <= 0x04) { + if (action >= 0x01 && action <= 0x05) { GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl(); devEvtNotificationControl.handle = id; GBDeviceEventSendBytes sendBytesAck = null; @@ -1458,6 +1513,11 @@ public class PebbleProtocol extends GBDeviceProtocol { sendBytesAck = new GBDeviceEventSendBytes(); sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Muted"); break; + case 0x05: + devEvtNotificationControl = null; // not implemented + sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "NOT IMPLEMENTED"); + break; default: return null; } @@ -1479,7 +1539,7 @@ public class PebbleProtocol extends GBDeviceProtocol { long uuid_low = buf.getLong(); int id = (int) (uuid_low & 0xffffffffL); byte action = buf.get(); - if (action >= 0x01 && action <= 0x04) { + if (action >= 0x01 && action <= 0x05) { GBDeviceEventNotificationControl dismissNotification = new GBDeviceEventNotificationControl(); dismissNotification.handle = id; String caption = "undefined"; @@ -1505,6 +1565,11 @@ public class PebbleProtocol extends GBDeviceProtocol { caption = "Muted"; icon_id = PebbleIconID.RESULT_MUTE; break; + case 0x05: + dismissNotification = null; // not implemented + caption = "NOT IMPLEMENTED"; + icon_id = PebbleIconID.GENERIC_WARNING; + break; } GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); sendBytesAck.encodedBytes = encodeActionResponse(new UUID(uuid_high, uuid_low), icon_id, caption); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index edf7823e..e7e267a9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -55,6 +55,8 @@ Blacklist Apps + Canned Replies + Developer Options Mi Band address diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 599e88b4..cb0f7082 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -65,6 +65,35 @@ + + + + + + + + + +