diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java index 354c6e275..7a9907c30 100644 --- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java +++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java @@ -45,7 +45,7 @@ public class GBDaoGenerator { public static void main(String[] args) throws Exception { - Schema schema = new Schema(19, MAIN_PACKAGE + ".entities"); + Schema schema = new Schema(20, MAIN_PACKAGE + ".entities"); Entity userAttributes = addUserAttributes(schema); Entity user = addUserInfo(schema, userAttributes); @@ -75,6 +75,10 @@ public class GBDaoGenerator { addCalendarSyncState(schema, device); addAlarms(schema, user, device); + Entity notificationFilter = addNotificationFilters(schema); + + addNotificationFilterEntry(schema, notificationFilter); + addBipActivitySummary(schema, user, device); new DaoGenerator().generateAll(schema, "app/src/main/java"); @@ -363,6 +367,30 @@ public class GBDaoGenerator { alarm.addToOne(device, deviceId); } + private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) { + Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry"); + notificatonFilterEntry.addIdProperty().autoincrement(); + Property notificationFilterId = notificatonFilterEntry.addLongProperty("notificationFilterId").notNull().getProperty(); + notificatonFilterEntry.addStringProperty("notificationFilterContent").notNull().getProperty(); + notificatonFilterEntry.addToOne(notificationFilterEntity, notificationFilterId); + } + + private static Entity addNotificationFilters(Schema schema) { + Entity notificatonFilter = addEntity(schema, "NotificationFilter"); + Property appIdentifier = notificatonFilter.addStringProperty("appIdentifier").notNull().getProperty(); + + notificatonFilter.addIdProperty().autoincrement(); + + Index indexUnique = new Index(); + indexUnique.addProperty(appIdentifier); + indexUnique.makeUnique(); + notificatonFilter.addIndex(indexUnique); + + Property notificationFilterMode = notificatonFilter.addIntProperty("notificationFilterMode").notNull().getProperty(); + Property notificationFilterSubMode = notificatonFilter.addIntProperty("notificationFilterSubMode").notNull().getProperty(); + return notificatonFilter; + } + private static void addBipActivitySummary(Schema schema, Entity user, Entity device) { Entity summary = addEntity(schema, "BaseActivitySummary"); summary.implementsInterface(ACTIVITY_SUMMARY); diff --git a/app/build.gradle b/app/build.gradle index b637e8ea8..8cf87c4ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,6 +60,7 @@ pmd { dependencies { // testImplementation "ch.qos.logback:logback-classic:1.1.3" // testImplementation "ch.qos.logback:logback-core:1.1.3" + implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation "junit:junit:4.12" testImplementation "org.mockito:mockito-core:1.10.19" testImplementation "org.robolectric:robolectric:3.6.1" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c9ecb863a..79cca0024 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -405,12 +405,17 @@ + android:parentActivityName=".activities.ConfigureAlarms" + android:screenOrientation="portrait" /> + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java new file mode 100644 index 000000000..1e8ff7900 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/NotificationFilterActivity.java @@ -0,0 +1,206 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.os.Bundle; +import android.view.View; +import android.widget.*; +import de.greenrobot.dao.query.Query; +import nodomain.freeyourgadget.gadgetbridge.BuildConfig; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilter; +import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterDao; +import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntry; +import nodomain.freeyourgadget.gadgetbridge.entities.NotificationFilterEntryDao; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationFilterActivity extends AbstractGBActivity { + + private static final String TAG = NotificationFilterActivity.class.getName(); + + public static final int NOTIFICATION_FILTER_MODE_NONE = 0; + public static final int NOTIFICATION_FILTER_MODE_WHITELIST = 1; + public static final int NOTIFICATION_FILTER_MODE_BLACKLIST = 2; + public static final int NOTIFICATION_FILTER_SUBMODE_ANY = 0; + public static final int NOTIFICATION_FILTER_SUBMODE_ALL = 1; + + private Button mButtonSave; + private Spinner mSpinnerFilterMode; + private Spinner mSpinnerFilterSubMode; + private NotificationFilter mNotificationFilter; + private EditText mEditTextWords; + private DBHandler db = null; + private List mWordsList = new ArrayList<>(); + private List mFilterEntryIds = new ArrayList<>(); + + private static final Logger LOG = LoggerFactory.getLogger(NotificationFilterActivity.class); + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_notification_filter); + + String packageName = getIntent().getStringExtra(AppBlacklistAdapter.STRING_EXTRA_PACKAGE_NAME); + + if (StringUtils.isBlank(packageName)) { + this.finish(); + } + + try { + db = GBApplication.acquireDB(); + } catch (GBException e) { + LOG.error("Could not acquire DB.", e); + this.finish(); + } + + NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao(); + NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao(); + + Query query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName)).build(); + mNotificationFilter = query.unique(); + + if (mNotificationFilter == null) { + mNotificationFilter = new NotificationFilter(); + mNotificationFilter.setAppIdentifier(packageName); + LOG.debug("New Notification Filter"); + } else { + LOG.debug("Loaded existing notification filter"); + Query queryEntries = notificationFilterEntryDao.queryBuilder().where(NotificationFilterEntryDao.Properties.NotificationFilterId.eq(mNotificationFilter.getId())).build(); + List filterEntries = queryEntries.list(); + if (!filterEntries.isEmpty()) { + for (NotificationFilterEntry temp : filterEntries) { + mWordsList.add(temp.getNotificationFilterContent()); + mFilterEntryIds.add(temp.getId()); + LOG.debug("Loaded filter word: " + temp.getNotificationFilterContent()); + } + } + } + + setupView(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (db != null) { + GBApplication.releaseDB(); + } + } + + private void setupView() { + + mSpinnerFilterMode = findViewById(R.id.spinnerFilterMode); + mSpinnerFilterMode.setSelection(mNotificationFilter.getNotificationFilterMode()); + + mSpinnerFilterSubMode = findViewById(R.id.spinnerSubMode); + mSpinnerFilterMode.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int pos, long id) { + switch (pos) { + case NOTIFICATION_FILTER_MODE_NONE: + mEditTextWords.setEnabled(false); + mSpinnerFilterSubMode.setEnabled(false); + break; + case NOTIFICATION_FILTER_MODE_BLACKLIST: + case NOTIFICATION_FILTER_MODE_WHITELIST: + mEditTextWords.setEnabled(true); + mSpinnerFilterSubMode.setEnabled(true); + break; + + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + }); + + mSpinnerFilterSubMode.setSelection(mNotificationFilter.getNotificationFilterSubMode()); + + mEditTextWords = findViewById(R.id.editTextWords); + + if (!mWordsList.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (String temp : mWordsList) { + builder.append(temp); + builder.append("\n"); + } + mEditTextWords.setText(builder.toString()); + } + + mEditTextWords.setEnabled(mSpinnerFilterMode.getSelectedItemPosition() == NOTIFICATION_FILTER_MODE_NONE); + + mButtonSave = findViewById(R.id.buttonSaveFilter); + mButtonSave.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + // TODO: check for modifications, only save if something changed + + String words = mEditTextWords.getText().toString(); + + if (StringUtils.isBlank(words) && mSpinnerFilterMode.getSelectedItemPosition() != NOTIFICATION_FILTER_MODE_NONE) { + Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_words_empty_hint, Toast.LENGTH_SHORT).show(); + return; + } + + try { + db = GBApplication.acquireDB(); + NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao(); + NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao(); + + debugOutput(notificationFilterDao); + + mNotificationFilter.setNotificationFilterMode(mSpinnerFilterMode.getSelectedItemPosition()); + mNotificationFilter.setNotificationFilterSubMode(mSpinnerFilterSubMode.getSelectedItemPosition()); + + notificationFilterEntryDao.deleteByKeyInTx(mFilterEntryIds); + + Long filterId = notificationFilterDao.insertOrReplace(mNotificationFilter); + + // only save words if filter mode != none + if (mNotificationFilter.getNotificationFilterMode() != NOTIFICATION_FILTER_MODE_NONE) { + String[] wordsSplitted = words.split("\n"); + for (String temp : wordsSplitted) { + temp = temp.trim(); + NotificationFilterEntry notificationFilterEntry = new NotificationFilterEntry(); + notificationFilterEntry.setNotificationFilterContent(temp); + notificationFilterEntry.setNotificationFilterId(filterId); + notificationFilterEntryDao.insert(notificationFilterEntry); + } + } + + Toast.makeText(NotificationFilterActivity.this, R.string.toast_notification_filter_saved_successfully, Toast.LENGTH_SHORT).show(); + NotificationFilterActivity.this.finish(); + + } catch (GBException e) { + LOG.error("Could not acquire DB.", e); + Toast.makeText(NotificationFilterActivity.this, "Database Error: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + }); + } + + private void debugOutput(NotificationFilterDao notificationFilterDao) { + if (BuildConfig.DEBUG) { + + List filters = notificationFilterDao.loadAll(); + + LOG.info(TAG, "Saved filters"); + + for (NotificationFilter temp : filters) { + LOG.info(TAG, "Filter: " + temp.getId() + " " + temp.getAppIdentifier()); + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java index 7888db2cf..9c14e606d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AppBlacklistAdapter.java @@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.adapter; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.support.v7.widget.RecyclerView; @@ -40,11 +41,14 @@ import java.util.Set; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.NotificationFilterActivity; import static nodomain.freeyourgadget.gadgetbridge.GBApplication.packageNameToPebbleMsgSender; public class AppBlacklistAdapter extends RecyclerView.Adapter implements Filterable { + public static final String STRING_EXTRA_PACKAGE_NAME = "packageName"; + private List applicationInfoList; private final int mLayoutId; private final Context mContext; @@ -92,7 +96,7 @@ public class AppBlacklistAdapter extends RecyclerView.Adapter wordsList = new ArrayList<>(); + + NotificationFilterDao notificationFilterDao = db.getDaoSession().getNotificationFilterDao(); + NotificationFilterEntryDao notificationFilterEntryDao = db.getDaoSession().getNotificationFilterEntryDao(); + + Query query = notificationFilterDao.queryBuilder().where(NotificationFilterDao.Properties.AppIdentifier.eq(packageName)).build(); + NotificationFilter notificationFilter = query.unique(); + + if (notificationFilter == null) { + LOG.debug("No Notification Filter found"); + return true; + } + + LOG.debug("Loaded notification filter for '{}'", packageName); + Query queryEntries = notificationFilterEntryDao.queryBuilder().where(NotificationFilterEntryDao.Properties.NotificationFilterId.eq(notificationFilter.getId())).build(); + + List filterEntries = queryEntries.list(); + + if (BuildConfig.DEBUG) { + LOG.info("Database lookup took '{}' ms", System.currentTimeMillis() - start); + } + + if (!filterEntries.isEmpty()) { + for (NotificationFilterEntry temp : filterEntries) { + wordsList.add(temp.getNotificationFilterContent()); + LOG.debug("Loaded filter word: " + temp.getNotificationFilterContent()); + } + } + + return shouldContinueAfterFilter(body, wordsList, notificationFilter); + } + + boolean shouldContinueAfterFilter(@NonNull String body, @NonNull List wordsList, @NonNull NotificationFilter notificationFilter) { + + LOG.debug("Mode: '{}' Submode: '{}' WordsList: '{}'", notificationFilter.getNotificationFilterMode(), notificationFilter.getNotificationFilterSubMode(), wordsList); + + boolean allMode = notificationFilter.getNotificationFilterSubMode() == NOTIFICATION_FILTER_SUBMODE_ALL; + + switch (notificationFilter.getNotificationFilterMode()) { + case NOTIFICATION_FILTER_MODE_BLACKLIST: + if (allMode) { + for (String word : wordsList) { + if (!body.contains(word)) { + LOG.info("Not every word was found, blacklist has no effect, processing continues."); + return true; + } + } + LOG.info("Every word was found, blacklist has effect, processing stops."); + return false; + } else { + boolean notContainsAny = !StringUtils.containsAny(body, wordsList.toArray(new CharSequence[0])); + if (notContainsAny) { + LOG.info("Not matching word was found, blacklist has no effect, processing continues."); + } else { + LOG.info("At least one matching word was found, blacklist has effect, processing stops."); + } + return notContainsAny; + } + + case NOTIFICATION_FILTER_MODE_WHITELIST: + if (allMode) { + for (String word : wordsList) { + if (!body.contains(word)) { + LOG.info("Not every word was found, whitelist has no effect, processing stops."); + return false; + } + } + LOG.info("Every word was found, whitelist has effect, processing continues."); + return true; + } else { + boolean containsAny = StringUtils.containsAny(body, wordsList.toArray(new CharSequence[0])); + if (containsAny) { + LOG.info("At least one matching word was found, whitelist has effect, processing continues."); + } else { + LOG.info("No matching word was found, whitelist has no effect, processing stops."); + } + return containsAny; + } + + default: + return true; + } + } + // Strip Unicode control sequences: some apps like Telegram add a lot of them for unknown reasons private String sanitizeUnicode(String orig) { return orig.replaceAll("\\p{C}", ""); } - private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, boolean preferBigText) { + private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, + boolean preferBigText) { Bundle extras = NotificationCompat.getExtras(notification); diff --git a/app/src/main/res/layout/activity_notification_filter.xml b/app/src/main/res/layout/activity_notification_filter.xml new file mode 100644 index 000000000..9f0d01513 --- /dev/null +++ b/app/src/main/res/layout/activity_notification_filter.xml @@ -0,0 +1,65 @@ + + + + + + + + +