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

Commit 5bfe1d3f authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Update NotificationChannelSettings Preferences in place to prevent re-layout.

(cherry picked from commit b409b640)

Fixes: 110093185
Test: ChannelListPreferenceControllerTest
Change-Id: If6acf305c44085e502a3304ea57e409ce049b40f
Merged-In: If6acf305c44085e502a3304ea57e409ce049b40f
parent ab41be56
Loading
Loading
Loading
Loading
+188 −81
Original line number Diff line number Diff line
@@ -23,16 +23,14 @@ import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.graphics.BlendMode;
import android.graphics.BlendModeColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
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.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
@@ -53,7 +51,8 @@ import java.util.List;
public class ChannelListPreferenceController extends NotificationPreferenceController {

    private static final String KEY = "channels";
    private static String KEY_GENERAL_CATEGORY = "categories";
    private static final String KEY_GENERAL_CATEGORY = "categories";
    private static final String KEY_ZERO_CATEGORIES = "zeroCategories";
    public static final String ARG_FROM_SETTINGS = "fromSettings";

    private List<NotificationChannelGroup> mChannelGroupList;
@@ -102,62 +101,192 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
                if (mContext == null) {
                    return;
                }
                populateList();
                updateFullList(mPreference, mChannelGroupList);
            }
        }.execute();
    }

    private void populateList() {
        // TODO: if preference has children, compare with newly loaded list
        mPreference.removeAll();
    /**
     * Update the preferences group to match the
     * @param groupPrefsList
     * @param channelGroups
     */
    void updateFullList(@NonNull PreferenceCategory groupPrefsList,
                @NonNull List<NotificationChannelGroup> channelGroups) {
        if (channelGroups.isEmpty()) {
            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();

        if (mChannelGroupList.isEmpty()) {
                PreferenceCategory groupCategory = new PreferenceCategory(mContext);
                groupCategory.setTitle(R.string.notification_channels);
            groupCategory.setKey(KEY_GENERAL_CATEGORY);
            mPreference.addPreference(groupCategory);
                groupCategory.setKey(KEY_ZERO_CATEGORIES);
                groupPrefsList.addPreference(groupCategory);

                Preference empty = new Preference(mContext);
                empty.setTitle(R.string.no_channels);
                empty.setEnabled(false);
                groupCategory.addPreference(empty);
            }
        } else {
            populateGroupList();
            updateGroupList(groupPrefsList, channelGroups);
        }
    }

    private void populateGroupList() {
        for (NotificationChannelGroup group : mChannelGroupList) {
    /**
     * Looks for the category for the given group's key at the expected index, if that doesn't
     * match, it checks all groups, and if it can't find that group anywhere, it creates it.
     */
    @NonNull
    private PreferenceCategory findOrCreateGroupCategoryForKey(
            @NonNull PreferenceCategory groupPrefsList, @Nullable String key, int expectedIndex) {
        if (key == null) {
            key = KEY_GENERAL_CATEGORY;
        }
        int preferenceCount = groupPrefsList.getPreferenceCount();
        if (expectedIndex < preferenceCount) {
            Preference preference = groupPrefsList.getPreference(expectedIndex);
            if (key.equals(preference.getKey())) {
                return (PreferenceCategory) preference;
            }
        }
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = groupPrefsList.getPreference(i);
            if (key.equals(preference.getKey())) {
                preference.setOrder(expectedIndex);
                return (PreferenceCategory) preference;
            }
        }
        PreferenceCategory groupCategory = new PreferenceCategory(mContext);
            groupCategory.setOrderingAsAdded(true);
            mPreference.addPreference(groupCategory);
        groupCategory.setOrder(expectedIndex);
        groupCategory.setKey(key);
        groupPrefsList.addPreference(groupCategory);
        return groupCategory;
    }

    private void updateGroupList(@NonNull PreferenceCategory groupPrefsList,
            @NonNull List<NotificationChannelGroup> channelGroups) {
        // 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);
        for (int i = 0; i < numFinalGroups; i++) {
            NotificationChannelGroup group = channelGroups.get(i);
            PreferenceCategory groupCategory =
                    findOrCreateGroupCategoryForKey(groupPrefsList, group.getId(), i);
            finalOrderedGroups.add(groupCategory);
            updateGroupPreferences(group, groupCategory);
        }
        int postAddPrefCount = groupPrefsList.getPreferenceCount();
        // If any groups were inserted (into a non-empty list) or need to be removed, we need to
        // remove all groups and re-add them all.
        // This is required to ensure proper ordering of inserted groups, and it simplifies logic
        // at the cost of computation in the rare case that the list is changing.
        boolean hasInsertions = initialPrefCount != 0 && initialPrefCount != numFinalGroups;
        boolean requiresRemoval = postAddPrefCount != numFinalGroups;
        if (hasInsertions || requiresRemoval) {
            groupPrefsList.removeAll();
            for (PreferenceCategory group : finalOrderedGroups) {
                groupPrefsList.addPreference(group);
            }
        }
    }

    /**
     * Looks for the channel preference for the given channel's key at the expected index, if that
     * doesn't match, it checks all rows, and if it can't find that channel anywhere, it creates
     * the preference.
     */
    @NonNull
    private MasterSwitchPreference findOrCreateChannelPrefForKey(
            @NonNull PreferenceGroup groupPrefGroup, @NonNull String key, int expectedIndex) {
        int preferenceCount = groupPrefGroup.getPreferenceCount();
        if (expectedIndex < preferenceCount) {
            Preference preference = groupPrefGroup.getPreference(expectedIndex);
            if (key.equals(preference.getKey())) {
                return (MasterSwitchPreference) preference;
            }
        }
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = groupPrefGroup.getPreference(i);
            if (key.equals(preference.getKey())) {
                preference.setOrder(expectedIndex);
                return (MasterSwitchPreference) preference;
            }
        }
        MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
        channelPref.setOrder(expectedIndex);
        channelPref.setKey(key);
        groupPrefGroup.addPreference(channelPref);
        return channelPref;
    }

    private void updateGroupPreferences(@NonNull NotificationChannelGroup group,
            @NonNull PreferenceGroup groupPrefGroup) {
        int initialPrefCount = groupPrefGroup.getPreferenceCount();
        List<Preference> finalOrderedPrefs = new ArrayList<>();
        if (group.getId() == null) {
                groupCategory.setTitle(R.string.notification_channels_other);
                groupCategory.setKey(KEY_GENERAL_CATEGORY);
            // For the 'null' group, set the "Other" title.
            groupPrefGroup.setTitle(R.string.notification_channels_other);
        } else {
                groupCategory.setTitle(group.getName());
                groupCategory.setKey(group.getId());
                populateGroupToggle(groupCategory, group);
            // For an app-defined group, set their name and create a row to toggle 'isBlocked'.
            groupPrefGroup.setTitle(group.getName());
            finalOrderedPrefs.add(addOrUpdateGroupToggle(groupPrefGroup, group));
        }
            if (!group.isBlocked()) {
                final List<NotificationChannel> channels = group.getChannels();
        // Here "empty" means having no channel rows; the group toggle is ignored for this purpose.
        boolean initiallyEmpty = groupPrefGroup.getPreferenceCount() == finalOrderedPrefs.size();

        // For each channel, add or update the preference object.
        final List<NotificationChannel> channels =
                group.isBlocked() ? Collections.emptyList() : group.getChannels();
        Collections.sort(channels, CHANNEL_COMPARATOR);
                int N = channels.size();
                for (int i = 0; i < N; i++) {
                    final NotificationChannel channel = channels.get(i);
        for (NotificationChannel channel : channels) {
            if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
                // conversations get their own section
                    if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
                        populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
                continue;
            }
            // Get or create the row, and populate its current state.
            MasterSwitchPreference channelPref = findOrCreateChannelPrefForKey(groupPrefGroup,
                    channel.getId(), /* expectedIndex */ finalOrderedPrefs.size());
            updateSingleChannelPrefs(channelPref, channel, group.isBlocked());
            finalOrderedPrefs.add(channelPref);
        }
        int postAddPrefCount = groupPrefGroup.getPreferenceCount();

        // If any channels were inserted (into a non-empty list) or need to be removed, we need to
        // remove all preferences and re-add them all.
        // This is required to ensure proper ordering of inserted channels, and it simplifies logic
        // at the cost of computation in the rare case that the list is changing.
        int numFinalGroups = finalOrderedPrefs.size();
        boolean hasInsertions = !initiallyEmpty && initialPrefCount != numFinalGroups;
        boolean requiresRemoval = postAddPrefCount != numFinalGroups;
        if (hasInsertions || requiresRemoval) {
            groupPrefGroup.removeAll();
            for (Preference preference : finalOrderedPrefs) {
                groupPrefGroup.addPreference(preference);
            }
        }
    }

    protected void populateGroupToggle(final PreferenceGroup parent,
            NotificationChannelGroup group) {
        RestrictedSwitchPreference preference =
                new RestrictedSwitchPreference(mContext);
    /** Add or find and update the toggle for disabling the entire notification channel group. */
    private Preference addOrUpdateGroupToggle(@NonNull final PreferenceGroup parent,
            @NonNull final NotificationChannelGroup group) {
        boolean shouldAdd = false;
        final RestrictedSwitchPreference preference;
        if (parent.getPreferenceCount() > 0
                && parent.getPreference(0) instanceof RestrictedSwitchPreference) {
            preference = (RestrictedSwitchPreference) parent.getPreference(0);
        } else {
            shouldAdd = true;
            preference = new RestrictedSwitchPreference(mContext);
        }
        preference.setOrder(-1);
        preference.setTitle(mContext.getString(
                R.string.notification_switch_label, group.getName()));
        preference.setEnabled(mAdmin == null
@@ -171,23 +300,26 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
            onGroupBlockStateChanged(group);
            return true;
        });

        if (shouldAdd) {
            parent.addPreference(preference);
        }
        return preference;
    }

    protected Preference populateSingleChannelPrefs(PreferenceGroup parent,
            final NotificationChannel channel, final boolean groupBlocked) {
        MasterSwitchPreference channelPref = new MasterSwitchPreference(mContext);
    /** Update the properties of the channel preference with the values from the channel object. */
    private void updateSingleChannelPrefs(@NonNull final MasterSwitchPreference channelPref,
            @NonNull final NotificationChannel channel,
            final boolean groupBlocked) {
        channelPref.setSwitchEnabled(mAdmin == null
                && isChannelBlockable(channel)
                && isChannelConfigurable(channel)
                && !groupBlocked);
        channelPref.setIcon(null);
        if (channel.getImportance() > IMPORTANCE_LOW) {
            channelPref.setIcon(getAlertingIcon());
        } else {
            channelPref.setIcon(null);
        }
        channelPref.setIconSize(MasterSwitchPreference.ICON_SIZE_SMALL);
        channelPref.setKey(channel.getId());
        channelPref.setTitle(channel.getName());
        channelPref.setSummary(NotificationBackend.getSentSummary(
                mContext, mAppRow.sentByChannel.get(channel.getId()), false));
@@ -219,10 +351,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr

                    return true;
                });
        if (parent.findPreference(channelPref.getKey()) == null) {
            parent.addPreference(channelPref);
        }
        return channelPref;
    }

    private Drawable getAlertingIcon() {
@@ -235,30 +363,9 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr
        if (group == null) {
            return;
        }
        PreferenceGroup groupGroup = mPreference.findPreference(group.getId());

        if (groupGroup != null) {
            if (group.isBlocked()) {
                List<Preference> toRemove = new ArrayList<>();
                int childCount = groupGroup.getPreferenceCount();
                for (int i = 0; i < childCount; i++) {
                    Preference pref = groupGroup.getPreference(i);
                    if (pref instanceof MasterSwitchPreference) {
                        toRemove.add(pref);
                    }
                }
                for (Preference pref : toRemove) {
                    groupGroup.removePreference(pref);
                }
            } else {
                final List<NotificationChannel> channels = group.getChannels();
                Collections.sort(channels, CHANNEL_COMPARATOR);
                int N = channels.size();
                for (int i = 0; i < N; i++) {
                    final NotificationChannel channel = channels.get(i);
                    populateSingleChannelPrefs(groupGroup, channel, group.isBlocked());
                }
            }
        PreferenceGroup groupPrefGroup = mPreference.findPreference(group.getId());
        if (groupPrefGroup != null) {
            updateGroupPreferences(group, groupPrefGroup);
        }
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Switch;

import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceViewHolder;

import com.android.settings.R;
@@ -101,6 +103,16 @@ public class MasterSwitchPreference extends RestrictedPreference {
        return mSwitch != null && mChecked;
    }

    /**
     * Used to validate the state of mChecked and mCheckedSet when testing, without requiring
     * that a ViewHolder be bound to the object.
     */
    @Keep
    @Nullable
    public Boolean getCheckedState() {
        return mCheckedSet ? mChecked : null;
    }

    public void setChecked(boolean checked) {
        // Always set checked the first time; don't assume the field's default of false.
        final boolean changed = mChecked != checked;
+395 −0

File added.

Preview size limit exceeded, changes collapsed.