From 504e927168f1cf246cac48dfa11cfe27bc430f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Hern=C3=A1ndez?= Date: Fri, 4 Oct 2024 17:34:28 +0200 Subject: [PATCH] Better Support for profiles in "People that can interrupt" * Show contacts from personal and work profile. * Open personal or work profile Contacts app when choosing settings. * Skip conversations with no ShortcutInfo (they are returned for a disabled work profile but we cannot show an icon for them). Fixes: 371513451 Test: atest com.android.settings.notification.modes Flag: android.app.modes_ui Change-Id: Id8653a85ee4fd15dfccbecb3ea2d31e615d29f8c --- .../profileselector/UserAdapter.java | 4 +- .../notification/modes/ZenHelperBackend.java | 61 ++-- ...dePrioritySendersPreferenceController.java | 52 +++- .../modes/ZenHelperBackendTest.java | 260 ++++++++++++++++++ ...odePeopleLinkPreferenceControllerTest.java | 11 +- ...ioritySendersPreferenceControllerTest.java | 172 +++++++++++- 6 files changed, 520 insertions(+), 40 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java diff --git a/src/com/android/settings/dashboard/profileselector/UserAdapter.java b/src/com/android/settings/dashboard/profileselector/UserAdapter.java index 0fefa2f246e..54887d7b732 100644 --- a/src/com/android/settings/dashboard/profileselector/UserAdapter.java +++ b/src/com/android/settings/dashboard/profileselector/UserAdapter.java @@ -71,7 +71,9 @@ public class UserAdapter extends BaseAdapter { && userInfo.isPrivateProfile())) { mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); - mIcon.setTint(tintColor); + if (mIcon != null) { + mIcon.setTint(tintColor); + } } else { mIcon = UserIcons.getDefaultUserIconInColor(context.getResources(), tintColor); } diff --git a/src/com/android/settings/notification/modes/ZenHelperBackend.java b/src/com/android/settings/notification/modes/ZenHelperBackend.java index bf9167873cd..31c1ce40b19 100644 --- a/src/com/android/settings/notification/modes/ZenHelperBackend.java +++ b/src/com/android/settings/notification/modes/ZenHelperBackend.java @@ -18,13 +18,17 @@ package com.android.settings.notification.modes; import android.annotation.Nullable; import android.app.INotificationManager; +import android.content.ContentProvider; import android.content.Context; import android.content.pm.ParceledListSlice; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; import android.service.notification.ConversationChannelWrapper; import android.util.Log; @@ -39,6 +43,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; /** * Class used for Settings-system_server interactions that are not directly related to @@ -54,6 +59,7 @@ class ZenHelperBackend { private final Context mContext; private final INotificationManager mInm; + private final UserManager mUserManager; static ZenHelperBackend getInstance(Context context) { if (sInstance == null) { @@ -66,6 +72,7 @@ class ZenHelperBackend { mContext = context; mInm = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mUserManager = context.getSystemService(UserManager.class); } /** @@ -81,10 +88,12 @@ class ZenHelperBackend { } } + /** Returns all conversation channels for profiles of the current user. */ ImmutableList getAllConversations() { return getConversations(false); } + /** Returns all important (priority) conversation channels for profiles of the current user. */ ImmutableList getImportantConversations() { return getConversations(true); } @@ -97,7 +106,9 @@ class ZenHelperBackend { onlyImportant); if (parceledList != null) { for (ConversationChannelWrapper conversation : parceledList.getList()) { - if (!conversation.getNotificationChannel().isDemoted()) { + if (conversation.getShortcutInfo() != null + && conversation.getNotificationChannel() != null + && !conversation.getNotificationChannel().isDemoted()) { list.add(conversation); } } @@ -109,38 +120,52 @@ class ZenHelperBackend { } } - record Contact(long id, @Nullable String displayName, @Nullable Uri photoUri) { } + record Contact(UserHandle user, long contactId, @Nullable String displayName, + @Nullable Uri photoUri) { } + /** Returns all contacts for profiles of the current user. */ ImmutableList getAllContacts() { - try (Cursor cursor = queryAllContactsData()) { - return getContactsFromCursor(cursor); - } + return getContactsForUserProfiles(this::queryAllContactsData); } + /** Returns all starred contacts for profiles of the current user. */ ImmutableList getStarredContacts() { - try (Cursor cursor = queryStarredContactsData()) { - return getContactsFromCursor(cursor); - } + return getContactsForUserProfiles(this::queryStarredContactsData); } - private ImmutableList getContactsFromCursor(Cursor cursor) { - ImmutableList.Builder list = new ImmutableList.Builder<>(); + private ImmutableList getContactsForUserProfiles( + Function userQuery) { + ImmutableList.Builder contacts = new ImmutableList.Builder<>(); + for (UserHandle user : mUserManager.getAllProfiles()) { + try (Cursor cursor = userQuery.apply(user)) { + loadContactsFromCursor(user, cursor, contacts); + } + } + return contacts.build(); + } + + private void loadContactsFromCursor(UserHandle user, Cursor cursor, + ImmutableList.Builder contactsListBuilder) { if (cursor != null && cursor.moveToFirst()) { do { long id = cursor.getLong(0); String name = Strings.emptyToNull(cursor.getString(1)); String photoUriStr = cursor.getString(2); Uri photoUri = !Strings.isNullOrEmpty(photoUriStr) ? Uri.parse(photoUriStr) : null; - list.add(new Contact(id, name, photoUri)); + contactsListBuilder.add(new Contact(user, id, name, + ContentProvider.maybeAddUserId(photoUri, user.getIdentifier()))); } while (cursor.moveToNext()); } - return list.build(); } int getAllContactsCount() { - try (Cursor cursor = queryAllContactsData()) { - return cursor != null ? cursor.getCount() : 0; + int count = 0; + for (UserHandle user : mUserManager.getEnabledProfiles()) { + try (Cursor cursor = queryAllContactsData(user)) { + count += (cursor != null ? cursor.getCount() : 0); + } } + return count; } private static final String[] CONTACTS_PROJECTION = new String[] { @@ -149,17 +174,17 @@ class ZenHelperBackend { ContactsContract.Contacts.PHOTO_THUMBNAIL_URI }; - private Cursor queryStarredContactsData() { + private Cursor queryStarredContactsData(UserHandle user) { return mContext.getContentResolver().query( - ContactsContract.Contacts.CONTENT_URI, + ContentProvider.maybeAddUserId(Contacts.CONTENT_URI, user.getIdentifier()), CONTACTS_PROJECTION, /* selection= */ ContactsContract.Data.STARRED + "=1", /* selectionArgs= */ null, /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY); } - private Cursor queryAllContactsData() { + private Cursor queryAllContactsData(UserHandle user) { return mContext.getContentResolver().query( - ContactsContract.Contacts.CONTENT_URI, + ContentProvider.maybeAddUserId(Contacts.CONTENT_URI, user.getIdentifier()), CONTACTS_PROJECTION, /* selection= */ null, /* selectionArgs= */ null, /* sortOrder= */ ContactsContract.Contacts.DISPLAY_NAME_PRIMARY); diff --git a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java index 3b9311defe8..2a7ec903bb7 100644 --- a/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java @@ -28,11 +28,14 @@ import static android.service.notification.ZenPolicy.PEOPLE_TYPE_UNSET; import static com.google.common.base.Preconditions.checkNotNull; +import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.icu.text.MessageFormat; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.Contacts; import android.service.notification.ZenPolicy; import android.view.View; @@ -46,6 +49,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.notification.app.ConversationListSettings; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; @@ -55,6 +59,7 @@ import com.google.common.collect.ImmutableSet; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; @@ -87,16 +92,18 @@ class ZenModePrioritySendersPreferenceController private static final Intent STARRED_CONTACTS_INTENT = new Intent(Contacts.Intents.UI.LIST_STARRED_ACTION) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - private static final Intent FALLBACK_INTENT = new Intent(Intent.ACTION_MAIN) + private static final Intent FALLBACK_CONTACTS_INTENT = new Intent(Intent.ACTION_MAIN) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); private final ZenHelperBackend mHelperBackend; + private final UserManager mUserManager; private final PackageManager mPackageManager; private PreferenceCategory mPreferenceCategory; private final LinkedHashMap mOptions = new LinkedHashMap<>(); private final ZenModeSummaryHelper mZenModeSummaryHelper; + @Nullable private Dialog mProfileSelectDialog; public ZenModePrioritySendersPreferenceController(Context context, String key, boolean isMessages, ZenModesBackend backend, ZenHelperBackend helperBackend) { @@ -107,11 +114,12 @@ class ZenModePrioritySendersPreferenceController String contactsPackage = context.getString(R.string.config_contacts_package_name); ALL_CONTACTS_INTENT.setPackage(contactsPackage); STARRED_CONTACTS_INTENT.setPackage(contactsPackage); - FALLBACK_INTENT.setPackage(contactsPackage); + FALLBACK_CONTACTS_INTENT.setPackage(contactsPackage); + mUserManager = mContext.getSystemService(UserManager.class); mPackageManager = mContext.getPackageManager(); - if (!FALLBACK_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) { - FALLBACK_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS); + if (!FALLBACK_CONTACTS_INTENT.hasCategory(Intent.CATEGORY_APP_CONTACTS)) { + FALLBACK_CONTACTS_INTENT.addCategory(Intent.CATEGORY_APP_CONTACTS); } mZenModeSummaryHelper = new ZenModeSummaryHelper(mContext, mHelperBackend); } @@ -270,32 +278,48 @@ class ZenModePrioritySendersPreferenceController } return v -> { - if (KEY_STARRED.equals(key) - && STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { - mContext.startActivity(STARRED_CONTACTS_INTENT); - } else if (KEY_CONTACTS.equals(key) - && ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null) { - mContext.startActivity(ALL_CONTACTS_INTENT); + if (KEY_STARRED.equals(key)) { + startContactsActivity(STARRED_CONTACTS_INTENT); + } else if (KEY_CONTACTS.equals(key)) { + startContactsActivity(ALL_CONTACTS_INTENT); } else if (KEY_ANY_CONVERSATIONS.equals(key) || KEY_IMPORTANT_CONVERSATIONS.equals(key)) { new SubSettingLauncher(mContext) .setDestination(ConversationListSettings.class.getName()) .setSourceMetricsCategory(SettingsEnums.DND_MESSAGES) .launch(); - } else { - mContext.startActivity(FALLBACK_INTENT); } }; } + private void startContactsActivity(Intent preferredIntent) { + Intent intent = preferredIntent.resolveActivity(mPackageManager) != null + ? preferredIntent : FALLBACK_CONTACTS_INTENT; + + List userProfiles = mUserManager.getEnabledProfiles(); + if (userProfiles.size() <= 1) { + mContext.startActivity(intent); + } + + mProfileSelectDialog = ProfileSelectDialog.createDialog(mContext, userProfiles, + position -> { + mContext.startActivityAsUser(intent, userProfiles.get(position)); + if (mProfileSelectDialog != null) { + mProfileSelectDialog.dismiss(); + mProfileSelectDialog = null; + } + }); + mProfileSelectDialog.show(); + } + private boolean isStarredIntentValid() { return STARRED_CONTACTS_INTENT.resolveActivity(mPackageManager) != null - || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; + || FALLBACK_CONTACTS_INTENT.resolveActivity(mPackageManager) != null; } private boolean isContactsIntentValid() { return ALL_CONTACTS_INTENT.resolveActivity(mPackageManager) != null - || FALLBACK_INTENT.resolveActivity(mPackageManager) != null; + || FALLBACK_CONTACTS_INTENT.resolveActivity(mPackageManager) != null; } void updateSummaries() { diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java new file mode 100644 index 00000000000..2288099c26e --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.notification.modes; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.app.Flags; +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.content.pm.UserInfo; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.ContactsContract; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.notification.modes.ZenHelperBackend.Contact; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +@RunWith(RobolectricTestRunner.class) +@EnableFlags(Flags.FLAG_MODES_UI) +public class ZenHelperBackendTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private Context mContext; + private ZenHelperBackend mBackend; + private HashMap mContactsProviders = new HashMap<>(); + + private int mUserId; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.getApplication(); + mBackend = new ZenHelperBackend(mContext); + + mUserId = mContext.getUserId(); + addContactsProvider(mUserId); + } + + private int addMainUserProfile() { + UserInfo workProfile = new UserInfo(mUserId + 10, "Work Profile", 0); + workProfile.userType = UserManager.USER_TYPE_PROFILE_MANAGED; + UserManager userManager = mContext.getSystemService(UserManager.class); + shadowOf(userManager).addProfile(mUserId, workProfile.id, workProfile); + + addContactsProvider(workProfile.id); + + return workProfile.id; + } + + private void addContactsProvider(int userId) { + ProviderInfo providerInfo = new ProviderInfo(); + providerInfo.authority = String.format("%s@%s", userId, ContactsContract.AUTHORITY); + mContactsProviders.put(userId, Robolectric.buildContentProvider(FakeContactsProvider.class) + .create(providerInfo).get()); + } + + private void addContact(int userId, String name, boolean starred) { + mContactsProviders.get(userId).addContact(name, starred); + } + + @Test + public void getAllContacts_singleProfile() { + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + + ImmutableList allContacts = mBackend.getAllContacts(); + + assertThat(allContacts).containsExactly( + new Contact(UserHandle.of(mUserId), 1, "Huey", null), + new Contact(UserHandle.of(mUserId), 2, "Dewey", null), + new Contact(UserHandle.of(mUserId), 3, "Louie", null)); + } + + @Test + public void getAllContacts_multipleProfiles() { + int profileId = addMainUserProfile(); + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + addContact(profileId, "Fry", false); + addContact(profileId, "Bender", true); + + ImmutableList allContacts = mBackend.getAllContacts(); + + assertThat(allContacts).containsExactly( + new Contact(UserHandle.of(mUserId), 1, "Huey", null), + new Contact(UserHandle.of(mUserId), 2, "Dewey", null), + new Contact(UserHandle.of(mUserId), 3, "Louie", null), + new Contact(UserHandle.of(profileId), 1, "Fry", null), + new Contact(UserHandle.of(profileId), 2, "Bender", null)); + } + + @Test + public void getStarredContacts_singleProfile() { + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + + ImmutableList allContacts = mBackend.getStarredContacts(); + + assertThat(allContacts).containsExactly( + new Contact(UserHandle.of(mUserId), 2, "Dewey", null)); + } + + @Test + public void getStarredContacts_multipleProfiles() { + int profileId = addMainUserProfile(); + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + addContact(profileId, "Fry", false); + addContact(profileId, "Bender", true); + + ImmutableList allContacts = mBackend.getStarredContacts(); + + assertThat(allContacts).containsExactly( + new Contact(UserHandle.of(mUserId), 2, "Dewey", null), + new Contact(UserHandle.of(profileId), 2, "Bender", null)); + } + + @Test + public void getAllContactsCount_singleProfile() { + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + + assertThat(mBackend.getAllContactsCount()).isEqualTo(3); + } + + @Test + public void getAllContactsCount_multipleProfiles() { + int profileId = addMainUserProfile(); + addContact(mUserId, "Huey", false); + addContact(mUserId, "Dewey", true); + addContact(mUserId, "Louie", false); + addContact(profileId, "Fry", false); + addContact(profileId, "Bender", true); + + assertThat(mBackend.getAllContactsCount()).isEqualTo(5); + } + + private static class FakeContactsProvider extends ContentProvider { + + private record ContactRow(int id, String name, boolean starred) {} + + private final ArrayList mContacts = new ArrayList<>(); + + FakeContactsProvider() { + } + + @Override + public boolean onCreate() { + return true; + } + + public int addContact(String name, boolean starred) { + mContacts.add(new ContactRow(mContacts.size() + 1, name, starred)); + return mContacts.size(); + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + Uri baseUri = ContentProvider.getUriWithoutUserId(uri); + if (!ContactsContract.Contacts.CONTENT_URI.equals(baseUri)) { + throw new IllegalArgumentException("Unsupported uri for fake: " + uri); + } + + if (projection == null || !Iterables.elementsEqual(ImmutableList.copyOf(projection), + ImmutableList.of(ContactsContract.Contacts._ID, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, + ContactsContract.Contacts.PHOTO_THUMBNAIL_URI))) { + throw new IllegalArgumentException( + "Unsupported projection for fake: " + Arrays.toString(projection)); + } + + if (selection != null && !selection.equals(ContactsContract.Data.STARRED + "=1")) { + throw new IllegalArgumentException("Unsupported selection for fake: " + selection); + } + boolean selectingStarred = selection != null; // Checked as only valid selection above + + + MatrixCursor cursor = new MatrixCursor(projection); + for (ContactRow contactRow : mContacts) { + if (!selectingStarred || contactRow.starred) { + cursor.addRow(ImmutableList.of(contactRow.id, contactRow.name, Uri.EMPTY)); + } + } + + return cursor; + } + + @Override + @Nullable + public String getType(@NonNull Uri uri) { + return ""; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + throw new UnsupportedOperationException(); + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, + @Nullable String selection, @Nullable String[] selectionArgs) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java index 85fd0043039..8a66253e589 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java @@ -43,6 +43,7 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.ConversationChannelWrapper; @@ -229,13 +230,13 @@ public final class ZenModePeopleLinkPreferenceControllerTest { private void setUpContacts(Collection allIds, Collection starredIds) { when(mHelperBackend.getAllContacts()).thenReturn(ImmutableList.copyOf( - allIds.stream() - .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id))) + allIds.stream().map(id -> new Contact(UserHandle.SYSTEM, id, "#" + id, + Uri.parse("photo://" + id))) .toList())); when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.copyOf( - starredIds.stream() - .map(id -> new Contact(id, "#" + id, Uri.parse("photo://" + id))) + starredIds.stream().map(id -> new Contact(UserHandle.SYSTEM, id, "#" + id, + Uri.parse("photo://" + id))) .toList())); } @@ -253,6 +254,6 @@ public final class ZenModePeopleLinkPreferenceControllerTest { } private static ColorDrawable photoOf(Contact contact) { - return new ColorDrawable((int) contact.id()); + return new ColorDrawable((int) contact.contactId()); } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java index eb570947d88..486a880588a 100644 --- a/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceControllerTest.java @@ -39,21 +39,41 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; +import android.app.Activity; +import android.app.Dialog; import android.app.Flags; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Contacts; import android.service.notification.ZenPolicy; import android.service.notification.ZenPolicy.ConversationSenders; import android.service.notification.ZenPolicy.PeopleType; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.fragment.app.testing.EmptyFragmentActivity; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; import androidx.preference.TwoStatePreference; +import androidx.test.core.content.pm.PackageInfoBuilder; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import com.android.settings.R; import com.android.settingslib.notification.modes.TestModeBuilder; import com.android.settingslib.notification.modes.ZenMode; import com.android.settingslib.notification.modes.ZenModesBackend; @@ -70,6 +90,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowDialog; import java.util.function.Consumer; import java.util.function.Predicate; @@ -84,7 +105,13 @@ public final class ZenModePrioritySendersPreferenceControllerTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public ActivityScenarioRule mActivityScenario = + new ActivityScenarioRule<>(EmptyFragmentActivity.class); + private Context mContext; + private Activity mActivity; + private PackageManager mPackageManager; @Mock private ZenModesBackend mBackend; @Mock private ZenHelperBackend mHelperBackend; @@ -97,6 +124,9 @@ public final class ZenModePrioritySendersPreferenceControllerTest { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; + mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); + mActivityScenario.getScenario().onActivity(activity -> mActivity = activity); + mPackageManager = mContext.getPackageManager(); mMessagesController = new ZenModePrioritySendersPreferenceController(mContext, "messages", true, mBackend, mHelperBackend); @@ -114,8 +144,8 @@ public final class ZenModePrioritySendersPreferenceControllerTest { mPreferenceScreen.addPreference(mMessagesPrefCategory); when(mHelperBackend.getStarredContacts()).thenReturn(ImmutableList.of()); - when(mHelperBackend.getAllContacts()).thenReturn( - ImmutableList.of(new ZenHelperBackend.Contact(1, "The only contact", null))); + when(mHelperBackend.getAllContacts()).thenReturn(ImmutableList.of( + new ZenHelperBackend.Contact(UserHandle.SYSTEM, 1, "The only contact", null))); when(mHelperBackend.getAllContactsCount()).thenReturn(1); when(mHelperBackend.getImportantConversations()).thenReturn(ImmutableList.of()); @@ -1439,4 +1469,142 @@ public final class ZenModePrioritySendersPreferenceControllerTest { assertThat(captor.getValue().getPolicy().getPriorityCallSenders()) .isEqualTo(PEOPLE_TYPE_NONE); } + + @Test + public void displayPreference_hasContactsApp_hasSettingsButton() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + setUpContactsApp(contactsPackage, /* withPreciseIntents= */ false); + mCallsController.displayPreference(mPreferenceScreen); + + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_STARRED); + + assertThat(((View) contactsPref.getExtraWidget().getParent()).getVisibility()).isEqualTo( + View.VISIBLE); + } + + @Test + public void displayPreference_noContactsApp_noSettingsButton() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + shadowOf(mPackageManager).removePackage(contactsPackage); + mCallsController.displayPreference(mPreferenceScreen); + + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_STARRED); + + assertThat(((View) contactsPref.getExtraWidget().getParent()).getVisibility()).isEqualTo( + View.GONE); + } + + @Test + public void contactsSettingsClick_usesBestIntent() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + setUpContactsApp(contactsPackage, /* withPreciseIntents= */ true); + + mCallsController.displayPreference(mPreferenceScreen); + mCallsController.updateZenMode(mCallsPrefCategory, TestModeBuilder.EXAMPLE); + + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_CONTACTS); + contactsPref.getExtraWidget().performClick(); + + Intent nextActivity = shadowOf(mActivity).getNextStartedActivity(); + assertThat(nextActivity).isNotNull(); + assertThat(nextActivity.getPackage()).isEqualTo(contactsPackage); + assertThat(nextActivity.getAction()).isEqualTo(Contacts.Intents.UI.LIST_DEFAULT); + } + + @Test + public void starredContactsSettingsClick_usesBestIntent() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + setUpContactsApp(contactsPackage, /* withPreciseIntents= */ true); + + mCallsController.displayPreference(mPreferenceScreen); + mCallsController.updateZenMode(mCallsPrefCategory, TestModeBuilder.EXAMPLE); + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_STARRED); + + contactsPref.getExtraWidget().performClick(); + + Intent nextActivity = shadowOf(mActivity).getNextStartedActivity(); + assertThat(nextActivity).isNotNull(); + assertThat(nextActivity.getPackage()).isEqualTo(contactsPackage); + assertThat(nextActivity.getAction()).isEqualTo(Contacts.Intents.UI.LIST_STARRED_ACTION); + } + + @Test + public void contactsSettingsClick_usesFallbackIntent() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + setUpContactsApp(contactsPackage, /* withPreciseIntents= */ false); + + mCallsController.displayPreference(mPreferenceScreen); + mCallsController.updateZenMode(mCallsPrefCategory, TestModeBuilder.EXAMPLE); + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_CONTACTS); + + contactsPref.getExtraWidget().performClick(); + + Intent nextActivity = shadowOf(mActivity).getNextStartedActivity(); + assertThat(nextActivity).isNotNull(); + assertThat(nextActivity.getPackage()).isEqualTo(contactsPackage); + assertThat(nextActivity.getAction()).isEqualTo(Intent.ACTION_MAIN); + } + + @Test + public void contactsSettingsClick_multipleProfiles_showsProfileChooserDialog() { + String contactsPackage = mContext.getString(R.string.config_contacts_package_name); + setUpContactsApp(contactsPackage, /* withPreciseIntents= */ true); + + UserInfo workProfile = new UserInfo(mContext.getUserId() + 10, "Work Profile", 0); + workProfile.userType = UserManager.USER_TYPE_PROFILE_MANAGED; + UserManager userManager = mContext.getSystemService(UserManager.class); + shadowOf(userManager).addProfile(mContext.getUserId(), workProfile.id, workProfile); + + mCallsController.displayPreference(mPreferenceScreen); + mCallsController.updateZenMode(mCallsPrefCategory, TestModeBuilder.EXAMPLE); + SelectorWithWidgetPreference contactsPref = getBoundSelectorPreference(KEY_CONTACTS); + + contactsPref.getExtraWidget().performClick(); + + Dialog profileSelectDialog = ShadowDialog.getLatestDialog(); + assertThat(profileSelectDialog).isNotNull(); + TextView dialogTitle = profileSelectDialog.findViewById(android.R.id.title); + assertThat(dialogTitle.getText().toString()).isEqualTo("Choose profile"); + } + + private void setUpContactsApp(String contactsPackage, boolean withPreciseIntents) { + ComponentName contactsActivity = new ComponentName(contactsPackage, "ContactsActivity"); + shadowOf(mPackageManager).installPackage( + PackageInfoBuilder.newBuilder() + .setPackageName(contactsPackage) + .build()); + shadowOf(mPackageManager).addActivityIfNotPresent(contactsActivity); + + // Fallback / default intent filter. + IntentFilter mainFilter = new IntentFilter(Intent.ACTION_MAIN); + mainFilter.addCategory(Intent.CATEGORY_DEFAULT); + mainFilter.addCategory(Intent.CATEGORY_APP_CONTACTS); + shadowOf(mPackageManager).addIntentFilterForActivity(contactsActivity, mainFilter); + + if (withPreciseIntents) { + IntentFilter listFilter = new IntentFilter(Contacts.Intents.UI.LIST_DEFAULT); + listFilter.addCategory(Intent.CATEGORY_DEFAULT); + shadowOf(mPackageManager).addIntentFilterForActivity(contactsActivity, listFilter); + + IntentFilter starredFilter = new IntentFilter(Contacts.Intents.UI.LIST_STARRED_ACTION); + starredFilter.addCategory(Intent.CATEGORY_DEFAULT); + shadowOf(mPackageManager).addIntentFilterForActivity(contactsActivity, starredFilter); + } + } + + private SelectorWithWidgetPreference getBoundSelectorPreference(String key) { + SelectorWithWidgetPreference selectorPref = checkNotNull( + mCallsPrefCategory.findPreference(key)); + + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(selectorPref.getLayoutResource(), null); + LinearLayout widgetView = view.findViewById(android.R.id.widget_frame); + assertThat(widgetView).isNotNull(); + inflater.inflate(selectorPref.getWidgetLayoutResource(), widgetView, true); + + PreferenceViewHolder viewHolder = PreferenceViewHolder.createInstanceForTests(view); + selectorPref.onBindViewHolder(viewHolder); + + return selectorPref; + } } \ No newline at end of file