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

Commit da4bd209 authored by Chris Wren's avatar Chris Wren
Browse files

Keep contact affinities separate across users.

Also ignore people extras on USER_ALL notifications.

Bug: 16213960
Change-Id: Ic0341c88d42d4a4f12cac35c0f41c2746aec02ac
parent b501eb7a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ interface INotificationManager

    ComponentName getEffectsSuppressor();
    boolean matchesCallFilter(in Bundle extras);
    boolean matchesCallFilterAsUser(in Bundle extras, int userId);

    ZenModeConfig getZenModeConfig();
    boolean setZenModeConfig(in ZenModeConfig config);
+8 −1
Original line number Diff line number Diff line
@@ -1470,7 +1470,14 @@ public class NotificationManagerService extends SystemService {
        @Override
        public boolean matchesCallFilter(Bundle extras) {
            enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
            return mZenModeHelper.matchesCallFilter(extras,
            return matchesCallFilterAsUser(extras, Binder.getCallingUid());
        }

        @Override
        public boolean matchesCallFilterAsUser(Bundle extras, int userId) {
            enforceSystemOrSystemUI("INotificationManager.matchesCallFilter");
            UserHandle userHandle = new UserHandle(userId);
            return mZenModeHelper.matchesCallFilter(userHandle, extras,
                    mRankingHelper.findExtractor(ValidateNotificationPeople.class));
        }
    };
+4 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.AudioAttributes;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;

import com.android.internal.annotations.VisibleForTesting;
@@ -88,8 +89,10 @@ public final class NotificationRecord {

    public Notification getNotification() { return sbn.getNotification(); }
    public int getFlags() { return sbn.getNotification().flags; }
    public int getUserId() { return sbn.getUserId(); }
    public UserHandle getUser() { return sbn.getUser(); }
    public String getKey() { return sbn.getKey(); }
    /** @deprecated Use {@link #getUser()} instead. */
    public int getUserId() { return sbn.getUserId(); }

    void dump(PrintWriter pw, String prefix, Context baseContext) {
        final Notification notification = sbn.getNotification();
+97 −55
Original line number Diff line number Diff line
@@ -18,18 +18,23 @@ package com.android.server.notification;

import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Map;

/**
 * This {@link NotificationSignalExtractor} attempts to validate
@@ -65,21 +70,88 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
    static final float STARRED_CONTACT = 1f;

    protected boolean mEnabled;
    private Context mContext;
    private Context mBaseContext;

    // maps raw person handle to resolved person object
    private LruCache<String, LookupResult> mPeopleCache;
    private Map<Integer, Context> mUserToContextMap;

    private RankingReconsideration validatePeople(final NotificationRecord record) {
    public void initialize(Context context) {
        if (DEBUG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
        mUserToContextMap = new ArrayMap<>();
        mBaseContext = context;
        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
        mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
                mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
    }

    public RankingReconsideration process(NotificationRecord record) {
        if (!mEnabled) {
            if (INFO) Slog.i(TAG, "disabled");
            return null;
        }
        if (record == null || record.getNotification() == null) {
            if (INFO) Slog.i(TAG, "skipping empty notification");
            return null;
        }
        if (record.getUserId() == UserHandle.USER_ALL) {
            if (INFO) Slog.i(TAG, "skipping global notification");
            return null;
        }
        Context context = getContextAsUser(record.getUser());
        if (context == null) {
            if (INFO) Slog.i(TAG, "skipping notification that lacks a context");
            return null;
        }
        return validatePeople(context, record);
    }

    @Override
    public void setConfig(RankingConfig config) {
        // ignore: config has no relevant information yet.
    }

    public float getContactAffinity(UserHandle userHandle, Bundle extras) {
        if (extras == null) return NONE;
        final String key = Long.toString(System.nanoTime());
        final float[] affinityOut = new float[1];
        Context context = getContextAsUser(userHandle);
        if (context == null) {
            return NONE;
        }
        final PeopleRankingReconsideration prr = validatePeople(context, key, extras, affinityOut);
        float affinity = affinityOut[0];
        if (prr != null) {
            prr.work();
            affinity = Math.max(prr.getContactAffinity(), affinity);
        }
        return affinity;
    }

    private Context getContextAsUser(UserHandle userHandle) {
        Context context = mUserToContextMap.get(userHandle.getIdentifier());
        if (context == null) {
            try {
                context = mBaseContext.createPackageContextAsUser("android", 0, userHandle);
                mUserToContextMap.put(userHandle.getIdentifier(), context);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "failed to create package context for lookups", e);
            }
        }
        return context;
    }

    private RankingReconsideration validatePeople(Context context,
            final NotificationRecord record) {
        final String key = record.getKey();
        final Bundle extras = record.getNotification().extras;
        final float[] affinityOut = new float[1];
        final RankingReconsideration rr = validatePeople(key, extras, affinityOut);
        final RankingReconsideration rr = validatePeople(context, key, extras, affinityOut);
        record.setContactAffinity(affinityOut[0]);
        return rr;
    }

    private PeopleRankingReconsideration validatePeople(String key, Bundle extras,
    private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
            float[] affinityOut) {
        float affinity = NONE;
        if (extras == null) {
@@ -98,7 +170,8 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
            if (TextUtils.isEmpty(handle)) continue;

            synchronized (mPeopleCache) {
                LookupResult lookupResult = mPeopleCache.get(handle);
                final String cacheKey = getCacheKey(context.getUserId(), handle);
                LookupResult lookupResult = mPeopleCache.get(cacheKey);
                if (lookupResult == null || lookupResult.isExpired()) {
                    pendingLookups.add(handle);
                } else {
@@ -119,7 +192,11 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
        }

        if (DEBUG) Slog.d(TAG, "Pending: future work scheduled for: " + key);
        return new PeopleRankingReconsideration(key, pendingLookups);
        return new PeopleRankingReconsideration(context, key, pendingLookups);
    }

    private String getCacheKey(int userId, String handle) {
        return Integer.toString(userId) + ":" + handle;
    }

    // VisibleForTesting
@@ -185,24 +262,24 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
        return null;
    }

    private LookupResult resolvePhoneContact(final String number) {
    private LookupResult resolvePhoneContact(Context context, final String number) {
        Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
                Uri.encode(number));
        return searchContacts(phoneUri);
        return searchContacts(context, phoneUri);
    }

    private LookupResult resolveEmailContact(final String email) {
    private LookupResult resolveEmailContact(Context context, final String email) {
        Uri numberUri = Uri.withAppendedPath(
                ContactsContract.CommonDataKinds.Email.CONTENT_LOOKUP_URI,
                Uri.encode(email));
        return searchContacts(numberUri);
        return searchContacts(context, numberUri);
    }

    private LookupResult searchContacts(Uri lookupUri) {
    private LookupResult searchContacts(Context context, Uri lookupUri) {
        LookupResult lookupResult = new LookupResult();
        Cursor c = null;
        try {
            c = mContext.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
            c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null);
            if (c != null && c.getCount() > 0) {
                c.moveToFirst();
                lookupResult.readContact(c);
@@ -217,44 +294,6 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
        return lookupResult;
    }

    public void initialize(Context context) {
        if (DEBUG) Slog.d(TAG, "Initializing  " + getClass().getSimpleName() + ".");
        mContext = context;
        mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
        mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
                mContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
    }

    public RankingReconsideration process(NotificationRecord record) {
        if (!mEnabled) {
            if (INFO) Slog.i(TAG, "disabled");
            return null;
        }
        if (record == null || record.getNotification() == null) {
            if (INFO) Slog.i(TAG, "skipping empty notification");
            return null;
        }
        return validatePeople(record);
    }

    @Override
    public void setConfig(RankingConfig config) {
        // ignore: config has no relevant information yet.
    }

    public float getContactAffinity(Bundle extras) {
        if (extras == null) return NONE;
        final String key = Long.toString(System.nanoTime());
        final float[] affinityOut = new float[1];
        final PeopleRankingReconsideration prr = validatePeople(key, extras, affinityOut);
        float affinity = affinityOut[0];
        if (prr != null) {
            prr.work();
            affinity = Math.max(prr.getContactAffinity(), affinity);
        }
        return affinity;
    }

    private static class LookupResult {
        private static final long CONTACT_REFRESH_MILLIS = 60 * 60 * 1000;  // 1hr
        public static final int INVALID_ID = -1;
@@ -317,11 +356,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {

    private class PeopleRankingReconsideration extends RankingReconsideration {
        private final LinkedList<String> mPendingLookups;
        private final Context mContext;

        private float mContactAffinity = NONE;

        private PeopleRankingReconsideration(String key, LinkedList<String> pendingLookups) {
        private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) {
            super(key);
            mContext = context;
            mPendingLookups = pendingLookups;
        }

@@ -333,20 +374,21 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
                final Uri uri = Uri.parse(handle);
                if ("tel".equals(uri.getScheme())) {
                    if (DEBUG) Slog.d(TAG, "checking telephone URI: " + handle);
                    lookupResult = resolvePhoneContact(uri.getSchemeSpecificPart());
                    lookupResult = resolvePhoneContact(mContext, uri.getSchemeSpecificPart());
                } else if ("mailto".equals(uri.getScheme())) {
                    if (DEBUG) Slog.d(TAG, "checking mailto URI: " + handle);
                    lookupResult = resolveEmailContact(uri.getSchemeSpecificPart());
                    lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
                } else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
                    if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
                    lookupResult = searchContacts(uri);
                    lookupResult = searchContacts(mContext, uri);
                } else {
                    lookupResult = new LookupResult();  // invalid person for the cache
                    Slog.w(TAG, "unsupported URI " + handle);
                }
                if (lookupResult != null) {
                    synchronized (mPeopleCache) {
                        mPeopleCache.put(handle, lookupResult);
                        final String cacheKey = getCacheKey(mContext.getUserId(), handle);
                        mPeopleCache.put(cacheKey, lookupResult);
                    }
                    mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
                }
+3 −2
Original line number Diff line number Diff line
@@ -373,13 +373,14 @@ public class ZenModeHelper {
        return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
    }

    public boolean matchesCallFilter(Bundle extras, ValidateNotificationPeople validator) {
    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
            ValidateNotificationPeople validator) {
        final int zen = mZenMode;
        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
            if (!mConfig.allowCalls) return false; // no calls get through
            if (validator != null) {
                final float contactAffinity = validator.getContactAffinity(extras);
                final float contactAffinity = validator.getContactAffinity(userHandle, extras);
                return audienceMatches(contactAffinity);
            }
        }