Keep contact affinities separate across users.

Also ignore people extras on USER_ALL notifications.

Bug: 16213960
Change-Id: Ic0341c88d42d4a4f12cac35c0f41c2746aec02ac
This commit is contained in:
Chris Wren
2014-09-04 15:53:52 -04:00
parent b501eb7ae2
commit da4bd209cf
5 changed files with 113 additions and 59 deletions

View File

@@ -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);

View File

@@ -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));
}
};

View File

@@ -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();

View File

@@ -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());
}

View File

@@ -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);
}
}