Merge "Look up phone numbers associated with a NotificationRecord's contact URI." into sc-v2-dev am: c69d39d72e
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17198933 Change-Id: I7a6da08a453893addc99144a02e6e84d8a340168
This commit is contained in:
@@ -200,6 +200,10 @@ public final class NotificationRecord {
|
|||||||
private boolean mIsAppImportanceLocked;
|
private boolean mIsAppImportanceLocked;
|
||||||
private ArraySet<Uri> mGrantableUris;
|
private ArraySet<Uri> mGrantableUris;
|
||||||
|
|
||||||
|
// Storage for phone numbers that were found to be associated with
|
||||||
|
// contacts in this notification.
|
||||||
|
private ArraySet<String> mPhoneNumbers;
|
||||||
|
|
||||||
// Whether this notification record should have an update logged the next time notifications
|
// Whether this notification record should have an update logged the next time notifications
|
||||||
// are sorted.
|
// are sorted.
|
||||||
private boolean mPendingLogUpdate = false;
|
private boolean mPendingLogUpdate = false;
|
||||||
@@ -1525,6 +1529,26 @@ public final class NotificationRecord {
|
|||||||
return mPendingLogUpdate;
|
return mPendingLogUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the given set of phone numbers into the list of phone numbers that
|
||||||
|
* are cached on this notification record.
|
||||||
|
*/
|
||||||
|
public void mergePhoneNumbers(ArraySet<String> phoneNumbers) {
|
||||||
|
// if the given phone numbers are null or empty then don't do anything
|
||||||
|
if (phoneNumbers == null || phoneNumbers.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// initialize if not already
|
||||||
|
if (mPhoneNumbers == null) {
|
||||||
|
mPhoneNumbers = new ArraySet<>();
|
||||||
|
}
|
||||||
|
mPhoneNumbers.addAll(phoneNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArraySet<String> getPhoneNumbers() {
|
||||||
|
return mPhoneNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final class Light {
|
static final class Light {
|
||||||
public final int color;
|
public final int color;
|
||||||
|
|||||||
@@ -68,7 +68,10 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
|
private static final boolean ENABLE_PEOPLE_VALIDATOR = true;
|
||||||
private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
|
private static final String SETTING_ENABLE_PEOPLE_VALIDATOR =
|
||||||
"validate_notification_people_enabled";
|
"validate_notification_people_enabled";
|
||||||
private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.STARRED };
|
private static final String[] LOOKUP_PROJECTION = { Contacts._ID, Contacts.LOOKUP_KEY,
|
||||||
|
Contacts.STARRED, Contacts.HAS_PHONE_NUMBER };
|
||||||
|
private static final String[] PHONE_LOOKUP_PROJECTION =
|
||||||
|
{ ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER };
|
||||||
private static final int MAX_PEOPLE = 10;
|
private static final int MAX_PEOPLE = 10;
|
||||||
private static final int PEOPLE_CACHE_SIZE = 200;
|
private static final int PEOPLE_CACHE_SIZE = 200;
|
||||||
|
|
||||||
@@ -409,6 +412,35 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
return lookupResult;
|
return lookupResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
// Performs a contacts search using searchContacts, and then follows up by looking up
|
||||||
|
// any phone numbers associated with the resulting contact information and merge those
|
||||||
|
// into the lookup result as well. Will have no additional effect if the contact does
|
||||||
|
// not have any phone numbers.
|
||||||
|
LookupResult searchContactsAndLookupNumbers(Context context, Uri lookupUri) {
|
||||||
|
LookupResult lookupResult = searchContacts(context, lookupUri);
|
||||||
|
String phoneLookupKey = lookupResult.getPhoneLookupKey();
|
||||||
|
if (phoneLookupKey != null) {
|
||||||
|
String selection = Contacts.LOOKUP_KEY + " = ?";
|
||||||
|
String[] selectionArgs = new String[] { phoneLookupKey };
|
||||||
|
try (Cursor cursor = context.getContentResolver().query(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONE_LOOKUP_PROJECTION,
|
||||||
|
selection, selectionArgs, /* sortOrder= */ null)) {
|
||||||
|
if (cursor == null) {
|
||||||
|
Slog.w(TAG, "Cursor is null when querying contact phone number.");
|
||||||
|
return lookupResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
lookupResult.mergePhoneNumber(cursor);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Slog.w(TAG, "Problem getting content resolver or querying phone numbers.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lookupResult;
|
||||||
|
}
|
||||||
|
|
||||||
private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
|
private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) {
|
||||||
final int workUserId = findWorkUserId(context);
|
final int workUserId = findWorkUserId(context);
|
||||||
if (workUserId == -1) {
|
if (workUserId == -1) {
|
||||||
@@ -454,6 +486,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
|
|
||||||
private final long mExpireMillis;
|
private final long mExpireMillis;
|
||||||
private float mAffinity = NONE;
|
private float mAffinity = NONE;
|
||||||
|
private boolean mHasPhone = false;
|
||||||
|
private String mPhoneLookupKey = null;
|
||||||
|
private ArraySet<String> mPhoneNumbers = new ArraySet<>();
|
||||||
|
|
||||||
public LookupResult() {
|
public LookupResult() {
|
||||||
mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
|
mExpireMillis = System.currentTimeMillis() + CONTACT_REFRESH_MILLIS;
|
||||||
@@ -473,6 +508,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
Slog.i(TAG, "invalid cursor: no _ID");
|
Slog.i(TAG, "invalid cursor: no _ID");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lookup key for potentially looking up contact phone number later
|
||||||
|
final int lookupKeyIdx = cursor.getColumnIndex(Contacts.LOOKUP_KEY);
|
||||||
|
if (lookupKeyIdx >= 0) {
|
||||||
|
mPhoneLookupKey = cursor.getString(lookupKeyIdx);
|
||||||
|
if (DEBUG) Slog.d(TAG, "contact LOOKUP_KEY is: " + mPhoneLookupKey);
|
||||||
|
} else {
|
||||||
|
if (DEBUG) Slog.d(TAG, "invalid cursor: no LOOKUP_KEY");
|
||||||
|
}
|
||||||
|
|
||||||
// Starred
|
// Starred
|
||||||
final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
|
final int starIdx = cursor.getColumnIndex(Contacts.STARRED);
|
||||||
if (starIdx >= 0) {
|
if (starIdx >= 0) {
|
||||||
@@ -484,6 +528,39 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
} else {
|
} else {
|
||||||
if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
|
if (DEBUG) Slog.d(TAG, "invalid cursor: no STARRED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// whether a phone number is present
|
||||||
|
final int hasPhoneIdx = cursor.getColumnIndex(Contacts.HAS_PHONE_NUMBER);
|
||||||
|
if (hasPhoneIdx >= 0) {
|
||||||
|
mHasPhone = cursor.getInt(hasPhoneIdx) != 0;
|
||||||
|
if (DEBUG) Slog.d(TAG, "contact HAS_PHONE_NUMBER is: " + mHasPhone);
|
||||||
|
} else {
|
||||||
|
if (DEBUG) Slog.d(TAG, "invalid cursor: no HAS_PHONE_NUMBER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the phone lookup key that is cached in this result, or null
|
||||||
|
// if the contact has no known phone info.
|
||||||
|
public String getPhoneLookupKey() {
|
||||||
|
if (!mHasPhone) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return mPhoneLookupKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge phone number found in this lookup and store it in mPhoneNumbers.
|
||||||
|
public void mergePhoneNumber(Cursor cursor) {
|
||||||
|
final int phoneNumIdx = cursor.getColumnIndex(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
|
||||||
|
if (phoneNumIdx >= 0) {
|
||||||
|
mPhoneNumbers.add(cursor.getString(phoneNumIdx));
|
||||||
|
} else {
|
||||||
|
if (DEBUG) Slog.d(TAG, "invalid cursor: no NORMALIZED_NUMBER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArraySet<String> getPhoneNumbers() {
|
||||||
|
return mPhoneNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isExpired() {
|
private boolean isExpired() {
|
||||||
@@ -509,6 +586,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
// Amount of time to wait for a result from the contacts db before rechecking affinity.
|
// Amount of time to wait for a result from the contacts db before rechecking affinity.
|
||||||
private static final long LOOKUP_TIME = 1000;
|
private static final long LOOKUP_TIME = 1000;
|
||||||
private float mContactAffinity = NONE;
|
private float mContactAffinity = NONE;
|
||||||
|
private ArraySet<String> mPhoneNumbers = null;
|
||||||
private NotificationRecord mRecord;
|
private NotificationRecord mRecord;
|
||||||
|
|
||||||
private PeopleRankingReconsideration(Context context, String key,
|
private PeopleRankingReconsideration(Context context, String key,
|
||||||
@@ -543,7 +621,9 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
|
lookupResult = resolveEmailContact(mContext, uri.getSchemeSpecificPart());
|
||||||
} else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
|
} else if (handle.startsWith(Contacts.CONTENT_LOOKUP_URI.toString())) {
|
||||||
if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
|
if (DEBUG) Slog.d(TAG, "checking lookup URI: " + handle);
|
||||||
lookupResult = searchContacts(mContext, uri);
|
// only look up phone number if this is a contact lookup uri and thus isn't
|
||||||
|
// already directly a phone number.
|
||||||
|
lookupResult = searchContactsAndLookupNumbers(mContext, uri);
|
||||||
} else {
|
} else {
|
||||||
lookupResult = new LookupResult(); // invalid person for the cache
|
lookupResult = new LookupResult(); // invalid person for the cache
|
||||||
if (!"name".equals(uri.getScheme())) {
|
if (!"name".equals(uri.getScheme())) {
|
||||||
@@ -561,6 +641,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
|
Slog.d(TAG, "lookup contactAffinity is " + lookupResult.getAffinity());
|
||||||
}
|
}
|
||||||
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
|
mContactAffinity = Math.max(mContactAffinity, lookupResult.getAffinity());
|
||||||
|
// merge any phone numbers found in this lookup result
|
||||||
|
if (lookupResult.getPhoneNumbers() != null) {
|
||||||
|
if (mPhoneNumbers == null) {
|
||||||
|
mPhoneNumbers = new ArraySet<>();
|
||||||
|
}
|
||||||
|
mPhoneNumbers.addAll(lookupResult.getPhoneNumbers());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (DEBUG) Slog.d(TAG, "lookupResult is null");
|
if (DEBUG) Slog.d(TAG, "lookupResult is null");
|
||||||
}
|
}
|
||||||
@@ -581,6 +668,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
|
|||||||
float affinityBound = operand.getContactAffinity();
|
float affinityBound = operand.getContactAffinity();
|
||||||
operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
|
operand.setContactAffinity(Math.max(mContactAffinity, affinityBound));
|
||||||
if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
|
if (VERBOSE) Slog.i(TAG, "final affinity: " + operand.getContactAffinity());
|
||||||
|
operand.mergePhoneNumbers(mPhoneNumbers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getContactAffinity() {
|
public float getContactAffinity() {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import android.telecom.TelecomManager;
|
|||||||
import android.telephony.PhoneNumberUtils;
|
import android.telephony.PhoneNumberUtils;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
|
import android.util.ArraySet;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.messages.nano.SystemMessageProto;
|
import com.android.internal.messages.nano.SystemMessageProto;
|
||||||
@@ -126,7 +127,7 @@ public class ZenModeFiltering {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void recordCall(NotificationRecord record) {
|
protected void recordCall(NotificationRecord record) {
|
||||||
REPEAT_CALLERS.recordCall(mContext, extras(record));
|
REPEAT_CALLERS.recordCall(mContext, extras(record), record.getPhoneNumbers());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,6 +326,10 @@ public class ZenModeFiltering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void cleanUpCallersAfter(long timeThreshold) {
|
||||||
|
REPEAT_CALLERS.cleanUpCallsAfter(timeThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
private static class RepeatCallers {
|
private static class RepeatCallers {
|
||||||
// We keep a separate map per uri scheme to do more generous number-matching
|
// We keep a separate map per uri scheme to do more generous number-matching
|
||||||
// handling on telephone numbers specifically. For other inputs, we
|
// handling on telephone numbers specifically. For other inputs, we
|
||||||
@@ -333,7 +338,8 @@ public class ZenModeFiltering {
|
|||||||
private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>();
|
private final ArrayMap<String, Long> mOtherCalls = new ArrayMap<>();
|
||||||
private int mThresholdMinutes;
|
private int mThresholdMinutes;
|
||||||
|
|
||||||
private synchronized void recordCall(Context context, Bundle extras) {
|
private synchronized void recordCall(Context context, Bundle extras,
|
||||||
|
ArraySet<String> phoneNumbers) {
|
||||||
setThresholdMinutes(context);
|
setThresholdMinutes(context);
|
||||||
if (mThresholdMinutes <= 0 || extras == null) return;
|
if (mThresholdMinutes <= 0 || extras == null) return;
|
||||||
final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
|
final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
|
||||||
@@ -341,7 +347,7 @@ public class ZenModeFiltering {
|
|||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
cleanUp(mTelCalls, now);
|
cleanUp(mTelCalls, now);
|
||||||
cleanUp(mOtherCalls, now);
|
cleanUp(mOtherCalls, now);
|
||||||
recordCallers(extraPeople, now);
|
recordCallers(extraPeople, phoneNumbers, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean isRepeat(Context context, Bundle extras) {
|
private synchronized boolean isRepeat(Context context, Bundle extras) {
|
||||||
@@ -365,6 +371,23 @@ public class ZenModeFiltering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up all calls that occurred after the given time.
|
||||||
|
// Used only for tests, to clean up after testing.
|
||||||
|
private synchronized void cleanUpCallsAfter(long timeThreshold) {
|
||||||
|
for (int i = mTelCalls.size() - 1; i >= 0; i--) {
|
||||||
|
final long time = mTelCalls.valueAt(i);
|
||||||
|
if (time > timeThreshold) {
|
||||||
|
mTelCalls.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int j = mOtherCalls.size() - 1; j >= 0; j--) {
|
||||||
|
final long time = mOtherCalls.valueAt(j);
|
||||||
|
if (time > timeThreshold) {
|
||||||
|
mOtherCalls.removeAt(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setThresholdMinutes(Context context) {
|
private void setThresholdMinutes(Context context) {
|
||||||
if (mThresholdMinutes <= 0) {
|
if (mThresholdMinutes <= 0) {
|
||||||
mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
|
mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
|
||||||
@@ -372,7 +395,8 @@ public class ZenModeFiltering {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void recordCallers(String[] people, long now) {
|
private synchronized void recordCallers(String[] people, ArraySet<String> phoneNumbers,
|
||||||
|
long now) {
|
||||||
for (int i = 0; i < people.length; i++) {
|
for (int i = 0; i < people.length; i++) {
|
||||||
String person = people[i];
|
String person = people[i];
|
||||||
if (person == null) continue;
|
if (person == null) continue;
|
||||||
@@ -393,6 +417,14 @@ public class ZenModeFiltering {
|
|||||||
mOtherCalls.put(person, now);
|
mOtherCalls.put(person, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record any additional numbers from the notification record if
|
||||||
|
// provided; these are in the format of just a phone number string
|
||||||
|
if (phoneNumbers != null) {
|
||||||
|
for (String num : phoneNumbers) {
|
||||||
|
mTelCalls.put(num, now);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized boolean checkCallers(Context context, String[] people) {
|
private synchronized boolean checkCallers(Context context, String[] people) {
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import android.os.Vibrator;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.service.notification.Adjustment;
|
import android.service.notification.Adjustment;
|
||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
|
import android.util.ArraySet;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest;
|
import androidx.test.filters.SmallTest;
|
||||||
@@ -1328,4 +1329,45 @@ public class NotificationRecordTest extends UiServiceTestCase {
|
|||||||
|
|
||||||
assertFalse(record.isConversation());
|
assertFalse(record.isConversation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mergePhoneNumbers_nulls() {
|
||||||
|
// make sure nothing dies if we just don't have any phone numbers
|
||||||
|
StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
|
||||||
|
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
|
||||||
|
false /* lights */, false /* defaultLights */, null /* group */);
|
||||||
|
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
|
||||||
|
|
||||||
|
// by default, no phone numbers
|
||||||
|
assertNull(record.getPhoneNumbers());
|
||||||
|
|
||||||
|
// nothing happens if we attempt to merge phone numbers but there aren't any
|
||||||
|
record.mergePhoneNumbers(null);
|
||||||
|
assertNull(record.getPhoneNumbers());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mergePhoneNumbers_addNumbers() {
|
||||||
|
StatusBarNotification sbn = getNotification(PKG_N_MR1, true /* noisy */,
|
||||||
|
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
|
||||||
|
false /* lights */, false /* defaultLights */, null /* group */);
|
||||||
|
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
|
||||||
|
|
||||||
|
// by default, no phone numbers
|
||||||
|
assertNull(record.getPhoneNumbers());
|
||||||
|
|
||||||
|
// make sure it behaves properly when we merge in some real content
|
||||||
|
record.mergePhoneNumbers(new ArraySet<>(
|
||||||
|
new String[]{"16175551212", "16175552121"}));
|
||||||
|
assertTrue(record.getPhoneNumbers().contains("16175551212"));
|
||||||
|
assertTrue(record.getPhoneNumbers().contains("16175552121"));
|
||||||
|
assertFalse(record.getPhoneNumbers().contains("16175553434"));
|
||||||
|
|
||||||
|
// now merge in a new number, make sure old ones are still there and the new one
|
||||||
|
// is also there
|
||||||
|
record.mergePhoneNumbers(new ArraySet<>(new String[]{"16175553434"}));
|
||||||
|
assertTrue(record.getPhoneNumbers().contains("16175551212"));
|
||||||
|
assertTrue(record.getPhoneNumbers().contains("16175552121"));
|
||||||
|
assertTrue(record.getPhoneNumbers().contains("16175553434"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,13 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.contains;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -29,6 +34,7 @@ import android.app.Person;
|
|||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
@@ -43,6 +49,8 @@ import com.android.server.UiServiceTestCase;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -240,6 +248,118 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase {
|
|||||||
assertFalse(ContentProvider.uriHasUserId(queryUri.getValue()));
|
assertFalse(ContentProvider.uriHasUserId(queryUri.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergePhoneNumbers_noPhoneNumber() {
|
||||||
|
// If merge phone number is called but the contacts lookup turned up no available
|
||||||
|
// phone number (HAS_PHONE_NUMBER is false), then no query should happen.
|
||||||
|
|
||||||
|
// setup of various bits required for querying
|
||||||
|
final Context mockContext = mock(Context.class);
|
||||||
|
final ContentResolver mockContentResolver = mock(ContentResolver.class);
|
||||||
|
when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
|
||||||
|
final int contactId = 12345;
|
||||||
|
final Uri lookupUri = Uri.withAppendedPath(
|
||||||
|
ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
|
||||||
|
|
||||||
|
// when the contact is looked up, we return a cursor that has one entry whose info is:
|
||||||
|
// _ID: 1
|
||||||
|
// LOOKUP_KEY: "testlookupkey"
|
||||||
|
// STARRED: 0
|
||||||
|
// HAS_PHONE_NUMBER: 0
|
||||||
|
Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 0);
|
||||||
|
when(mockContentResolver.query(any(), any(), any(), any(), any())).thenReturn(cursor);
|
||||||
|
|
||||||
|
// call searchContacts and then mergePhoneNumbers, make sure we never actually
|
||||||
|
// query the content resolver for a phone number
|
||||||
|
new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
|
||||||
|
verify(mockContentResolver, never()).query(
|
||||||
|
eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
|
||||||
|
eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
|
||||||
|
contains(ContactsContract.Contacts.LOOKUP_KEY),
|
||||||
|
any(), // selection args
|
||||||
|
isNull()); // sort order
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergePhoneNumbers_hasNumber() {
|
||||||
|
// If merge phone number is called and the contact lookup has a phone number,
|
||||||
|
// make sure there's then a subsequent query for the phone number.
|
||||||
|
|
||||||
|
// setup of various bits required for querying
|
||||||
|
final Context mockContext = mock(Context.class);
|
||||||
|
final ContentResolver mockContentResolver = mock(ContentResolver.class);
|
||||||
|
when(mockContext.getContentResolver()).thenReturn(mockContentResolver);
|
||||||
|
final int contactId = 12345;
|
||||||
|
final Uri lookupUri = Uri.withAppendedPath(
|
||||||
|
ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId));
|
||||||
|
|
||||||
|
// when the contact is looked up, we return a cursor that has one entry whose info is:
|
||||||
|
// _ID: 1
|
||||||
|
// LOOKUP_KEY: "testlookupkey"
|
||||||
|
// STARRED: 0
|
||||||
|
// HAS_PHONE_NUMBER: 1
|
||||||
|
Cursor cursor = makeMockCursor(1, "testlookupkey", 0, 1);
|
||||||
|
|
||||||
|
// make sure to add some specifics so this cursor is only returned for the
|
||||||
|
// contacts database lookup.
|
||||||
|
when(mockContentResolver.query(eq(lookupUri), any(),
|
||||||
|
isNull(), isNull(), isNull())).thenReturn(cursor);
|
||||||
|
|
||||||
|
// in the case of a phone lookup, return null cursor; that's not an error case
|
||||||
|
// and we're not checking the actual storing of the phone data here.
|
||||||
|
when(mockContentResolver.query(eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
|
||||||
|
eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
|
||||||
|
contains(ContactsContract.Contacts.LOOKUP_KEY),
|
||||||
|
any(), isNull())).thenReturn(null);
|
||||||
|
|
||||||
|
// call searchContacts and then mergePhoneNumbers, and check that we query
|
||||||
|
// once for the
|
||||||
|
new ValidateNotificationPeople().searchContactsAndLookupNumbers(mockContext, lookupUri);
|
||||||
|
verify(mockContentResolver, times(1)).query(
|
||||||
|
eq(ContactsContract.CommonDataKinds.Phone.CONTENT_URI),
|
||||||
|
eq(new String[] { ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER }),
|
||||||
|
contains(ContactsContract.Contacts.LOOKUP_KEY),
|
||||||
|
eq(new String[] { "testlookupkey" }), // selection args
|
||||||
|
isNull()); // sort order
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a cursor that points to one item of Contacts data with the specified
|
||||||
|
// columns.
|
||||||
|
private Cursor makeMockCursor(int id, String lookupKey, int starred, int hasPhone) {
|
||||||
|
Cursor mockCursor = mock(Cursor.class);
|
||||||
|
when(mockCursor.moveToFirst()).thenReturn(true);
|
||||||
|
doAnswer(new Answer<Boolean>() {
|
||||||
|
boolean mAccessed = false;
|
||||||
|
@Override
|
||||||
|
public Boolean answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
if (!mAccessed) {
|
||||||
|
mAccessed = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).when(mockCursor).moveToNext();
|
||||||
|
|
||||||
|
// id
|
||||||
|
when(mockCursor.getColumnIndex(ContactsContract.Contacts._ID)).thenReturn(0);
|
||||||
|
when(mockCursor.getInt(0)).thenReturn(id);
|
||||||
|
|
||||||
|
// lookup key
|
||||||
|
when(mockCursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)).thenReturn(1);
|
||||||
|
when(mockCursor.getString(1)).thenReturn(lookupKey);
|
||||||
|
|
||||||
|
// starred
|
||||||
|
when(mockCursor.getColumnIndex(ContactsContract.Contacts.STARRED)).thenReturn(2);
|
||||||
|
when(mockCursor.getInt(2)).thenReturn(starred);
|
||||||
|
|
||||||
|
// has phone number
|
||||||
|
when(mockCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)).thenReturn(3);
|
||||||
|
when(mockCursor.getInt(3)).thenReturn(hasPhone);
|
||||||
|
|
||||||
|
return mockCursor;
|
||||||
|
}
|
||||||
|
|
||||||
private void assertStringArrayEquals(String message, String[] expected, String[] result) {
|
private void assertStringArrayEquals(String message, String[] expected, String[] result) {
|
||||||
String expectedString = Arrays.toString(expected);
|
String expectedString = Arrays.toString(expected);
|
||||||
String resultString = Arrays.toString(result);
|
String resultString = Arrays.toString(result);
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ import android.telephony.TelephonyManager;
|
|||||||
import android.test.suitebuilder.annotation.SmallTest;
|
import android.test.suitebuilder.annotation.SmallTest;
|
||||||
import android.testing.AndroidTestingRunner;
|
import android.testing.AndroidTestingRunner;
|
||||||
import android.testing.TestableLooper;
|
import android.testing.TestableLooper;
|
||||||
|
import android.util.ArraySet;
|
||||||
|
|
||||||
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
|
||||||
import com.android.internal.util.NotificationMessagingUtil;
|
import com.android.internal.util.NotificationMessagingUtil;
|
||||||
import com.android.server.UiServiceTestCase;
|
import com.android.server.UiServiceTestCase;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@@ -72,6 +74,8 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
|
|
||||||
@Mock private TelephonyManager mTelephonyManager;
|
@Mock private TelephonyManager mTelephonyManager;
|
||||||
|
|
||||||
|
private long mTestStartTime;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
@@ -79,6 +83,13 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
|
|
||||||
// for repeat callers / matchesCallFilter
|
// for repeat callers / matchesCallFilter
|
||||||
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
|
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
|
||||||
|
mTestStartTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
// make sure to get rid of any data stored in repeat callers
|
||||||
|
mZenModeFiltering.cleanUpCallersAfter(mTestStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationRecord getNotificationRecord() {
|
private NotificationRecord getNotificationRecord() {
|
||||||
@@ -108,7 +119,10 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
return extras;
|
return extras;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationRecord getNotificationRecordWithPeople(String[] people) {
|
// Create a notification record with the people String array as the
|
||||||
|
// bundled extras, and the numbers ArraySet as additional phone numbers.
|
||||||
|
private NotificationRecord getRecordWithPeopleInfo(String[] people,
|
||||||
|
ArraySet<String> numbers) {
|
||||||
// set up notification record
|
// set up notification record
|
||||||
NotificationRecord r = mock(NotificationRecord.class);
|
NotificationRecord r = mock(NotificationRecord.class);
|
||||||
StatusBarNotification sbn = mock(StatusBarNotification.class);
|
StatusBarNotification sbn = mock(StatusBarNotification.class);
|
||||||
@@ -116,6 +130,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
notification.extras = makeExtrasBundleWithPeople(people);
|
notification.extras = makeExtrasBundleWithPeople(people);
|
||||||
when(sbn.getNotification()).thenReturn(notification);
|
when(sbn.getNotification()).thenReturn(notification);
|
||||||
when(r.getSbn()).thenReturn(sbn);
|
when(r.getSbn()).thenReturn(sbn);
|
||||||
|
when(r.getPhoneNumbers()).thenReturn(numbers);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +354,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
// after calls given an email with an exact string match, make sure that
|
// after calls given an email with an exact string match, make sure that
|
||||||
// matchesCallFilter returns the right thing
|
// matchesCallFilter returns the right thing
|
||||||
String[] mailSource = new String[]{"mailto:hello.world"};
|
String[] mailSource = new String[]{"mailto:hello.world"};
|
||||||
mZenModeFiltering.recordCall(getNotificationRecordWithPeople(mailSource));
|
mZenModeFiltering.recordCall(getRecordWithPeopleInfo(mailSource, null));
|
||||||
|
|
||||||
// set up policy to only allow repeat callers
|
// set up policy to only allow repeat callers
|
||||||
Policy policy = new Policy(
|
Policy policy = new Policy(
|
||||||
@@ -362,7 +377,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
|
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
|
||||||
|
|
||||||
String[] telSource = new String[]{"tel:+1-617-555-1212"};
|
String[] telSource = new String[]{"tel:+1-617-555-1212"};
|
||||||
mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource));
|
mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
|
||||||
|
|
||||||
// set up policy to only allow repeat callers
|
// set up policy to only allow repeat callers
|
||||||
Policy policy = new Policy(
|
Policy policy = new Policy(
|
||||||
@@ -406,7 +421,7 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
|
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
|
||||||
|
|
||||||
String[] telSource = new String[]{"tel:%2B16175551212"};
|
String[] telSource = new String[]{"tel:%2B16175551212"};
|
||||||
mZenModeFiltering.recordCall(getNotificationRecordWithPeople(telSource));
|
mZenModeFiltering.recordCall(getRecordWithPeopleInfo(telSource, null));
|
||||||
|
|
||||||
// set up policy to only allow repeat callers
|
// set up policy to only allow repeat callers
|
||||||
Policy policy = new Policy(
|
Policy policy = new Policy(
|
||||||
@@ -419,25 +434,64 @@ public class ZenModeFilteringTest extends UiServiceTestCase {
|
|||||||
Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
|
Bundle different1 = makeExtrasBundleWithPeople(new String[]{"tel:%2B16175553434"});
|
||||||
Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
|
Bundle different2 = makeExtrasBundleWithPeople(new String[]{"tel:+16175553434"});
|
||||||
|
|
||||||
assertTrue("same number should match",
|
assertTrue("same number 1 should match",
|
||||||
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
policy, UserHandle.SYSTEM,
|
policy, UserHandle.SYSTEM,
|
||||||
same1, null, 0, 0));
|
same1, null, 0, 0));
|
||||||
assertTrue("same number should match",
|
assertTrue("same number 2 should match",
|
||||||
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
policy, UserHandle.SYSTEM,
|
policy, UserHandle.SYSTEM,
|
||||||
same2, null, 0, 0));
|
same2, null, 0, 0));
|
||||||
assertTrue("same number should match",
|
assertTrue("same number 3 should match",
|
||||||
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
policy, UserHandle.SYSTEM,
|
policy, UserHandle.SYSTEM,
|
||||||
same3, null, 0, 0));
|
same3, null, 0, 0));
|
||||||
assertFalse("different number should not match",
|
assertFalse("different number 1 should not match",
|
||||||
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
policy, UserHandle.SYSTEM,
|
policy, UserHandle.SYSTEM,
|
||||||
different1, null, 0, 0));
|
different1, null, 0, 0));
|
||||||
assertFalse("different number should not match",
|
assertFalse("different number 2 should not match",
|
||||||
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
policy, UserHandle.SYSTEM,
|
policy, UserHandle.SYSTEM,
|
||||||
different2, null, 0, 0));
|
different2, null, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchesCallFilter_repeatCallers_viaRecordPhoneNumbers() {
|
||||||
|
// make sure that phone numbers that are passed in via the NotificationRecord's
|
||||||
|
// cached phone numbers field (from a contact lookup if the record is provided a contact
|
||||||
|
// uri) also get recorded in the repeat callers list.
|
||||||
|
|
||||||
|
// set up telephony manager behavior
|
||||||
|
when(mTelephonyManager.getNetworkCountryIso()).thenReturn("us");
|
||||||
|
|
||||||
|
String[] contactSource = new String[]{"content://contacts/lookup/uri-here"};
|
||||||
|
ArraySet<String> contactNumbers = new ArraySet<>(
|
||||||
|
new String[]{"1-617-555-1212", "1-617-555-3434"});
|
||||||
|
NotificationRecord record = getRecordWithPeopleInfo(contactSource, contactNumbers);
|
||||||
|
record.mergePhoneNumbers(contactNumbers);
|
||||||
|
mZenModeFiltering.recordCall(record);
|
||||||
|
|
||||||
|
// set up policy to only allow repeat callers
|
||||||
|
Policy policy = new Policy(
|
||||||
|
PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0, 0, CONVERSATION_SENDERS_NONE);
|
||||||
|
|
||||||
|
// both phone numbers should register here
|
||||||
|
Bundle tel1 = makeExtrasBundleWithPeople(new String[]{"tel:+1-617-555-1212"});
|
||||||
|
Bundle tel2 = makeExtrasBundleWithPeople(new String[]{"tel:16175553434"});
|
||||||
|
Bundle different = makeExtrasBundleWithPeople(new String[]{"tel:16175555656"});
|
||||||
|
|
||||||
|
assertTrue("contact number 1 should match",
|
||||||
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
|
policy, UserHandle.SYSTEM,
|
||||||
|
tel1, null, 0, 0));
|
||||||
|
assertTrue("contact number 2 should match",
|
||||||
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
|
policy, UserHandle.SYSTEM,
|
||||||
|
tel2, null, 0, 0));
|
||||||
|
assertFalse("different number should not match",
|
||||||
|
ZenModeFiltering.matchesCallFilter(mContext, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
|
||||||
|
policy, UserHandle.SYSTEM,
|
||||||
|
different, null, 0, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user