Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch

Closes #113
master
Andreas Shimokawa 2015-09-25 00:53:40 +02:00
parent 94ad7f2eb9
commit e3533a2b18
8 changed files with 158 additions and 87 deletions

View File

@ -1,6 +1,7 @@
###Changelog
####Next Version
* Pebble: Allow muting (blacklisting) Apps from within generic notifications on the watch
* Pebble: Detect all known Pebble Versions including new "chalk" platform (Pebble Time Round)
* Option to ignore phone calls (useful for Pebble Dialer)

View File

@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -31,6 +32,7 @@ public class GBApplication extends Application {
private static ActivityDatabaseHandler mActivityDatabaseHandler;
private static final Lock dbLock = new ReentrantLock();
private static DeviceService deviceService;
private static SharedPreferences sharedPrefs;
public GBApplication() {
context = this;
@ -46,6 +48,8 @@ public class GBApplication extends Application {
public void onCreate() {
super.onCreate();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
// don't do anything here before we set up logging, otherwise
// slf4j may be implicitly initialized before we properly configured it.
setupLogging();
@ -57,14 +61,14 @@ public class GBApplication extends Application {
GB.environment = GBEnvironment.createDeviceEnvironment();
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
loadBlackList();
// for testing DB stuff
// SQLiteDatabase db = mActivityDatabaseHandler.getWritableDatabase();
// db.close();
}
public static boolean isFileLoggingEnabled() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
return prefs.getBoolean("log_to_file", false);
return sharedPrefs.getBoolean("log_to_file", false);
}
private void setupLogging() {
@ -130,4 +134,36 @@ public class GBApplication extends Application {
public static boolean isRunningLollipopOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static HashSet<String> blacklist = null;
public static void loadBlackList() {
blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
if (blacklist == null) {
blacklist = new HashSet<>();
}
}
public static void saveBlackList() {
SharedPreferences.Editor editor = sharedPrefs.edit();
if (blacklist.isEmpty()) {
editor.putStringSet("package_blacklist", null);
} else {
editor.putStringSet("package_blacklist", blacklist);
}
editor.apply();
}
public static void addToBlacklist(String packageName) {
if (!blacklist.contains(packageName)) {
blacklist.add(packageName);
saveBlackList();
}
}
public static synchronized void removeFromBlacklist(String packageName) {
blacklist.remove(packageName);
saveBlackList();
}
}

View File

@ -26,9 +26,9 @@ import android.widget.TextView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
@ -47,37 +47,6 @@ public class AppBlacklistActivity extends Activity {
private SharedPreferences sharedPrefs;
private HashSet<String> blacklist = null;
private void loadBlackList() {
blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
if (blacklist == null) {
blacklist = new HashSet<>();
}
}
private void saveBlackList() {
SharedPreferences.Editor editor = sharedPrefs.edit();
if (blacklist.isEmpty()) {
editor.putStringSet("package_blacklist", null);
} else {
editor.putStringSet("package_blacklist", blacklist);
}
editor.apply();
}
private synchronized void addToBlacklist(String packageName) {
if (!blacklist.contains(packageName)) {
blacklist.add(packageName);
saveBlackList();
}
}
private synchronized void removeFromBlacklist(String packageName) {
blacklist.remove(packageName);
saveBlackList();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -87,8 +56,6 @@ public class AppBlacklistActivity extends Activity {
final PackageManager pm = getPackageManager();
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
loadBlackList();
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
ListView appListView = (ListView) findViewById(R.id.appListView);
@ -110,7 +77,7 @@ public class AppBlacklistActivity extends Activity {
deviceAppNameLabel.setText(appInfo.loadLabel(pm));
deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
if (blacklist.contains(appInfo.packageName)) {
if (GBApplication.blacklist.contains(appInfo.packageName)) {
checkbox.setChecked(true);
}
@ -126,9 +93,9 @@ public class AppBlacklistActivity extends Activity {
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
checkBox.toggle();
if (checkBox.isChecked()) {
addToBlacklist(packageName);
GBApplication.addToBlacklist(packageName);
} else {
removeFromBlacklist(packageName);
GBApplication.removeFromBlacklist(packageName);
}
}
});

View File

@ -9,6 +9,7 @@ public class GBDeviceEventNotificationControl extends GBDeviceEvent {
UNKNOWN,
DISMISS,
DISMISS_ALL,
OPEN
OPEN,
MUTE
}
}

View File

@ -9,6 +9,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
@ -19,8 +21,6 @@ import android.support.v4.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
@ -36,45 +36,60 @@ public class NotificationListener extends NotificationListenerService {
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.dismiss_all";
public static final String ACTION_OPEN
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.open";
public static final String ACTION_MUTE
= "nodomain.freeyourgadget.gadgetbridge.notificationlistener.action.mute";
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@SuppressLint("NewApi")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_OPEN)) {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
try {
PendingIntent pi = sbn.getNotification().contentIntent;
if (pi != null) {
pi.send();
switch (action) {
case ACTION_MUTE:
case ACTION_OPEN: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (action.equals(ACTION_OPEN)) {
try {
PendingIntent pi = sbn.getNotification().contentIntent;
if (pi != null) {
pi.send();
}
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
} else {
// ACTION_MUTE
LOG.info("going to mute " + sbn.getPackageName());
GBApplication.addToBlacklist(sbn.getPackageName());
}
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
break;
}
} else if (action.equals(ACTION_DISMISS)) {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (GBApplication.isRunningLollipopOrLater()) {
String key = sbn.getKey();
NotificationListener.this.cancelNotification(key);
} else {
int id = sbn.getId();
String pkg = sbn.getPackageName();
String tag = sbn.getTag();
NotificationListener.this.cancelNotification(pkg, tag, id);
case ACTION_DISMISS: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
int handle = intent.getIntExtra("handle", -1);
for (StatusBarNotification sbn : sbns) {
if ((int) sbn.getPostTime() == handle) {
if (GBApplication.isRunningLollipopOrLater()) {
String key = sbn.getKey();
NotificationListener.this.cancelNotification(key);
} else {
int id = sbn.getId();
String pkg = sbn.getPackageName();
String tag = sbn.getTag();
NotificationListener.this.cancelNotification(pkg, tag, id);
}
}
}
break;
}
} else if (action.equals(ACTION_DISMISS_ALL)) {
NotificationListener.this.cancelAllNotifications();
case ACTION_DISMISS_ALL:
NotificationListener.this.cancelAllNotifications();
break;
}
}
@ -87,6 +102,7 @@ public class NotificationListener extends NotificationListenerService {
filterLocal.addAction(ACTION_OPEN);
filterLocal.addAction(ACTION_DISMISS);
filterLocal.addAction(ACTION_DISMISS_ALL);
filterLocal.addAction(ACTION_MUTE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
}
@ -154,13 +170,24 @@ public class NotificationListener extends NotificationListenerService {
}
}
HashSet<String> blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
if (blacklist != null && blacklist.contains(source)) {
if (GBApplication.blacklist != null && GBApplication.blacklist.contains(source)) {
return;
}
// Set application icons for generic notifications
NotificationSpec notificationSpec = new NotificationSpec();
// determinate Source App Name ("Label")
PackageManager pm = getPackageManager();
ApplicationInfo ai = null;
try {
ai = pm.getApplicationInfo(source, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (ai != null) {
notificationSpec.sourceName = (String) pm.getApplicationLabel(ai);
}
switch (source) {
case "org.mariotaku.twidere":
case "com.twitter.android":

View File

@ -220,6 +220,9 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
case OPEN:
action = NotificationListener.ACTION_OPEN;
break;
case MUTE:
action = NotificationListener.ACTION_MUTE;
break;
}
if (action != null) {
Intent notificationListenerIntent = new Intent(action);

View File

@ -409,10 +409,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, hasHandle, notificationSpec.type);
return encodeBlobdbNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle, notificationSpec.type);
} else if (mForceProtocol || notificationSpec.type != NotificationType.EMAIL) {
// 2.x notification
return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, hasHandle);
return encodeExtensibleNotification(id, (int) (ts & 0xffffffffL), title, subtitle, notificationSpec.body, notificationSpec.sourceName, hasHandle);
} else {
// 1.x notification on FW 2.X
String[] parts = {title, notificationSpec.body, ts.toString(), subtitle};
@ -455,7 +455,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, boolean hasHandle) {
private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle) {
final short ACTION_LENGTH_MIN = 10;
String[] parts = {title, subtitle, body};
@ -465,12 +465,18 @@ public class PebbleProtocol extends GBDeviceProtocol {
short actions_length;
String dismiss_string;
String open_string = "Open on phone";
String mute_string = "Mute";
if (sourceName != null) {
mute_string += " " + sourceName;
}
byte dismiss_action_id;
if (hasHandle) {
actions_count = 2;
actions_count = 3;
dismiss_string = "Dismiss";
dismiss_action_id = 0x02;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length);
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length);
} else {
actions_count = 1;
dismiss_string = "Dismiss all";
@ -539,11 +545,19 @@ public class PebbleProtocol extends GBDeviceProtocol {
// open action
if (hasHandle) {
buf.put((byte) 0x01);
buf.put((byte) 0x02); // dissmiss - FIXME: find out how to answer to 2.x generic actions
buf.put((byte) 0x02); // generic
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) open_string.getBytes().length);
buf.put(open_string.getBytes());
buf.put((byte) 0x04);
buf.put((byte) 0x02); // generic
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) mute_string.getBytes().length);
buf.put(mute_string.getBytes());
}
return buf.array();
@ -619,7 +633,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, boolean hasHandle, NotificationType notificationType) {
private byte[] encodeBlobdbNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, NotificationType notificationType) {
final short NOTIFICATION_PIN_LENGTH = 46;
final short ACTION_LENGTH_MIN = 10;
@ -670,12 +684,17 @@ public class PebbleProtocol extends GBDeviceProtocol {
short actions_length;
String dismiss_string;
String open_string = "Open on phone";
String mute_string = "Mute";
if (sourceName != null) {
mute_string += " " + sourceName;
}
byte dismiss_action_id;
if (hasHandle) {
actions_count = 2;
actions_count = 3;
dismiss_string = "Dismiss";
dismiss_action_id = 0x02;
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length);
actions_length = (short) (ACTION_LENGTH_MIN * actions_count + dismiss_string.getBytes().length + open_string.getBytes().length + mute_string.getBytes().length);
} else {
actions_count = 1;
dismiss_string = "Dismiss all";
@ -751,7 +770,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 action
@ -759,11 +778,18 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) open_string.getBytes().length);
buf.put(open_string.getBytes());
buf.put((byte) 0x04);
buf.put((byte) 0x02); // generic action
buf.put((byte) 0x01); // number attributes
buf.put((byte) 0x01); // attribute id (title)
buf.putShort((short) mute_string.getBytes().length);
buf.put(mute_string.getBytes());
}
return encodeBlobdb(UUID.randomUUID(), BLOBDB_INSERT, BLOBDB_NOTIFICATION, buf.array());
}
public byte[] encodeActionResponse2x(int id, int iconId, String caption) {
public byte[] encodeActionResponse2x(int id, byte actionId, int iconId, String caption) {
short length = (short) (18 + caption.getBytes().length);
ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + length);
buf.order(ByteOrder.BIG_ENDIAN);
@ -772,7 +798,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(NOTIFICATIONACTION_RESPONSE);
buf.putInt(id);
buf.put((byte) 0x01); // action id?
buf.put(actionId);
buf.put(NOTIFICATIONACTION_ACK);
buf.put((byte) 2); //nr of attributes
buf.put((byte) 6); // icon
@ -1386,7 +1412,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
if (command == 0x02) {
int id = buf.getInt();
byte action = buf.get();
if (action >= 0x01 && action <= 0x03) {
if (action >= 0x01 && action <= 0x04) {
GBDeviceEventNotificationControl devEvtNotificationControl = new GBDeviceEventNotificationControl();
devEvtNotificationControl.handle = id;
GBDeviceEventSendBytes sendBytesAck = null;
@ -1395,7 +1421,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
case 0x01:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.OPEN;
sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeActionResponse2x(id, 6, "Opened");
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Opened");
break;
case 0x02:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS;
@ -1403,6 +1429,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
case 0x03:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.DISMISS_ALL;
break;
case 0x04:
devEvtNotificationControl.event = GBDeviceEventNotificationControl.Event.MUTE;
sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeActionResponse2x(id, action, 6, "Muted");
break;
default:
return null;
}
@ -1424,7 +1455,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
long uuid_low = buf.getLong();
int id = (int) (uuid_low & 0xffffffff);
byte action = buf.get();
if (action >= 0x01 && action <= 0x03) {
if (action >= 0x01 && action <= 0x04) {
GBDeviceEventNotificationControl dismissNotification = new GBDeviceEventNotificationControl();
dismissNotification.handle = id;
String caption = "undefined";
@ -1445,6 +1476,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
caption = "All dismissed";
icon_id = PebbleIconID.RESULT_DISMISSED;
break;
case 0x04:
dismissNotification.event = GBDeviceEventNotificationControl.Event.MUTE;
caption = "Muted";
icon_id = PebbleIconID.RESULT_MUTE;
break;
}
GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
sendBytesAck.encodedBytes = encodeActionResponse(new UUID(uuid_high, uuid_low), icon_id, caption);

View File

@ -308,7 +308,7 @@ public class GB {
}
public static void updateTransferNotification(String text, boolean ongoing, int percentage, Context context) {
if(percentage == 100) {
if (percentage == 100) {
removeNotification(NOTIFICATION_ID_TRANSFER, context);
} else {
Notification notification = createTransferNotification(text, ongoing, percentage, context);