Loading src/com/android/settings/dashboard/profileselector/UserAdapter.java +3 −1 Original line number Diff line number Diff line Loading @@ -71,7 +71,9 @@ public class UserAdapter extends BaseAdapter { && userInfo.isPrivateProfile())) { mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); if (mIcon != null) { mIcon.setTint(tintColor); } } else { mIcon = UserIcons.getDefaultUserIconInColor(context.getResources(), tintColor); } Loading src/com/android/settings/notification/modes/ZenHelperBackend.java +42 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 <em>directly</em> related to Loading @@ -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) { Loading @@ -66,6 +72,7 @@ class ZenHelperBackend { mContext = context; mInm = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mUserManager = context.getSystemService(UserManager.class); } /** Loading @@ -81,10 +88,12 @@ class ZenHelperBackend { } } /** Returns all conversation channels for profiles of the current user. */ ImmutableList<ConversationChannelWrapper> getAllConversations() { return getConversations(false); } /** Returns all important (priority) conversation channels for profiles of the current user. */ ImmutableList<ConversationChannelWrapper> getImportantConversations() { return getConversations(true); } Loading @@ -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); } } Loading @@ -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<Contact> getAllContacts() { try (Cursor cursor = queryAllContactsData()) { return getContactsFromCursor(cursor); } return getContactsForUserProfiles(this::queryAllContactsData); } /** Returns all starred contacts for profiles of the current user. */ ImmutableList<Contact> getStarredContacts() { try (Cursor cursor = queryStarredContactsData()) { return getContactsFromCursor(cursor); return getContactsForUserProfiles(this::queryStarredContactsData); } private ImmutableList<Contact> getContactsForUserProfiles( Function<UserHandle, Cursor> userQuery) { ImmutableList.Builder<Contact> contacts = new ImmutableList.Builder<>(); for (UserHandle user : mUserManager.getAllProfiles()) { try (Cursor cursor = userQuery.apply(user)) { loadContactsFromCursor(user, cursor, contacts); } } return contacts.build(); } private ImmutableList<Contact> getContactsFromCursor(Cursor cursor) { ImmutableList.Builder<Contact> list = new ImmutableList.Builder<>(); private void loadContactsFromCursor(UserHandle user, Cursor cursor, ImmutableList.Builder<Contact> 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[] { Loading @@ -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); Loading src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java +38 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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<String, SelectorWithWidgetPreference> mOptions = new LinkedHashMap<>(); private final ZenModeSummaryHelper mZenModeSummaryHelper; @Nullable private Dialog mProfileSelectDialog; public ZenModePrioritySendersPreferenceController(Context context, String key, boolean isMessages, ZenModesBackend backend, ZenHelperBackend helperBackend) { Loading @@ -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); } Loading Loading @@ -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<UserHandle> 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() { Loading tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * 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<Integer, FakeContactsProvider> 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<Contact> 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<Contact> 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<Contact> 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<Contact> 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<ContactRow> 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(); } } } tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java +6 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -229,13 +230,13 @@ public final class ZenModePeopleLinkPreferenceControllerTest { private void setUpContacts(Collection<Integer> allIds, Collection<Integer> 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())); } Loading @@ -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 Loading
src/com/android/settings/dashboard/profileselector/UserAdapter.java +3 −1 Original line number Diff line number Diff line Loading @@ -71,7 +71,9 @@ public class UserAdapter extends BaseAdapter { && userInfo.isPrivateProfile())) { mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); if (mIcon != null) { mIcon.setTint(tintColor); } } else { mIcon = UserIcons.getDefaultUserIconInColor(context.getResources(), tintColor); } Loading
src/com/android/settings/notification/modes/ZenHelperBackend.java +42 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 <em>directly</em> related to Loading @@ -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) { Loading @@ -66,6 +72,7 @@ class ZenHelperBackend { mContext = context; mInm = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mUserManager = context.getSystemService(UserManager.class); } /** Loading @@ -81,10 +88,12 @@ class ZenHelperBackend { } } /** Returns all conversation channels for profiles of the current user. */ ImmutableList<ConversationChannelWrapper> getAllConversations() { return getConversations(false); } /** Returns all important (priority) conversation channels for profiles of the current user. */ ImmutableList<ConversationChannelWrapper> getImportantConversations() { return getConversations(true); } Loading @@ -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); } } Loading @@ -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<Contact> getAllContacts() { try (Cursor cursor = queryAllContactsData()) { return getContactsFromCursor(cursor); } return getContactsForUserProfiles(this::queryAllContactsData); } /** Returns all starred contacts for profiles of the current user. */ ImmutableList<Contact> getStarredContacts() { try (Cursor cursor = queryStarredContactsData()) { return getContactsFromCursor(cursor); return getContactsForUserProfiles(this::queryStarredContactsData); } private ImmutableList<Contact> getContactsForUserProfiles( Function<UserHandle, Cursor> userQuery) { ImmutableList.Builder<Contact> contacts = new ImmutableList.Builder<>(); for (UserHandle user : mUserManager.getAllProfiles()) { try (Cursor cursor = userQuery.apply(user)) { loadContactsFromCursor(user, cursor, contacts); } } return contacts.build(); } private ImmutableList<Contact> getContactsFromCursor(Cursor cursor) { ImmutableList.Builder<Contact> list = new ImmutableList.Builder<>(); private void loadContactsFromCursor(UserHandle user, Cursor cursor, ImmutableList.Builder<Contact> 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[] { Loading @@ -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); Loading
src/com/android/settings/notification/modes/ZenModePrioritySendersPreferenceController.java +38 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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<String, SelectorWithWidgetPreference> mOptions = new LinkedHashMap<>(); private final ZenModeSummaryHelper mZenModeSummaryHelper; @Nullable private Dialog mProfileSelectDialog; public ZenModePrioritySendersPreferenceController(Context context, String key, boolean isMessages, ZenModesBackend backend, ZenHelperBackend helperBackend) { Loading @@ -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); } Loading Loading @@ -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<UserHandle> 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() { Loading
tests/robotests/src/com/android/settings/notification/modes/ZenHelperBackendTest.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * 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<Integer, FakeContactsProvider> 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<Contact> 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<Contact> 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<Contact> 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<Contact> 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<ContactRow> 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(); } } }
tests/robotests/src/com/android/settings/notification/modes/ZenModePeopleLinkPreferenceControllerTest.java +6 −5 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -229,13 +230,13 @@ public final class ZenModePeopleLinkPreferenceControllerTest { private void setUpContacts(Collection<Integer> allIds, Collection<Integer> 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())); } Loading @@ -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