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

Commit 3532044b authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Use collapsable preferences for channel lists

And replace AsyncTask, which is deprecated, with futures

Test: ShowMorePreferenceControllerTest
Test: ChannelListPreferenceControllerTest
Flag: EXEMPT bug fix
Bug: 409751384

Change-Id: I53fcee0a5a870a45c96c201603b7401ce660e349
parent 52e9b923
Loading
Loading
Loading
Loading
+1 −6
Original line number Diff line number Diff line
@@ -49,12 +49,7 @@
    <!-- Channels/Channel groups added here -->
    <PreferenceCategory
        android:key="channels"
        android:layout="@layout/empty_view" />

    <Preference
        android:key="more"
        android:title="@string/no_recent_channels"
        android:icon="@drawable/ic_expand"/>
        android:title="@string/notification_channels" />

    <PreferenceCategory
        android:key="pre_channels_fields"
+5 −5
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 * Copyright (C) 2025 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.
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.settings.notification.modes;
package com.android.settings.notification;

import android.util.Log;

@@ -28,11 +28,11 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

class FutureUtil {
public class FutureUtil {

    private static final String TAG = "ZenFutureUtil";
    private static final String TAG = "FutureUtil";

    static <V> void whenDone(ListenableFuture<V> future, Consumer<V> consumer, Executor executor) {
    public static <V> void whenDone(ListenableFuture<V> future, Consumer<V> consumer, Executor executor) {
        whenDone(future, consumer, executor, "Error in future");
    }

+1 −1
Original line number Diff line number Diff line
@@ -865,7 +865,7 @@ public class NotificationBackend {
        public int userId;
        public int blockedChannelCount;
        public int channelCount;
        public Map<String, NotificationsSentState> sentByChannel;
        public Map<String, NotificationsSentState> sentByChannel = new HashMap<>();
        public NotificationsSentState sentByApp;
        public boolean showAllChannels = true;
        public boolean canBePromoted;
+2 −3
Original line number Diff line number Diff line
@@ -122,14 +122,13 @@ public class AppNotificationSettings extends NotificationSettings {
                mBackend));
        mControllers.add(new DndPreferenceController(context, mBackend));
        mControllers.add(new AppLinkPreferenceController(context));
        mControllers.add(new ChannelListPreferenceController(context, mBackend));
        mControllers.add(new ChannelListPreferenceController(
                context, mDependentFieldListener, mBackend));
        mControllers.add(new AppConversationListPreferenceController(context, mBackend));
        mControllers.add(new InvalidConversationInfoPreferenceController(context, mBackend));
        mControllers.add(new InvalidConversationPreferenceController(context, mBackend));
        mControllers.add(new BubbleSummaryPreferenceController(context, mBackend));
        mControllers.add(new NotificationsOffPreferenceController(context));
        mControllers.add(new ShowMorePreferenceController(
                context, mDependentFieldListener, mBackend));
        mControllers.add(new BundleListPreferenceController(context, mBackend));
        mControllers.add(new PromotedNotificationsPreferenceController(context, mBackend));
        mControllers.add(new AdjustmentKeyPreferenceController(context, mBackend,
+93 −68
Original line number Diff line number Diff line
@@ -24,13 +24,13 @@ import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
@@ -40,13 +40,21 @@ import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.FutureUtil;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.ExpandablePreference;

import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

public class ChannelListPreferenceController extends NotificationPreferenceController {

@@ -54,13 +62,30 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
    private static final String KEY_GENERAL_CATEGORY = "categories";
    private static final String KEY_ZERO_CATEGORIES = "zeroCategories";
    public static final String ARG_FROM_SETTINGS = "fromSettings";
    public static final String KEY_SHOW_MORE = "show_more";

    private List<NotificationChannelGroup> mChannelGroupList;
    private PreferenceCategory mPreference;
    int mChannelCount;

    public ChannelListPreferenceController(Context context, NotificationBackend backend) {
    private int mChannelCount;
    private int mVisibleChannelCount;
    private final NotificationSettings.DependentFieldListener mDependentFieldListener;
    private ListeningExecutorService mBackgroundExecutor;
    private Executor mUiExecutor;

    public ChannelListPreferenceController(Context context,
            NotificationSettings.DependentFieldListener dependentFieldListener,
            NotificationBackend backend) {
        super(context, backend);
        mDependentFieldListener = dependentFieldListener;
        mBackgroundExecutor = ThreadUtils.getBackgroundExecutor();
        mUiExecutor = context.getMainExecutor();
    }

    @VisibleForTesting
    void setExecutors(ListeningExecutorService backgroundExecutor,
            Executor uiExecutor) {
        mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor);
        mUiExecutor = uiExecutor;
    }

    @Override
@@ -93,68 +118,69 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
    @Override
    public void updateState(Preference preference) {
        mPreference = (PreferenceCategory) preference;
        // Load channel settings
        new AsyncTask<Void, Void, Void>() {
        FutureUtil.whenDone(mBackgroundExecutor.submit(this::loadChannels),
                new Consumer<>() {
                    @Override
            protected Void doInBackground(Void... unused) {
                    public void accept(Boolean aBoolean) {
                        updateFullList();
                    }
                },
                mUiExecutor);
    }

    private boolean loadChannels() {
        mChannelCount = mBackend.getChannelCount(mAppRow.pkg, mAppRow.uid);
        if (mAppRow.showAllChannels) {
            mChannelGroupList = mBackend.getGroups(mAppRow.pkg, mAppRow.uid).getList();
            mVisibleChannelCount = mChannelCount;
        } else {
            mChannelGroupList = mBackend.getGroupsWithRecentBlockedFilter(mAppRow.pkg,
                    mAppRow.uid).getList();
            mVisibleChannelCount = 0;
            for (NotificationChannelGroup group : mChannelGroupList) {
                    mVisibleChannelCount += group.getChannels().size();
            }

                mChannelCount = mBackend.getChannelCount(mAppRow.pkg, mAppRow.uid);
                Collections.sort(mChannelGroupList, CHANNEL_GROUP_COMPARATOR);
                return null;
        }

            @Override
            protected void onPostExecute(Void unused) {
                if (mContext == null) {
                    return;
                }

                updateFullList(mPreference, mChannelGroupList);
            }
        }.execute();
        mChannelGroupList.sort(CHANNEL_GROUP_COMPARATOR);
        return true;
    }

    /**
     * Update the preferences group to match the
     * @param groupPrefsList
     * @param channelGroups
     * Update the preferences group to match the backing data
     */
    void updateFullList(@NonNull PreferenceCategory groupPrefsList,
                @NonNull List<NotificationChannelGroup> channelGroups) {
        if (channelGroups.isEmpty()) {
            if (mChannelCount > 0) {
                groupPrefsList.removeAll();
            } else {
                if (groupPrefsList.getPreferenceCount() == 1
                        && KEY_ZERO_CATEGORIES.equals(groupPrefsList.getPreference(0).getKey())) {
                    // Ensure the titles are correct for the current language, but otherwise leave alone
                    PreferenceGroup groupCategory = (PreferenceGroup) groupPrefsList.getPreference(
                            0);
                    groupCategory.setTitle(R.string.notification_channels);
                    groupCategory.getPreference(0).setTitle(R.string.no_channels);
                } else {
                    // Clear any contents and create the 'zero-categories' group.
                    groupPrefsList.removeAll();

                    PreferenceCategory groupCategory = new PreferenceCategory(mContext);
                    groupCategory.setTitle(R.string.notification_channels);
                    groupCategory.setKey(KEY_ZERO_CATEGORIES);
                    groupPrefsList.addPreference(groupCategory);

                    Preference empty = new Preference(mContext);
    void updateFullList() {
        Preference empty = mPreference.findPreference(KEY_ZERO_CATEGORIES);
        if (mChannelCount == 0) {
            if (empty == null) {
                mPreference.removeAll();
                empty = new Preference(mContext);
                empty.setTitle(R.string.no_channels);
                    empty.setEnabled(false);
                    groupCategory.addPreference(empty);
                empty.setKey(KEY_ZERO_CATEGORIES);
                mPreference.addPreference(empty);
            }
        } else if (empty != null) {
            mPreference.removePreference(empty);
        }
        } else {
            updateGroupList(groupPrefsList, channelGroups);

        if (!mChannelGroupList.isEmpty()) {
            updateGroupList(mPreference, mChannelGroupList);
        }
        Preference showMore = mPreference.findPreference(KEY_SHOW_MORE);
        if (!mAppRow.showAllChannels && mVisibleChannelCount != mChannelCount) {
            if (showMore == null) {
                showMore = new Preference(mContext);
                showMore.setKey(KEY_SHOW_MORE);
                showMore.setTitle(R.string.no_recent_channels);
                showMore.setOnPreferenceClickListener(preference1 -> {
                    mAppRow.showAllChannels = true;
                    mDependentFieldListener.onFieldValueChanged();
                    return true;
                });
                mPreference.addPreference(showMore);
            }
        } else if (showMore != null) {
            mPreference.removePreference(showMore);
        }
    }

@@ -163,7 +189,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
     * match, it checks all groups, and if it can't find that group anywhere, it creates it.
     */
    @NonNull
    private PreferenceCategory findOrCreateGroupCategoryForKey(
    private ExpandablePreference findOrCreateGroupCategoryForKey(
            @NonNull PreferenceCategory groupPrefsList, @Nullable String key, int expectedIndex) {
        if (key == null) {
            key = KEY_GENERAL_CATEGORY;
@@ -172,17 +198,17 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
        if (expectedIndex < preferenceCount) {
            Preference preference = groupPrefsList.getPreference(expectedIndex);
            if (key.equals(preference.getKey())) {
                return (PreferenceCategory) preference;
                return (ExpandablePreference) preference;
            }
        }
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = groupPrefsList.getPreference(i);
            if (key.equals(preference.getKey())) {
                preference.setOrder(expectedIndex);
                return (PreferenceCategory) preference;
                return (ExpandablePreference) preference;
            }
        }
        PreferenceCategory groupCategory = new PreferenceCategory(mContext);
        ExpandablePreference groupCategory = new ExpandablePreference(mContext);
        groupCategory.setOrder(expectedIndex);
        groupCategory.setKey(key);
        groupPrefsList.addPreference(groupCategory);
@@ -194,10 +220,10 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
        // Update the list, but optimize for the most common case where the list hasn't changed.
        int numFinalGroups = channelGroups.size();
        int initialPrefCount = groupPrefsList.getPreferenceCount();
        List<PreferenceCategory> finalOrderedGroups = new ArrayList<>(numFinalGroups);
        List<ExpandablePreference> finalOrderedGroups = new ArrayList<>(numFinalGroups);
        for (int i = 0; i < numFinalGroups; i++) {
            NotificationChannelGroup group = channelGroups.get(i);
            PreferenceCategory groupCategory =
            ExpandablePreference groupCategory =
                    findOrCreateGroupCategoryForKey(groupPrefsList, group.getId(), i);
            finalOrderedGroups.add(groupCategory);
            updateGroupPreferences(group, groupCategory);
@@ -211,14 +237,13 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
        boolean requiresRemoval = postAddPrefCount != numFinalGroups;
        if (hasInsertions || requiresRemoval) {
            groupPrefsList.removeAll();
            for (PreferenceCategory group : finalOrderedGroups) {
            for (ExpandablePreference group : finalOrderedGroups) {
                groupPrefsList.addPreference(group);
            }
        }
        Preference otherGroup = groupPrefsList.findPreference(KEY_GENERAL_CATEGORY);
        ExpandablePreference otherGroup = groupPrefsList.findPreference(KEY_GENERAL_CATEGORY);
        if (otherGroup != null) {
            otherGroup.setTitle(numFinalGroups == 1
                    ? R.string.notification_channels : R.string.notification_channels_other);
            otherGroup.setTitle(R.string.notification_channels_other);
        }
    }

Loading