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

Commit eba6cd38 authored by Danesh M's avatar Danesh M Committed by Steve Kondik
Browse files

SpamFilter : Avoid a requery for each item

Keep an up to date in memory cache of spam item,
to avoid the lookup cost of querying the content provider each time.

Change-Id: I9e772584792eb22489ab72c0e741b6f8af57d5bc
parent 1882a02f
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -9,9 +9,11 @@ import android.text.TextUtils;
public class SpamFilter {

    public static final String AUTHORITY = "com.cyanogenmod.spam";
    public static final String MESSAGE_PATH = "message";
    public static final Uri NOTIFICATION_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(AUTHORITY)
            .appendEncodedPath(MESSAGE_PATH)
            .build();

    public static final class SpamContract {
@@ -38,6 +40,11 @@ public class SpamFilter {
        return msg.toLowerCase().replaceAll("[^\\p{L}\\p{Nd}]+", "");
    }

    public static String getNormalizedNotificationContent(Notification notification) {
        String content = getNotificationContent(notification);
        return getNormalizedContent(content);
    }

    public static String getNotificationContent(Notification notification) {
        Bundle extras = notification.extras;
        String titleExtra = extras.containsKey(Notification.EXTRA_TITLE_BIG)
+39 −40
Original line number Diff line number Diff line
@@ -25,14 +25,13 @@ public class SpamMessageProvider extends ContentProvider {

    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int PACKAGES = 0;
    private static final int MESSAGE = 1;
    private static final int PACKAGE_ID = 2;
    private static final int MESSAGES = 1;
    private static final int PACKAGE_FOR_ID = 2;
    private static final int MESSAGE_UPDATE_COUNT = 3;
    private static final int MESSAGE_FOR_ID = 4;
    static {
        sURIMatcher.addURI(AUTHORITY, "packages", PACKAGES);
        sURIMatcher.addURI(AUTHORITY, "package/id/*", PACKAGE_ID);
        sURIMatcher.addURI(AUTHORITY, "message", MESSAGE);
        sURIMatcher.addURI(AUTHORITY, "messages", MESSAGES);
        sURIMatcher.addURI(AUTHORITY, "message/#", MESSAGE_FOR_ID);
        sURIMatcher.addURI(AUTHORITY, "message/inc_count/#", MESSAGE_UPDATE_COUNT);
    }
@@ -50,33 +49,31 @@ public class SpamMessageProvider extends ContentProvider {
            String[] selectionArgs, String sortOrder) {
        int match = sURIMatcher.match(uri);
        switch (match) {
        case PACKAGE_ID:
            Cursor idCursor = mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
        case PACKAGE_FOR_ID:
            return mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
                    new String[]{NotificationTable.ID}, PackageTable.PACKAGE_NAME + "=?",
                    new String[]{uri.getLastPathSegment()}, null, null, null);
            return idCursor;
        case PACKAGES:
            Cursor pkgCursor = mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
                    null, null, null, null, null, null);
            return pkgCursor;
        case MESSAGE:
            return mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
                    projection, selection, selectionArgs, null, null, sortOrder);
        case MESSAGES:
            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
            qb.setTables(PackageTable.TABLE_NAME + "," + NotificationTable.TABLE_NAME);
            String pkgId = PackageTable.TABLE_NAME + "." + PackageTable.ID;
            String notificationPkgId = NotificationTable.TABLE_NAME + "."
                    + NotificationTable.PACKAGE_ID;
            qb.appendWhere(pkgId + "=" + notificationPkgId);
            qb.setTables(NotificationTable.TABLE_NAME + " LEFT OUTER JOIN " + PackageTable.TABLE_NAME +
                    " ON (" + NotificationTable.TABLE_NAME + "." + NotificationTable.PACKAGE_ID + " = "
                    + PackageTable.TABLE_NAME + "." + PackageTable.ID + ")");
            SQLiteDatabase db = mDbHelper.getReadableDatabase();
            Cursor ret = qb.query(db, new String[]{NotificationTable.TABLE_NAME + ".*"},
                    selection, selectionArgs, null, null, null);
            return ret;
            return qb.query(db, projection, selection, selectionArgs,
                    null, null, null);
        case MESSAGE_FOR_ID:
            qb = new SQLiteQueryBuilder();
            qb.setTables(NotificationTable.TABLE_NAME);
            qb.appendWhere(NotificationTable.PACKAGE_ID + "=" + uri.getLastPathSegment());
            db = mDbHelper.getReadableDatabase();
            ret = qb.query(db, null, null, null, null, null, null);
            return ret;
            qb.setTables(NotificationTable.TABLE_NAME + " LEFT OUTER JOIN " + PackageTable.TABLE_NAME +
                    " ON (" + NotificationTable.TABLE_NAME + "." + NotificationTable.PACKAGE_ID + " = "
                    + PackageTable.TABLE_NAME + "." + PackageTable.ID + ")");
            qb.appendWhere(NotificationTable.TABLE_NAME + "." + NotificationTable.ID + "="
                    + uri.getLastPathSegment());
            return qb.query(mDbHelper.getReadableDatabase(),
                    null, null, null, null,
                    null, null);
        default:
            return null;
        }
@@ -108,7 +105,7 @@ public class SpamMessageProvider extends ContentProvider {
        }
        int match = sURIMatcher.match(uri);
        switch (match) {
        case MESSAGE:
        case MESSAGES:
            String msgText = values.getAsString(NotificationTable.MESSAGE_TEXT);
            String packageName = values.getAsString(PackageTable.PACKAGE_NAME);
            if (TextUtils.isEmpty(msgText) || TextUtils.isEmpty(packageName)) {
@@ -117,9 +114,8 @@ public class SpamMessageProvider extends ContentProvider {
            values.clear();
            values.put(PackageTable.PACKAGE_NAME, packageName);
            long packageId = getPackageId(packageName);
            SQLiteDatabase writableDb = null;
            if (packageId == -1) {
                writableDb = mDbHelper.getWritableDatabase();
                SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
                packageId = writableDb.insert(
                        PackageTable.TABLE_NAME, null, values);
            }
@@ -130,20 +126,22 @@ public class SpamMessageProvider extends ContentProvider {
                        SpamFilter.getNormalizedContent(msgText));
                values.put(NotificationTable.PACKAGE_ID, packageId);
                values.put(NotificationTable.LAST_BLOCKED, System.currentTimeMillis());
                mDbHelper.getReadableDatabase().insert(NotificationTable.TABLE_NAME,
                long id = mDbHelper.getReadableDatabase().insert(NotificationTable.TABLE_NAME,
                        null, values);
                notifyChange();
                if (id != -1) {
                    notifyChange(String.valueOf(id));
                }
            }
            // Close the writable DB if non-null
            if (writableDb != null) writableDb.close();
            return null;
        default:
            return null;
        }
    }

    private void notifyChange() {
        getContext().getContentResolver().notifyChange(SpamFilter.NOTIFICATION_URI, null);
    private void notifyChange(String id) {
        Uri uri = Uri.withAppendedPath(SpamFilter.NOTIFICATION_URI,
                id);
        getContext().getContentResolver().notifyChange(uri, null);
    }

    private void removePackageIfNecessary(int packageId) {
@@ -154,7 +152,6 @@ public class SpamMessageProvider extends ContentProvider {
            SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
            writableDb.delete(PackageTable.TABLE_NAME, PackageTable.ID + "=?",
                    new String[]{String.valueOf(packageId)});
            writableDb.close();
        }
    }

@@ -174,11 +171,13 @@ public class SpamMessageProvider extends ContentProvider {
                idCursor.close();
            }
            SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
            String id = uri.getLastPathSegment();
            int result = writableDb.delete(NotificationTable.TABLE_NAME,
                    NotificationTable.ID + "=?", new String[]{uri.getLastPathSegment()});
            writableDb.close();
                    NotificationTable.ID + "=?", new String[]{id});
            removePackageIfNecessary(packageId);
            notifyChange();
            if (result > 0) {
                notifyChange(id);
            }
            return result;
        default:
            return 0;
@@ -190,12 +189,12 @@ public class SpamMessageProvider extends ContentProvider {
        int match = sURIMatcher.match(uri);
        switch (match) {
        case MESSAGE_UPDATE_COUNT:
            String id = uri.getLastPathSegment();
            String formattedQuery = String.format(UPDATE_COUNT_QUERY,
                    System.currentTimeMillis(), uri.getLastPathSegment());
                    System.currentTimeMillis(), id);
            SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
            writableDb.execSQL(formattedQuery);
            writableDb.close();
            notifyChange();
            notifyChange(id);
            return 0;
        default:
            return 0;
+12 −7
Original line number Diff line number Diff line
@@ -156,7 +156,7 @@ public abstract class BaseStatusBar extends SystemUI implements
    private static final Uri SPAM_MESSAGE_URI = new Uri.Builder()
           .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SpamMessageProvider.AUTHORITY)
            .appendPath("message")
            .appendPath("messages")
            .build();

    protected CommandQueue mCommandQueue;
@@ -947,12 +947,17 @@ public abstract class BaseStatusBar extends SystemUI implements
            filterButton.setVisibility(View.VISIBLE);
            filterButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                    AsyncTask.execute(new Runnable() {
                        @Override
                        public void run() {
                            ContentValues values = new ContentValues();
                            String message = SpamFilter.getNotificationContent(
                                    sbn.getNotification());
                            values.put(NotificationTable.MESSAGE_TEXT, message);
                            values.put(PackageTable.PACKAGE_NAME, pkg);
                            mContext.getContentResolver().insert(SPAM_MESSAGE_URI, values);
                        }
                    });
                    removeNotification(sbn.getKey(), null);
                }
            });
