Look up phone numbers associated with a NotificationRecord's contact URI.

Then, when a call comes in, store the additional phone numbers in the NotificationRecord (if any) in the repeat callers map.

This allows us to store those phone numbers in the repeat callers data to confirm whether future calls from that contact's phone are considered "repeat" when a caller passes in the phone number. This matches on any number that belongs to the contact, and not just the one used to give in the phone call (as contact lookup seems not to distinguish).

Bug: 183924362
Bug: 203806689
Test: ValidateNotificationPeopleTest; NotificationRecordTest; ZenModeFilteringTest; manually confirmed that the phone rings audibly when repeat callers is allowed and a contact calls

Change-Id: I83cc7fc036a3962d2468557d41b546f40a3cfbcf
This commit is contained in:
Yuri Lin
2022-01-26 16:11:24 -05:00
parent 37da147fdf
commit fa8fd0d917
6 changed files with 354 additions and 15 deletions

View File

@@ -201,6 +201,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;
@@ -1547,6 +1551,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;

View File

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

View File

@@ -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;
@@ -140,7 +141,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());
} }
/** /**
@@ -351,7 +352,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);
@@ -359,7 +361,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) {
@@ -407,7 +409,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;
@@ -428,6 +431,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) {

View File

@@ -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;
@@ -1304,4 +1305,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"));
}
} }

View File

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

View File

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