diff --git a/res/layout/popup_notificationsfilter.xml b/res/layout/popup_notificationsfilter.xml new file mode 100644 --- /dev/null +++ b/res/layout/popup_notificationsfilter.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/res/layout/privacy_options.xml b/res/layout/privacy_options.xml new file mode 100644 --- /dev/null +++ b/res/layout/privacy_options.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -305,4 +305,9 @@ Required by Android since Android 8.0 Since Android 9.0, this notification can only be minimized by long tapping on it + Extra options + Privacy options + Set your privacy options + New notification + diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/AppDatabase.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/AppDatabase.java --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/AppDatabase.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/AppDatabase.java @@ -26,6 +26,7 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; import java.util.HashSet; @@ -41,9 +42,11 @@ private static final int DATABASE_VERSION = 4; private static final String DATABASE_NAME = "Applications"; - private static final String DATABASE_TABLE = "Applications"; + private static final String TABLE_ENABLED = "Applications"; + private static final String TABLE_PRIVACY = "PrivacyOpts"; private static final String KEY_PACKAGE_NAME = "packageName"; private static final String KEY_IS_ENABLED = "isEnabled"; + private static final String KEY_PRIVACY_OPTIONS = "privacyOptions"; private SQLiteDatabase ourDatabase; @@ -74,27 +77,33 @@ @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + DATABASE_TABLE + "(" + KEY_PACKAGE_NAME + " TEXT PRIMARY KEY NOT NULL, " + KEY_IS_ENABLED + " INTEGER NOT NULL); "); + db.execSQL("CREATE TABLE " + TABLE_ENABLED + + "(" + KEY_PACKAGE_NAME + " TEXT PRIMARY KEY NOT NULL, " + + KEY_IS_ENABLED + " INTEGER NOT NULL ); "); + db.execSQL("CREATE TABLE " + TABLE_PRIVACY + + "(" + KEY_PACKAGE_NAME + " TEXT PRIMARY KEY NOT NULL, " + + KEY_PRIVACY_OPTIONS + " INTEGER NOT NULL); "); } @Override public void onUpgrade(SQLiteDatabase db, int i, int i2) { - db.execSQL("DROP TABLE IF EXISTS " + DATABASE_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_ENABLED); onCreate(db); } } void setEnabled(String packageName, boolean isEnabled) { String[] columns = new String[]{KEY_IS_ENABLED}; - try (Cursor res = ourDatabase.query(DATABASE_TABLE, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { + try (Cursor res = ourDatabase.query(TABLE_ENABLED, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { ContentValues cv = new ContentValues(); cv.put(KEY_IS_ENABLED, isEnabled ? 1 : 0); if (res.getCount() > 0) { - ourDatabase.update(DATABASE_TABLE, cv, KEY_PACKAGE_NAME + "=?", new String[]{packageName}); + ourDatabase.update(TABLE_ENABLED, cv, KEY_PACKAGE_NAME + "=?", new String[]{packageName}); } else { cv.put(KEY_PACKAGE_NAME, packageName); - ourDatabase.insert(DATABASE_TABLE, null, cv); + long retVal = ourDatabase.insert(TABLE_ENABLED, null, cv); + Log.i("AppDatabase", "SetEnabled retval = " + retVal); } } } @@ -105,12 +114,12 @@ void setAllEnabled(boolean enabled) { prefs.edit().putBoolean(SETTINGS_KEY_ALL_ENABLED, enabled).apply(); - ourDatabase.execSQL("UPDATE " + DATABASE_TABLE + " SET " + KEY_IS_ENABLED + "=" + (enabled? "1" : "0")); + ourDatabase.execSQL("UPDATE " + TABLE_ENABLED + " SET " + KEY_IS_ENABLED + "=" + (enabled? "1" : "0")); } boolean isEnabled(String packageName) { String[] columns = new String[]{KEY_IS_ENABLED}; - try (Cursor res = ourDatabase.query(DATABASE_TABLE, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { + try (Cursor res = ourDatabase.query(TABLE_ENABLED, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { boolean result; if (res.getCount() > 0) { res.moveToFirst(); @@ -129,4 +138,55 @@ return getAllEnabled(); } + public enum PrivacyOptions { + BLOCK_CONTENTS, + BLOCK_IMAGES + } + + private int getPrivacyOptionsValue(String packageName) + { + String[] columns = new String[]{KEY_PRIVACY_OPTIONS}; + try (Cursor res = ourDatabase.query(TABLE_PRIVACY, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { + int result; + if (res.getCount() > 0) { + res.moveToFirst(); + result = res.getInt(res.getColumnIndex(KEY_PRIVACY_OPTIONS)); + } else { + result = 0; + } + return result; + } + } + + private void setPrivacyOptionsValue(String packageName, int value) { + String[] columns = new String[]{KEY_PRIVACY_OPTIONS}; + try (Cursor res = ourDatabase.query(TABLE_PRIVACY, columns, KEY_PACKAGE_NAME + " =? ", new String[]{packageName}, null, null, null)) { + ContentValues cv = new ContentValues(); + cv.put(KEY_PRIVACY_OPTIONS, value); + // FIXME inserts data to a database, but getPrivacyOptionsValue() always returns 0 + if (res.getCount() > 0) { + ourDatabase.update(TABLE_PRIVACY, cv, KEY_PACKAGE_NAME + "=?", new String[]{packageName}); + } else { + cv.put(KEY_PACKAGE_NAME, packageName); + long retVal = ourDatabase.insert(TABLE_PRIVACY, null, cv); + Log.i("AppDatabase", "SetPrivacyOptions retval = " + retVal); + } + } + } + + public void setPrivacy(String packageName, PrivacyOptions option, boolean isBlocked) { + int curBit = option.ordinal(); + int value = getPrivacyOptionsValue(packageName); + value |= (1 << curBit); + value ^= isBlocked ? 0 : (1 << curBit); + setPrivacyOptionsValue(packageName, value); + } + + public boolean getPrivacy(String packageName, PrivacyOptions option) { + int curBit = option.ordinal(); + int value = getPrivacyOptionsValue(packageName); + int bit = value & (1 << curBit); + return bit != 0; + // We still use getPrivacy() in NotificationsPlugin.java, because this function anyways always returns false, so it doesn't block anything + } } diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationFilterActivity.java @@ -20,6 +20,8 @@ package org.kde.kdeconnect.Plugins.NotificationsPlugin; +import android.app.AlertDialog; +import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -32,11 +34,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; import android.widget.BaseAdapter; +import android.widget.CheckBox; import android.widget.CheckedTextView; import android.widget.ListView; import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.StringsHelper; import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; @@ -132,6 +138,7 @@ AppListAdapter adapter = new AppListAdapter(); listView.setAdapter(adapter); listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + listView.setLongClickable(true); listView.setOnItemClickListener((adapterView, view, i, l) -> { if (i == 0) { @@ -147,6 +154,59 @@ apps[i - 1].isEnabled = checked; } }); + listView.setOnItemLongClickListener((adapterView, view, i, l) -> { + if(i == 0) + return true; + Context context = this; + AlertDialog.Builder builder = new AlertDialog.Builder(context); + View mView = getLayoutInflater().inflate(R.layout.popup_notificationsfilter, null); + builder.setMessage(context.getResources().getString(R.string.extra_options)); + + ListView lv = mView.findViewById(R.id.extra_options_list); + final String[] options = new String[] { + context.getResources().getString(R.string.privacy_options) + }; + ArrayAdapter extra_options_adapter = new ArrayAdapter<>(this, + android.R.layout.simple_list_item_1, options); + lv.setAdapter(extra_options_adapter); + builder.setView(mView); + + AlertDialog ad = builder.create(); + + lv.setOnItemClickListener((new_adapterView, new_view, new_i, new_l) -> { + switch (new_i){ + case 0: + AlertDialog.Builder myBuilder = new AlertDialog.Builder(context); + String packageName = apps[i - 1].pkg; + boolean testIsEnabled = appDatabase.isEnabled(packageName); + + View myView = getLayoutInflater().inflate(R.layout.privacy_options, null); + CheckBox checkbox_contents = myView.findViewById(R.id.checkbox_contents); + checkbox_contents.setChecked(appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_CONTENTS)); + CheckBox checkbox_images = myView.findViewById(R.id.checkbox_images); + checkbox_images.setChecked(appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_IMAGES)); + + myBuilder.setView(myView); + myBuilder.setTitle(context.getResources().getString(R.string.privacy_options)); + myBuilder.setPositiveButton(context.getResources().getString(R.string.ok), (dialog, id) -> dialog.dismiss()); + myBuilder.setMessage(context.getResources().getString(R.string.set_privacy_options)); + + checkbox_contents.setOnCheckedChangeListener((compoundButton, b) -> + appDatabase.setPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_CONTENTS, + compoundButton.isChecked())); + checkbox_images.setOnCheckedChangeListener((compoundButton, b) -> + appDatabase.setPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_IMAGES, + compoundButton.isChecked())); + + ad.cancel(); + myBuilder.create().show(); + break; + } + }); + + ad.show(); + return true; + }); listView.setItemChecked(0, appDatabase.getAllEnabled()); //"Select all" button for (int i = 0; i < apps.length; i++) { diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java --- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java @@ -15,7 +15,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ package org.kde.kdeconnect.Plugins.NotificationsPlugin; @@ -225,7 +225,8 @@ } } - if (appIcon != null) { + if (appIcon != null && !appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_IMAGES)) { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream); byte[] bitmapData = outStream.toByteArray(); @@ -244,18 +245,28 @@ currentNotifications.add(key); } - RepliableNotification rn = extractRepliableNotification(statusBarNotification); - if (rn.pendingIntent != null) { - np.set("requestReplyId", rn.id); - pendingIntents.put(rn.id, rn); + boolean blockContents = appDatabase.getPrivacy(packageName, AppDatabase.PrivacyOptions.BLOCK_CONTENTS); + + if (!blockContents) { + RepliableNotification rn = extractRepliableNotification(statusBarNotification); + if (rn.pendingIntent != null) { + np.set("requestReplyId", rn.id); + pendingIntents.put(rn.id, rn); + } } np.set("id", key); - np.set("appName", appName == null ? packageName : appName); np.set("isClearable", statusBarNotification.isClearable()); - np.set("ticker", getTickerText(notification)); - np.set("title", getNotificationTitle(notification)); - np.set("text", getNotificationText(notification)); + np.set("appName", appName == null ? packageName : appName); + if(blockContents) { + np.set("ticker", ""); + np.set("title", ""); + //np.set("text", ""); + } else { + np.set("ticker", getTickerText(notification)); + np.set("title", getNotificationTitle(notification)); + np.set("text", getNotificationText(notification)); + } np.set("time", Long.toString(statusBarNotification.getPostTime())); device.sendPacket(np);