+123 −38
Original line number Diff line number Diff line
@@ -102,6 +102,7 @@ import android.util.AtomicFile;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
import android.util.SparseIntArray;
import android.util.Xml;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -154,6 +155,7 @@ import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/** {@hide} */
public class NotificationManagerService extends SystemService {
@@ -248,16 +250,19 @@ public class NotificationManagerService extends SystemService {
    private boolean mUseAttentionLight;
    boolean mSystemReady;

    private final LruCache<Integer, FilterCacheInfo> mSpamCache;
    private final SparseIntArray mSpamCache;
    private ExecutorService mSpamExecutor = Executors.newSingleThreadExecutor();

    private static final Uri FILTER_MSG_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SpamFilter.AUTHORITY)
            .appendPath("message")
            .appendPath("messages")
            .build();

    private static final Uri UPDATE_MSG_URI = FILTER_MSG_URI.buildUpon()
    private static final Uri UPDATE_MSG_URI = new Uri.Builder()
            .scheme(ContentResolver.SCHEME_CONTENT)
            .authority(SpamFilter.AUTHORITY)
            .appendPath(SpamFilter.MESSAGE_PATH)
            .appendEncodedPath("inc_count")
            .build();

@@ -839,6 +844,7 @@ public class NotificationManagerService extends SystemService {
                final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                // reload per-user settings
                mSettingsObserver.update(null);
                mSpamFilterObserver.update(null);
                mUserProfiles.updateCache(context);
                // Refresh managed services
                mConditionProviders.onUserSwitched(user);
@@ -853,6 +859,98 @@ public class NotificationManagerService extends SystemService {
        }
    };

