Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 827e381c authored by Danny Baumann's avatar Danny Baumann
Browse files

Improve notification spam filter code.

- Follow coding style guidelines
- Replace dialog shown on click by adding the relevant information and
  the delete button to the item itself
- Minor layout tweaks
- Make the empty view more descriptive
- Add license headers

Change-Id: Ic685364087d6a093fdbb6a143a9c6ed78596b62b
parent a8391354
Loading
Loading
Loading
Loading

res/layout/item_row.xml

deleted100644 → 0
+0 −27
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:padding="10dp"
    android:layout_height="wrap_content">

    <ImageView
        android:src="@drawable/ic_notify_open_normal"
        android:layout_marginRight="10dp"
        android:gravity="top"
        android:layout_gravity="top"
        android:layout_width="25dp"
        android:layout_height="25dp" />

    <TextView
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:gravity="center_vertical"
        android:fadingEdge="horizontal"
        android:id="@+id/label"
        android:maxLines="7"
        android:layout_weight="1"
        android:textColor="@android:color/darker_gray"
        android:layout_width="0dp"
        android:layout_height="wrap_content" />

</LinearLayout>
+76 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The CyanogenMod Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:padding="6dp"
    android:orientation="horizontal">

    <ImageView
        android:layout_width="25dp"
        android:layout_height="25dp"
        android:layout_marginEnd="10dp"
        android:layout_gravity="top"
        android:src="@drawable/ic_notify_open_normal"
        android:gravity="top" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:fadingEdge="horizontal"
            android:maxLines="7"
            android:textColor="?android:attr/textColorSecondary" />

        <TextView
            android:id="@+id/date_and_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="2dp"
            android:textAppearance="?android:attr/textAppearanceSmall" />

    </LinearLayout>

    <View
        android:layout_width="2dip"
        android:layout_height="match_parent"
        android:layout_marginTop="5dip"
        android:layout_marginBottom="5dip"
        android:background="@android:drawable/divider_horizontal_dark" />

    <ImageView
        android:id="@+id/spam_item_remove"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:paddingStart="8dip"
        android:paddingEnd="8dp"
        android:src="@drawable/ic_menu_delete_holo_dark"
        android:layout_gravity="center"
        android:clickable="true"
        android:focusable="true"
        android:background="?android:attr/selectableItemBackground" />

</LinearLayout>
+4 −3
Original line number Diff line number Diff line
@@ -1278,10 +1278,11 @@ two in order to insert additional control points. \'Remove\' deletes the selecte
    <!-- Setting checkbox summary for Whether to enable Android debugging support on the phone -->
    <string name="enable_adb_summary_cm">Enable the Android Debug Bridge (adb) interface</string>

    <string name="spam_added_title">Added %1$s</string>
    <string name="spam_last_blocked_title">Last blocked %1$s</string>
    <string name="spam_added_title">Added <xliff:g id="time_since" example="5 minutes ago">%1$s</xliff:g></string>
    <string name="spam_last_blocked_title">Last blocked <xliff:g id="time_since" example="5 minutes ago">%1$s</xliff:g> (total: <xliff:g id="count" example="2 times">%2$s</xliff:g>)</string>
    <string name="block_notifications_title">Filter notifications</string>
    <string name="block_notifications_summary">Manage ignored notifications and filters</string>
    <string name="no_filters_title">No filters set</string>
    <!-- Keep the referenced menu string consistent with status_bar_notification_spam_item_title in frameworks/base/packages/SystemUI -->
    <string name="no_filters_title">No filters set. To filter a notification, long press it and choose \'Ignore messages like this\'.</string>

</resources>
+125 −113
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The CyanogenMod Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.settings.cyanogenmod;

import java.util.ArrayList;
import java.util.List;

