diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 146387e761d41..31e0a0613faf2 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -57,6 +57,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.List; /** *

@@ -2210,6 +2211,28 @@ public final class ContactsContract { public static InputStream openContactPhotoInputStream(ContentResolver cr, Uri contactUri) { return openContactPhotoInputStream(cr, contactUri, false); } + + /** + * Creates and returns a corp lookup URI from the given enterprise lookup URI by removing + * {@link #ENTERPRISE_CONTACT_LOOKUP_PREFIX} from the key. Returns {@code null} if the given + * URI is not an enterprise lookup URI. + * + * @hide + */ + @Nullable + public static Uri createCorpLookupUriFromEnterpriseLookupUri( + @NonNull Uri enterpriseLookupUri) { + final List pathSegments = enterpriseLookupUri.getPathSegments(); + if (pathSegments == null || pathSegments.size() <= 2) { + return null; + } + final String key = pathSegments.get(2); + if (TextUtils.isEmpty(key) || !key.startsWith(ENTERPRISE_CONTACT_LOOKUP_PREFIX)) { + return null; + } + final String actualKey = key.substring(ENTERPRISE_CONTACT_LOOKUP_PREFIX.length()); + return Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, actualKey); + } } /** diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java index 90fc59a893a65..f59934fd68108 100644 --- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java +++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java @@ -19,6 +19,7 @@ package com.android.server.notification; import android.annotation.Nullable; import android.app.Notification; import android.app.Person; +import android.content.ContentProvider; import android.content.Context; import android.content.pm.PackageManager; import android.database.ContentObserver; @@ -28,6 +29,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; +import android.os.UserManager; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.Settings; @@ -38,6 +40,8 @@ import android.util.Log; import android.util.LruCache; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; + import libcore.util.EmptyArray; import java.util.ArrayList; @@ -392,26 +396,57 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor { return searchContacts(context, numberUri); } - private LookupResult searchContacts(Context context, Uri lookupUri) { + @VisibleForTesting + LookupResult searchContacts(Context context, Uri lookupUri) { LookupResult lookupResult = new LookupResult(); - Cursor c = null; - try { - c = context.getContentResolver().query(lookupUri, LOOKUP_PROJECTION, null, null, null); + final Uri corpLookupUri = + ContactsContract.Contacts.createCorpLookupUriFromEnterpriseLookupUri(lookupUri); + if (corpLookupUri == null) { + addContacts(lookupResult, context, lookupUri); + } else { + addWorkContacts(lookupResult, context, corpLookupUri); + } + return lookupResult; + } + + private void addWorkContacts(LookupResult lookupResult, Context context, Uri corpLookupUri) { + final int workUserId = findWorkUserId(context); + if (workUserId == -1) { + Slog.w(TAG, "Work profile user ID not found for work contact: " + corpLookupUri); + return; + } + final Uri corpLookupUriWithUserId = + ContentProvider.maybeAddUserId(corpLookupUri, workUserId); + addContacts(lookupResult, context, corpLookupUriWithUserId); + } + + /** Returns the user ID of the managed profile or -1 if none is found. */ + private int findWorkUserId(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + final int[] profileIds = + userManager.getProfileIds(context.getUserId(), /* enabledOnly= */ true); + for (int profileId : profileIds) { + if (userManager.isManagedProfile(profileId)) { + return profileId; + } + } + return -1; + } + + /** Modifies the given lookup result to add contacts found at the given URI. */ + private void addContacts(LookupResult lookupResult, Context context, Uri uri) { + try (Cursor c = context.getContentResolver().query( + uri, LOOKUP_PROJECTION, null, null, null)) { if (c == null) { Slog.w(TAG, "Null cursor from contacts query."); - return lookupResult; + return; } while (c.moveToNext()) { lookupResult.mergeContact(c); } } catch (Throwable t) { Slog.w(TAG, "Problem getting content resolver or performing contacts query.", t); - } finally { - if (c != null) { - c.close(); - } } - return lookupResult; } private static class LookupResult { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java index b1ee1209d83eb..0bf105d62053e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ValidateNotificationPeopleTest.java @@ -16,11 +16,23 @@ package com.android.server.notification; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Notification; import android.app.Person; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; import android.os.Bundle; +import android.os.UserManager; +import android.provider.ContactsContract; import android.test.suitebuilder.annotation.SmallTest; import android.text.SpannableString; @@ -30,6 +42,7 @@ import com.android.server.UiServiceTestCase; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.Arrays; @@ -169,6 +182,64 @@ public class ValidateNotificationPeopleTest extends UiServiceTestCase { assertStringArrayEquals("testPeopleArrayList", expected, result); } + @Test + public void testSearchContacts_workContact_queriesWorkContactProvider() + throws Exception { + final int personalUserId = 0; + final int workUserId = 12; + final int contactId = 12345; + final Context mockContext = mock(Context.class); + when(mockContext.getUserId()).thenReturn(personalUserId); + final UserManager mockUserManager = mock(UserManager.class); + when(mockContext.getSystemService(UserManager.class)).thenReturn(mockUserManager); + when(mockUserManager.getProfileIds(personalUserId, /* enabledOnly= */ true)) + .thenReturn(new int[] {personalUserId, workUserId}); + when(mockUserManager.isManagedProfile(workUserId)).thenReturn(true); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, + ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX + contactId); + + new ValidateNotificationPeople().searchContacts(mockContext, lookupUri); + + ArgumentCaptor queryUri = ArgumentCaptor.forClass(Uri.class); + verify(mockContentResolver).query( + queryUri.capture(), + any(), + /* selection= */ isNull(), + /* selectionArgs= */ isNull(), + /* sortOrder= */ isNull()); + assertEquals(workUserId, ContentProvider.getUserIdFromUri(queryUri.getValue())); + } + + @Test + public void testSearchContacts_personalContact_queriesPersonalContactProvider() + throws Exception { + final int personalUserId = 0; + final int workUserId = 12; + final int contactId = 12345; + final Context mockContext = mock(Context.class); + when(mockContext.getUserId()).thenReturn(personalUserId); + final UserManager mockUserManager = mock(UserManager.class); + when(mockContext.getSystemService(UserManager.class)).thenReturn(mockUserManager); + final ContentResolver mockContentResolver = mock(ContentResolver.class); + when(mockContext.getContentResolver()).thenReturn(mockContentResolver); + final Uri lookupUri = Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_LOOKUP_URI, String.valueOf(contactId)); + + new ValidateNotificationPeople().searchContacts(mockContext, lookupUri); + + ArgumentCaptor queryUri = ArgumentCaptor.forClass(Uri.class); + verify(mockContentResolver).query( + queryUri.capture(), + any(), + /* selection= */ isNull(), + /* selectionArgs= */ isNull(), + /* sortOrder= */ isNull()); + assertFalse(ContentProvider.uriHasUserId(queryUri.getValue())); + } + private void assertStringArrayEquals(String message, String[] expected, String[] result) { String expectedString = Arrays.toString(expected); String resultString = Arrays.toString(result);