    class SpamFilterObserver extends ContentObserver {

        private Future mTask;

        public SpamFilterObserver(Handler handler) {
            super(handler);
        }

        private void addToCache(Cursor c) {
            int notifId = c.getInt(c.getColumnIndex(
                    NotificationTable.ID));
            String pkgName = c.getString(c.getColumnIndex(
                    PackageTable.PACKAGE_NAME));
            String normalizedText = c.getString(c.getColumnIndex(
                    NotificationTable.NORMALIZED_TEXT));
            int hash = getSpamCacheHash(normalizedText, pkgName);
            synchronized (mSpamCache) {
                mSpamCache.put(hash, notifId);
            }
        }

        private Runnable mFetchAllFilters = new Runnable() {
            @Override
            public void run() {
                Cursor c = getContext().getContentResolver().query(FILTER_MSG_URI,
                        null, null, null, null);
                if (c != null) {
                    synchronized (mSpamCache) {
                        mSpamCache.clear();
                        while (c.moveToNext()) {
                            addToCache(c);
                            if (Thread.interrupted()) {
                                break;
                            }
                            c.close();
                        }
                    }
                }
            }
        };

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            update(uri);
        }

        void update(final Uri uri) {
            if (mTask != null && !mTask.isDone()) {
                mTask.cancel(true);
            }
            if (uri == null) {
                mTask = mSpamExecutor.submit(mFetchAllFilters);
            } else {
                Runnable r = new Runnable() {
                    @Override
                    public void run() {
                        String id = uri.getLastPathSegment();
                        Cursor c = getContext().getContentResolver().query(
                                uri, null, null, null, null);

                        if (c != null) {
                            int index;
                            synchronized (mSpamCache) {
                                index = mSpamCache.indexOfValue(Integer.parseInt(id));
                            }
                            if (!c.moveToFirst()) {
                                synchronized (mSpamCache) {
                                    // Filter was deleted
                                    if (index >= 0) {
                                        mSpamCache.removeAt(index);
                                    }
                                }
                            } else if (index < 0) {
                                // Filter was added/updated
                                addToCache(c);
                            }
                            c.close();
                        }
                    }
                };
                mTask = mSpamExecutor.submit(r);
            }
        }

