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

Commit 9e1d9c40 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Update NotificationChannelSettings Preferences in place to prevent re-layout."

parents 18eabc3c b409b640
Loading
Loading
Loading
Loading
+188 −77
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ 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;
@@ -49,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;
@@ -98,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 PrimarySwitchPreference 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 (PrimarySwitchPreference) preference;
            }
        }
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = groupPrefGroup.getPreference(i);
            if (key.equals(preference.getKey())) {
                preference.setOrder(expectedIndex);
                return (PrimarySwitchPreference) preference;
            }
        }
        PrimarySwitchPreference channelPref = new PrimarySwitchPreference(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.
            PrimarySwitchPreference 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
@@ -167,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) {
        PrimarySwitchPreference channelPref = new PrimarySwitchPreference(mContext);
    /** Update the properties of the channel preference with the values from the channel object. */
    private void updateSingleChannelPrefs(@NonNull final PrimarySwitchPreference 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(PrimarySwitchPreference.ICON_SIZE_SMALL);
        channelPref.setKey(channel.getId());
        channelPref.setTitle(channel.getName());
        channelPref.setSummary(NotificationBackend.getSentSummary(
                mContext, mAppRow.sentByChannel.get(channel.getId()), false));
@@ -215,10 +351,6 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr

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

    private Drawable getAlertingIcon() {
@@ -231,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 PrimarySwitchPreference) {
                        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 PrimarySwitchPreference 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;
    }

    /**
     * Set the checked status to be {@code checked}.
     *