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