From e22c4d8cc197d0bae2329df1530697721a8c22d5 Mon Sep 17 00:00:00 2001 From: danijoo Date: Fri, 8 Jan 2016 00:36:24 +0100 Subject: [PATCH] backlist feature --- .../logger/BlacklistItem.java | 15 ++ .../logger/NotificationLoggerService.java | 36 +++- .../preferences/AppsAdapter.java | 73 +++++++ .../preferences/BlacklistFragment.java | 197 +++++++++++++++++- .../preferences/BlacklistListAdapter.java | 76 +++++++ .../preferences/PreferenceFragment.java | 41 +++- app/src/main/res/layout/apps_adapter_view.xml | 37 ++++ app/src/main/res/layout/blacklist_frag.xml | 38 ++++ app/src/main/res/values/strings.xml | 18 +- app/src/main/res/xml/preferences.xml | 2 +- 10 files changed, 510 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/net/headlezz/notificationlogger/logger/BlacklistItem.java create mode 100644 app/src/main/java/net/headlezz/notificationlogger/preferences/AppsAdapter.java create mode 100644 app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistListAdapter.java create mode 100644 app/src/main/res/layout/apps_adapter_view.xml create mode 100644 app/src/main/res/layout/blacklist_frag.xml diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/BlacklistItem.java b/app/src/main/java/net/headlezz/notificationlogger/logger/BlacklistItem.java new file mode 100644 index 0000000..4e7c3da --- /dev/null +++ b/app/src/main/java/net/headlezz/notificationlogger/logger/BlacklistItem.java @@ -0,0 +1,15 @@ +package net.headlezz.notificationlogger.logger; + + +import ckm.simple.sql_provider.annotation.SimpleSQLColumn; +import ckm.simple.sql_provider.annotation.SimpleSQLTable; + +@SimpleSQLTable(table="blacklist", provider="NotificationProvider") +public class BlacklistItem { + + @SimpleSQLColumn("_id") + public long id; + + @SimpleSQLColumn("package_name") + public String packageName; +} diff --git a/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java index d0c9c38..8539713 100644 --- a/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java +++ b/app/src/main/java/net/headlezz/notificationlogger/logger/NotificationLoggerService.java @@ -4,6 +4,7 @@ import android.app.Notification; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.database.Cursor; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; @@ -49,18 +50,37 @@ public class NotificationLoggerService extends NotificationListenerService imple public void onNotificationPosted(StatusBarNotification sbn) { super.onNotificationPosted(sbn); - if(!isEnabled) { + if (!isEnabled) { Timber.d("Notification posted but logger is disabled"); return; } LoggedNotification ln = buildLoggedNotification(sbn); - getApplicationContext().getContentResolver().insert( - Logged_notificationTable.CONTENT_URI, - Logged_notificationTable.getContentValues(ln, false) + if (isBlacklisted(ln)) { + Timber.d("App blacklisted. Notification not logged"); + } else { + getApplicationContext().getContentResolver().insert( + Logged_notificationTable.CONTENT_URI, + Logged_notificationTable.getContentValues(ln, false) + ); + Timber.d(String.format("Notificaton logged (id: %d)", ln.notificationId)); + } + } + + private boolean isBlacklisted(LoggedNotification ln) { + Cursor cursor = getApplicationContext().getContentResolver().query( + BlacklistTable.CONTENT_URI, + null, + BlacklistTable.FIELD_PACKAGE_NAME + " = ?", + new String[] { String.valueOf(ln.packageName) }, + null, + null ); - Timber.d(String.format("Notificaton logged (id: %d)", ln.notificationId)); + boolean isBlacklisted = cursor != null && cursor.getCount() > 0; + if(cursor != null) + cursor.close(); + return isBlacklisted; } private LoggedNotification buildLoggedNotification(StatusBarNotification sbn) { @@ -85,9 +105,9 @@ public class NotificationLoggerService extends NotificationListenerService imple String title = extras.getString("android.title", ""); String message = extras.getCharSequence("android.text", "").toString(); - if(message.isEmpty() && notification.tickerText != null) + if (message.isEmpty() && notification.tickerText != null) message = notification.tickerText.toString(); - if(message.isEmpty() && extras.containsKey("android.infoText")) + if (message.isEmpty() && extras.containsKey("android.infoText")) message = extras.get("android.infoText").toString(); LoggedNotification ln = new LoggedNotification(); @@ -104,7 +124,7 @@ public class NotificationLoggerService extends NotificationListenerService imple @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.equals("pref_logging_enabled")) { + if (key.equals("pref_logging_enabled")) { isEnabled = sharedPreferences.getBoolean("pref_logging_enabled", true); Timber.d("Logging is now " + isEnabled); } diff --git a/app/src/main/java/net/headlezz/notificationlogger/preferences/AppsAdapter.java b/app/src/main/java/net/headlezz/notificationlogger/preferences/AppsAdapter.java new file mode 100644 index 0000000..450f111 --- /dev/null +++ b/app/src/main/java/net/headlezz/notificationlogger/preferences/AppsAdapter.java @@ -0,0 +1,73 @@ +package net.headlezz.notificationlogger.preferences; + +import android.content.pm.PackageManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import net.headlezz.notificationlogger.PackageUtils; +import net.headlezz.notificationlogger.R; + +import java.util.List; + +import timber.log.Timber; + +public class AppsAdapter extends BaseAdapter { + + private List mPackageNames; + + class ViewHolder { + TextView tvName; + TextView tvPackageName; + ImageView ivIcon; + } + + public AppsAdapter(List packageNames) { + mPackageNames = packageNames; + } + + @Override + public int getCount() { + return mPackageNames.size(); + } + + @Override + public Object getItem(int position) { + return mPackageNames.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder viewHolder; + if(convertView == null) { + convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.apps_adapter_view, parent, false); + viewHolder = new ViewHolder(); + convertView.setTag(viewHolder); + viewHolder.ivIcon = (ImageView) convertView.findViewById(android.R.id.icon1); + viewHolder.tvName = (TextView) convertView.findViewById(android.R.id.text1); + viewHolder.tvPackageName = (TextView) convertView.findViewById(android.R.id.text2); + } else + viewHolder = (ViewHolder) convertView.getTag(); + + String packageName = (String) getItem(position); + viewHolder.tvPackageName.setText(packageName); + try { + viewHolder.tvName.setText(PackageUtils.getAppName(convertView.getContext(), packageName)); + viewHolder.ivIcon.setImageDrawable(PackageUtils.getApplicationLauncherIcon(convertView.getContext(), packageName)); + } catch (PackageManager.NameNotFoundException e) { + Timber.d("app name not found", e); + viewHolder.tvName.setText(""); + viewHolder.ivIcon.setImageDrawable(null); + } + + return convertView; + } +} diff --git a/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistFragment.java b/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistFragment.java index 1d37aba..923793b 100644 --- a/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistFragment.java +++ b/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistFragment.java @@ -1,13 +1,200 @@ package net.headlezz.notificationlogger.preferences; -import android.app.Activity; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; -public class BlacklistFragment extends Fragment { +import net.headlezz.notificationlogger.PackageUtils; +import net.headlezz.notificationlogger.R; +import net.headlezz.notificationlogger.logger.BlacklistItem; +import net.headlezz.notificationlogger.logger.BlacklistTable; - @SuppressWarnings("ConstantConditions") +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import timber.log.Timber; + +public class BlacklistFragment extends Fragment implements LoaderManager.LoaderCallbacks, View.OnClickListener, BlacklistListAdapter.BlacklistCallbacks { + + final int LOADER_ID = 1752; + + @Bind(R.id.blacklistList_emptyView) + View mEmptyView; + + @Bind(R.id.blacklistList_list) + RecyclerView mBlacklistList; + + @Bind(R.id.blacklistList_fab) + FloatingActionButton mFab; + + BlacklistListAdapter mAdapter; + + @Nullable @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.blacklist_frag, container, false); + ButterKnife.bind(this, view); + mBlacklistList.setLayoutManager(new LinearLayoutManager(getContext())); + mFab.setOnClickListener(this); + return view; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mAdapter = new BlacklistListAdapter(getContext(), null, this); + mBlacklistList.setAdapter(mAdapter); + getLoaderManager().initLoader(LOADER_ID, Bundle.EMPTY, this); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Timber.d("blacklistloader created"); + return new CursorLoader(getContext(), + BlacklistTable.CONTENT_URI, + null, + null, + null, + BlacklistTable.FIELD_PACKAGE_NAME + " DESC"); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + Timber.d("blacklistloader finished: " + data.getCount() + " items."); + mAdapter.changeCursor(data); + checkEmpty(); + } + + @Override + public void onLoaderReset(Loader loader) { + Timber.d("blacklistloader reset"); + mAdapter.changeCursor(null); + checkEmpty(); + } + + private void checkEmpty() { + if(mAdapter.getItemCount() == 0) { + mEmptyView.setVisibility(View.VISIBLE); + mBlacklistList.setVisibility(View.GONE); + } else { + mEmptyView.setVisibility(View.GONE); + mBlacklistList.setVisibility(View.VISIBLE); + } + } + + @Override + public void onClick(View v) { + loadBlacklistPickerDialog(); + } + + private void loadBlacklistPickerDialog() { + // disable fab until the list is loaded + mFab.setEnabled(false); + new AsyncTask>() { + + @Override + protected List doInBackground(Void... params) { + PackageManager pm = getContext().getPackageManager(); + List installedApps = pm.getInstalledApplications(PackageManager.GET_META_DATA); + List appPackages = new ArrayList<>(); + for(ApplicationInfo appInfo : installedApps) { + appPackages.add(appInfo.packageName); + } + Collections.sort(appPackages, new Comparator() { + @Override + public int compare(String lhs, String rhs) { + String lhsName, rhsName; + try { + lhsName = PackageUtils.getAppName(getContext(), lhs); + rhsName = PackageUtils.getAppName(getContext(), rhs); + } catch (PackageManager.NameNotFoundException e) { + return 0; + } + return lhsName.compareTo(rhsName); + } + }); + return appPackages; + } + + @Override + protected void onPostExecute(final List packageNames) { + mFab.setEnabled(true); + new AlertDialog.Builder(getContext()) + .setTitle(getContext().getString(R.string.blacklist_add_dialog_title)) + .setAdapter(new AppsAdapter(packageNames), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String packageName = packageNames.get(which); + BlacklistItem item = new BlacklistItem(); + item.packageName = packageName; + ContentResolver cr = getContext().getContentResolver(); + Cursor cursor = cr.query( + BlacklistTable.CONTENT_URI, + null, + BlacklistTable.FIELD_PACKAGE_NAME + " = ?", + new String[] { packageName }, + null, + null + ); + boolean blacklisted = cursor.getCount() > 0; + cursor.close(); + if(!blacklisted) { + getContext().getContentResolver().insert( + BlacklistTable.CONTENT_URI, + BlacklistTable.getContentValues(item, false) + ); + } + cursor.close(); + } + }) + .create().show(); + } + }.execute(); + } + + @Override + public void onBlacklistItemLongClicked(final BlacklistItem item) { + String appName; + try { + appName = PackageUtils.getAppName(getContext(), item.packageName); + } catch (PackageManager.NameNotFoundException e) { + appName = item.packageName; + } + new AlertDialog.Builder(getContext()) + .setTitle(getContext().getString(R.string.blacklist_dialog_remove_title)) + .setMessage(getContext().getString(R.string.blacklist_fragment_remove_message, appName)) + .setPositiveButton(getContext().getString(R.string.ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + getContext().getContentResolver().delete( + BlacklistTable.CONTENT_URI, + BlacklistTable.FIELD_PACKAGE_NAME + " = ?", + new String[] {String.valueOf(item.packageName)} + ); + } + }) + .setNegativeButton(getContext().getString(R.string.cancel), null) + .create().show(); + } } diff --git a/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistListAdapter.java b/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistListAdapter.java new file mode 100644 index 0000000..10a34e9 --- /dev/null +++ b/app/src/main/java/net/headlezz/notificationlogger/preferences/BlacklistListAdapter.java @@ -0,0 +1,76 @@ +package net.headlezz.notificationlogger.preferences; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import net.headlezz.notificationlogger.PackageUtils; +import net.headlezz.notificationlogger.logger.BlacklistItem; +import net.headlezz.notificationlogger.logger.BlacklistTable; +import net.headlezz.notificationlogger.notificationlist.CursorRecyclerViewAdapter; + +public class BlacklistListAdapter extends CursorRecyclerViewAdapter implements View.OnLongClickListener { + + interface BlacklistCallbacks { + void onBlacklistItemLongClicked(BlacklistItem item); + } + + class BlacklistViewHolder extends RecyclerView.ViewHolder { + + TextView mPackageName; + TextView mAppName; + + public BlacklistViewHolder(View itemView) { + super(itemView); + mAppName = (TextView) itemView.findViewById(android.R.id.text1); + mPackageName = (TextView) itemView.findViewById(android.R.id.text2); + } + + public void setBlacklistItem(BlacklistItem item) { + mPackageName.setText(item.packageName); + String appName; + try { + appName = PackageUtils.getAppName( + itemView.getContext(), + item.packageName + ); + } catch (PackageManager.NameNotFoundException e) { + appName = ""; + } + mAppName.setText(appName); + itemView.setTag(item); + } + } + + BlacklistCallbacks mCallbacks; + + public BlacklistListAdapter(Context context, Cursor cursor, BlacklistCallbacks cb) { + super(context, cursor); + mCallbacks = cb; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, Cursor cursor) { + BlacklistItem item = BlacklistTable.getRow(cursor, false); + ((BlacklistViewHolder) viewHolder).setBlacklistItem(item); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_2, parent, false); + view.setOnLongClickListener(this); + return new BlacklistViewHolder(view); + } + + @Override + public boolean onLongClick(View v) { + BlacklistItem item = (BlacklistItem) v.getTag(); + mCallbacks.onBlacklistItemLongClicked(item); + return true; + } +} diff --git a/app/src/main/java/net/headlezz/notificationlogger/preferences/PreferenceFragment.java b/app/src/main/java/net/headlezz/notificationlogger/preferences/PreferenceFragment.java index 0fc594a..1c69db2 100644 --- a/app/src/main/java/net/headlezz/notificationlogger/preferences/PreferenceFragment.java +++ b/app/src/main/java/net/headlezz/notificationlogger/preferences/PreferenceFragment.java @@ -3,14 +3,17 @@ package net.headlezz.notificationlogger.preferences; import android.app.Activity; import android.content.ContentResolver; import android.content.DialogInterface; +import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.AlertDialog; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; +import android.text.Html; import net.headlezz.notificationlogger.R; +import net.headlezz.notificationlogger.logger.BlacklistTable; import net.headlezz.notificationlogger.logger.Logged_notificationTable; public class PreferenceFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener { @@ -24,6 +27,7 @@ public class PreferenceFragment extends PreferenceFragmentCompat implements Pref interface PreferenceCallbacks { void showAboutScreen(); + void showBlacklistScreen(); } @@ -33,18 +37,47 @@ public class PreferenceFragment extends PreferenceFragmentCompat implements Pref findPreference(PREF_ABOUT).setOnPreferenceClickListener(this); findPreference(PREF_BLACKLIST).setOnPreferenceClickListener(this); findPreference(PREF_CLEAR_DATABASE).setOnPreferenceClickListener(this); + setBlacklistItemCount(); + } + + private void setBlacklistItemCount() { + new AsyncTask() { + + + @Override + protected Integer doInBackground(Void... params) { + Cursor cursor = getContext().getContentResolver().query( + BlacklistTable.CONTENT_URI, + null, + null, + null, + null + ); + cursor.moveToFirst(); + return cursor.getCount(); + } + + @Override + protected void onPostExecute(Integer blacklistItemCount) { + Preference blacklistPref = findPreference(PREF_BLACKLIST); + if (blacklistItemCount > 0) + blacklistPref.setSummary(Html.fromHtml(getString(R.string.pref_blacklist_summary_filled, blacklistItemCount))); + else + blacklistPref.setSummary(getString(R.string.pref_blacklist_summary_empty)); + } + }.execute(); } @Override public boolean onPreferenceClick(Preference preference) { String key = preference.getKey(); - if(key.equals(PREF_ABOUT)) { + if (key.equals(PREF_ABOUT)) { mCallbacks.showAboutScreen(); return true; - } else if(key.equals(PREF_BLACKLIST)) { + } else if (key.equals(PREF_BLACKLIST)) { mCallbacks.showBlacklistScreen(); return true; - } else if(key.equals(PREF_CLEAR_DATABASE)) + } else if (key.equals(PREF_CLEAR_DATABASE)) askClearDatabase(); return false; } @@ -77,7 +110,7 @@ public class PreferenceFragment extends PreferenceFragmentCompat implements Pref @Override protected void onPostExecute(Void aVoid) { - if(getActivity() != null) + if (getActivity() != null) Snackbar.make(getActivity().findViewById(android.R.id.content), R.string.pref_clear_database_snackbar_cleared, Snackbar.LENGTH_SHORT).show(); } }.execute(); diff --git a/app/src/main/res/layout/apps_adapter_view.xml b/app/src/main/res/layout/apps_adapter_view.xml new file mode 100644 index 0000000..5a72b12 --- /dev/null +++ b/app/src/main/res/layout/apps_adapter_view.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/blacklist_frag.xml b/app/src/main/res/layout/blacklist_frag.xml new file mode 100644 index 0000000..6c61735 --- /dev/null +++ b/app/src/main/res/layout/blacklist_frag.xml @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5240bcf..632d3bc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,21 +15,22 @@ Title: %1$s Message: %1$s App Info - OK + @string/ok Notification Settings Support Notification Logging Enable/Disable notification logging Blacklist - Exclude apps from being logged + Exclude apps from being logged + %1$s apps blacklisted. Clear database Remove all logged notifications Export to sdcard About Clear database Delete all logged notifications? - Cancel + @string/cancel Yes Database cleared. @@ -57,13 +58,20 @@ Failed to display app info. Filter Clear filter - OK - Cancel + @string/ok + @string/cancel Filter Title Message Application name Package name + No application is blacklisted. + FAB: Create new blacklist item + Choose app to blacklist + Remove from blacklist + Remove %s from blacklist? + OK + Cancel Alarm diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 1313815..2f47e66 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -11,7 +11,7 @@ android:title="@string/pref_logging_enabled_title" />