import android.app.AlertDialog;
import android.app.ListFragment;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.android.internal.util.cm.SpamFilter;
import static com.android.internal.util.cm.SpamFilter.*;
import com.android.internal.util.cm.SpamFilter.SpamContract;
import com.android.settings.R;
import com.android.settings.Settings;
import com.android.settings.Settings.NotificationStationActivity;

import java.util.ArrayList;
import java.util.List;

public class SpamList extends ListFragment {

    private static final int MENU_NOTIFICATIONS = Menu.FIRST;
    private static final Uri PACKAGES_URI;
    private static final Uri PACKAGES_NOTIFICATION_URI;
    static {
        Uri.Builder builder = new Uri.Builder();
        builder.scheme(ContentResolver.SCHEME_CONTENT);
        builder.authority(SpamFilter.AUTHORITY);
        builder.encodedPath("packages");
        PACKAGES_URI = builder.build();

        builder.encodedPath("message").build();
        PACKAGES_NOTIFICATION_URI = builder.build();
    }
    private static final Uri PACKAGES_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SpamFilter.AUTHORITY)
            .encodedPath("packages")
            .build();
    private static final Uri PACKAGES_NOTIFICATION_URI = PACKAGES_URI.buildUpon()
            .encodedPath("message")
            .build();

    private SpamAdapter mAdapter;
    private FetchFilters mTask;
    private FetchFilterTask mTask;

    private ContentObserver mObserver = new ContentObserver(null) {
        @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mTask = new FetchFilters();
        mTask.execute();
        getListView().setDividerHeight(0);
        addEmptyView();
        setHasOptionsMenu(true);
        getActivity().getContentResolver().registerContentObserver(
                SpamFilter.NOTIFICATION_URI, true, mObserver);
        public void onChange(boolean selfChange) {
            if (mTask != null) {
                mTask.cancel(true);
            }

    private void addEmptyView() {
        TextView v = new TextView(getActivity());
        v.setText(R.string.no_filters_title);
        v.setGravity(Gravity.CENTER);
        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        getActivity().addContentView(v, params);
        getListView().setEmptyView(v);
            mTask = new FetchFilterTask();
            mTask.execute();
        }
    };

    @Override
    public void onListItemClick(ListView l, View v, final int position, long id) {
        if (mAdapter.getItemViewType(position) == SpamAdapter.HEADER_TYPE) {
            return;
        }
        NotificationInfo info = (NotificationInfo) mAdapter.getItem(position);
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(info.appLabel);
        int baseTitleId = info.count == 0 ? R.string.spam_added_title : R.string.spam_last_blocked_title;
        String baseTitle = getActivity().getString(baseTitleId);
        StringBuilder msg = new StringBuilder();
        msg.append(String.format(baseTitle, DateUtils.getRelativeTimeSpanString(info.date))).append("\n\n");
        msg.append(getActivity().getString(R.string.app_ops_ignored_count, info.count));
        builder.setMessage(msg.toString());
        builder.setPositiveButton(R.string.ok, null);
        builder.setNeutralButton(R.string.blacklist_button_delete, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                mAdapter.removeItem(position);
                dialog.dismiss();
            }
        });
        AlertDialog dialog = builder.show();
        TextView textView = (TextView) dialog.findViewById(android.R.id.message);
        textView.setTextSize(17);
    public View onCreateView(LayoutInflater inflater,
            ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
                container, false);
        TextView emptyView = (TextView) v.findViewById(android.R.id.empty);
        emptyView.setText(R.string.no_filters_title);
        return v;
    }

    private ContentObserver mObserver = new ContentObserver(null) {
    @Override
        public void onChange(boolean selfChange) {
            if (mTask != null) {
                mTask.cancel(true);
            }
            mTask = new FetchFilters();
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        mTask = new FetchFilterTask();
        mTask.execute();
        getListView().setDividerHeight(0);
        setHasOptionsMenu(true);
        getActivity().getContentResolver().registerContentObserver(
                SpamFilter.NOTIFICATION_URI, true, mObserver);
    }
    };

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
@@ -157,16 +135,22 @@ public class SpamList extends ListFragment {
        int count;
    }

    private class FetchFilters extends AsyncTask<Void, Void, List<ItemInfo>> {
    private class FetchFilterTask extends AsyncTask<Void, Void, List<ItemInfo>> {

        private void addNotificationsForPackage(PackageInfo pInfo, List<ItemInfo> items) {
            Uri notificationUri = Uri.withAppendedPath(PACKAGES_NOTIFICATION_URI, String.valueOf(pInfo.id));
            Cursor c = getActivity().getContentResolver().query(notificationUri, null, null, null, null);
            Uri notificationUri = Uri.withAppendedPath(PACKAGES_NOTIFICATION_URI,
                    String.valueOf(pInfo.id));
            Cursor c = getActivity().getContentResolver().query(notificationUri,
                    null, null, null, null);
            if (c != null) {
                int notificationIdIndex = c.getColumnIndex(SpamContract.NotificationTable.ID);
                int notificationMessageIndex = c.getColumnIndex(SpamContract.NotificationTable.MESSAGE_TEXT);
                int notificationBlockedIndex = c.getColumnIndex(SpamContract.NotificationTable.LAST_BLOCKED);
                int notificationCountIndex = c.getColumnIndex(SpamContract.NotificationTable.COUNT);
                int notificationMessageIndex =
                        c.getColumnIndex(SpamContract.NotificationTable.MESSAGE_TEXT);
                int notificationBlockedIndex =
                        c.getColumnIndex(SpamContract.NotificationTable.LAST_BLOCKED);
                int notificationCountIndex =
                        c.getColumnIndex(SpamContract.NotificationTable.COUNT);

                while (c.moveToNext()) {
                    NotificationInfo nInfo = new NotificationInfo();
                    nInfo.messageText = c.getString(notificationMessageIndex);
@@ -183,16 +167,16 @@ public class SpamList extends ListFragment {
        @Override
        protected List<ItemInfo> doInBackground(Void... params) {
            List<ItemInfo> items = new ArrayList<ItemInfo>();
            Cursor c = getActivity().getContentResolver().query(
                    PACKAGES_URI, null, null, null, null);
            Cursor c = getActivity().getContentResolver().query(PACKAGES_URI,
                    null, null, null, null);
            if (c != null) {
                int packageIdIndex = c.getColumnIndex(SpamContract.PackageTable.ID);
                int packageNameIndex = c.getColumnIndex(SpamContract.PackageTable.PACKAGE_NAME);
                while (c.moveToNext()) {
                    PackageInfo pInfo = new PackageInfo();
                    pInfo.packageName = c.getString(packageNameIndex);
                    getAppInfo(pInfo);
                    pInfo.id = c.getInt(packageIdIndex);
                    pInfo.packageName = c.getString(packageNameIndex);
                    pInfo.applicationLabel = fetchAppLabel(pInfo.packageName);
                    items.add(pInfo);
                    addNotificationsForPackage(pInfo, items);
                }
@@ -201,32 +185,32 @@ public class SpamList extends ListFragment {
            return items;
        }

        private void getAppInfo(PackageInfo info) {
            ApplicationInfo appInfo = null;
        private CharSequence fetchAppLabel(String packageName) {
            PackageManager pm = getActivity().getPackageManager();
            try {
                appInfo = pm.getApplicationInfo(info.packageName, 0);
                info.applicationLabel = appInfo.loadLabel(pm);
                ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
                return appInfo.loadLabel(pm);
            } catch (PackageManager.NameNotFoundException e) {
                info.applicationLabel = info.packageName;
                return packageName;
            }
        }

        @Override
        protected void onPostExecute(List<ItemInfo> result) {
            mAdapter = new SpamAdapter(result);
            mAdapter = new SpamAdapter(getActivity(), result);
            setListAdapter(mAdapter);
            mTask = null;
        }
    }

    private class SpamAdapter extends BaseAdapter {

    private static class SpamAdapter extends BaseAdapter implements View.OnClickListener {
        private static final int HEADER_TYPE = 0;
        private static final int ENTRY_TYPE = 1;
        private List<ItemInfo> mItems;
        private Context mContext;

        SpamAdapter(List<ItemInfo> items) {
        SpamAdapter(Context context, List<ItemInfo> items) {
            mContext = context;
            mItems = items;
        }

@@ -247,8 +231,7 @@ public class SpamList extends ListFragment {

        @Override
        public int getItemViewType(int position) {
            return getItem(position) instanceof
                    PackageInfo ? HEADER_TYPE : ENTRY_TYPE;
            return getItem(position) instanceof PackageInfo ? HEADER_TYPE : ENTRY_TYPE;
        }

        @Override
@@ -256,14 +239,6 @@ public class SpamList extends ListFragment {
            return 2;
        }

        public void removeItem(int position) {
            ItemInfo item = mItems.get(position);
            Uri uri = Uri.withAppendedPath(PACKAGES_NOTIFICATION_URI,
                    String.valueOf(((NotificationInfo) item).id));
            getActivity().getContentResolver().delete(uri, null, null);
            notifyDataSetChanged();
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
@@ -277,25 +252,62 @@ public class SpamList extends ListFragment {
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            int viewType = getItemViewType(position);
            TextView titleView = null;
            ItemInfo info = getItem(position);
            String text;

            if (viewType == HEADER_TYPE) {
                if (convertView == null) {
                    convertView = new TextView(getActivity(), null,
                    convertView = new TextView(mContext, null,
                            android.R.attr.listSeparatorTextViewStyle);
                }
                titleView = (TextView) convertView;
                text = (String) ((PackageInfo) info).applicationLabel;
                TextView titleView = (TextView) convertView;
                titleView.setText(((PackageInfo) info).applicationLabel);
            } else {
                final ViewHolder holder;
                final NotificationInfo nInfo = (NotificationInfo) info;

                if (convertView == null) {
                    convertView = View.inflate(getActivity(), R.layout.item_row, null);
                    convertView = View.inflate(mContext, R.layout.spam_item_row, null);
                    holder = new ViewHolder();
                    holder.message = (TextView) convertView.findViewById(R.id.label);
                    holder.dateAndCount = (TextView) convertView.findViewById(R.id.date_and_count);
                    holder.deleteButton = convertView.findViewById(R.id.spam_item_remove);
                    holder.deleteButton.setOnClickListener(this);
                    convertView.setTag(holder);
                } else {
                    holder = (ViewHolder) convertView.getTag();
                }
                titleView = ((TextView) convertView.findViewById(R.id.label));
                text = ((NotificationInfo) info).messageText;

                holder.message.setText(nInfo.messageText);

                CharSequence dateString = DateUtils.getRelativeTimeSpanString(nInfo.date);
                if (nInfo.count == 0) {
                    holder.dateAndCount.setText(mContext.getString(R.string.spam_added_title,
                            dateString));
                } else {
                    String countString = mContext.getResources().getQuantityString(
                            R.plurals.app_ops_count, nInfo.count, nInfo.count);
                    holder.dateAndCount.setText(mContext.getString(R.string.spam_last_blocked_title,
                            dateString, countString));
                }

                holder.deleteButton.setTag(nInfo);
            }
            titleView.setText(text);

            return convertView;
        }

        @Override
        public void onClick(View v) {
            NotificationInfo item = (NotificationInfo) v.getTag();
            Uri uri = Uri.withAppendedPath(PACKAGES_NOTIFICATION_URI, String.valueOf(item.id));
            mContext.getContentResolver().delete(uri, null, null);
            notifyDataSetChanged();
        }

        private static class ViewHolder {
            TextView message;
            TextView dateAndCount;
            View deleteButton;
        }
    }
}