        public void observe() {
            ContentResolver resolver = getContext().getContentResolver();
            resolver.registerContentObserver(SpamFilter.NOTIFICATION_URI,
                    true, this, UserHandle.USER_ALL);
            update(null);
        }
    }

    class SettingsObserver extends ContentObserver {
        private final Uri NOTIFICATION_LIGHT_PULSE_URI
                = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
@@ -967,6 +1065,7 @@ public class NotificationManagerService extends SystemService {
    }

    private SettingsObserver mSettingsObserver;
    private SpamFilterObserver mSpamFilterObserver;
    private ZenModeHelper mZenModeHelper;

    private final Runnable mBuzzBeepBlinked = new Runnable() {
@@ -991,7 +1090,7 @@ public class NotificationManagerService extends SystemService {

    public NotificationManagerService(Context context) {
        super(context);
        mSpamCache = new LruCache<Integer, FilterCacheInfo>(100);
        mSpamCache = new SparseIntArray();
    }

    @Override
@@ -1127,6 +1226,9 @@ public class NotificationManagerService extends SystemService {
        mSettingsObserver = new SettingsObserver(mHandler);
        mSettingsObserver.observe();

        mSpamFilterObserver = new SpamFilterObserver(mHandler);
        mSpamFilterObserver.observe();

        mArchive = new Archive(resources.getInteger(
                R.integer.config_notificationServiceArchiveSize));

@@ -1174,6 +1276,7 @@ public class NotificationManagerService extends SystemService {
            // This observer will force an update when observe is called, causing us to
            // bind to listener services.
            mSettingsObserver.observe();
            mSpamFilterObserver.observe();
            mListeners.onBootPhaseAppsCanStart();
            mConditionProviders.onBootPhaseAppsCanStart();
        }
@@ -2923,49 +3026,31 @@ public class NotificationManagerService extends SystemService {
        return (x < low) ? low : ((x > high) ? high : x);
    }

	private int getNotificationHash(Notification notification, String packageName) {
        CharSequence message = SpamFilter.getNotificationContent(notification);
        return (message + ":" + packageName).hashCode();
    }

    private static final class FilterCacheInfo {
        String packageName;
        int notificationId;
    private int getSpamCacheHash(CharSequence message, String packageName) {
        return (message + packageName).hashCode();
    }

    private boolean isNotificationSpam(Notification notification, String basePkg) {
        Integer notificationHash = getNotificationHash(notification, basePkg);
        boolean isSpam = false;
        if (mSpamCache.get(notificationHash) != null) {
            isSpam = true;
        } else {
            String msg = SpamFilter.getNotificationContent(notification);
            Cursor c = getContext().getContentResolver().query(FILTER_MSG_URI, null, IS_FILTERED_QUERY,
                    new String[]{SpamFilter.getNormalizedContent(msg), basePkg}, null);
            if (c != null) {
                if (c.moveToFirst()) {
                    FilterCacheInfo info = new FilterCacheInfo();
                    info.packageName = basePkg;
                    int notifId = c.getInt(c.getColumnIndex(NotificationTable.ID));
                    info.notificationId = notifId;
                    mSpamCache.put(notificationHash, info);
                    isSpam = true;
                }
                c.close();
            }
        CharSequence normalizedContent = SpamFilter
                .getNormalizedNotificationContent(notification);
        int notificationHash = getSpamCacheHash(normalizedContent, basePkg);
        int notificationId;
        synchronized (mSpamCache) {
            notificationId = mSpamCache.get(notificationHash, -1);
        }
        if (isSpam) {
            final int notifId = mSpamCache.get(notificationHash).notificationId;
        if (notificationId != -1) {
            final String notifIdString = String.valueOf(notificationId);
            mSpamExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Uri updateUri = Uri.withAppendedPath(UPDATE_MSG_URI, String.valueOf(notifId));
                    getContext().getContentResolver().update(updateUri, new ContentValues(),
                            null, null);
                    Uri updateUri = Uri.withAppendedPath(UPDATE_MSG_URI,
                            notifIdString);
                    getContext().getContentResolver().update(updateUri,
                            new ContentValues(), null, null);
                }
            });
        }
        return isSpam;
        return notificationId != -1;
    }

    void sendAccessibilityEvent(Notification notification, CharSequence packageName) {