Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7be2f12 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Better Support for profiles in "People that can interrupt"" into main

parents 7aabe039 504e9271
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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);
            }
+42 −17
Original line number Diff line number Diff line
@@ -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 <em>directly</em> 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<ConversationChannelWrapper> getAllConversations() {
        return getConversations(false);
    }

    /** Returns all important (priority) conversation channels for profiles of the current user. */
    ImmutableList<ConversationChannelWrapper> 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<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[] {
@@ -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);
+38 −14
Original line number Diff line number Diff line
@@ -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<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) {
@@ -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<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() {
+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();
        }
    }
}
+6 −5
Original line number Diff line number Diff line
@@ -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<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()));
    }

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