Merge "Update NotificationChannelSettings Preferences in place to prevent re-layout." into rvc-qpr-dev am: e9523b7636
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/12792910 Change-Id: I55f6310b344baebfe6a4425d3644d22d8e479638
This commit is contained in:
@@ -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();
|
||||
|
||||
if (mChannelGroupList.isEmpty()) {
|
||||
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
|
||||
groupCategory.setTitle(R.string.notification_channels);
|
||||
groupCategory.setKey(KEY_GENERAL_CATEGORY);
|
||||
mPreference.addPreference(groupCategory);
|
||||
|
||||
Preference empty = new Preference(mContext);
|
||||
empty.setTitle(R.string.no_channels);
|
||||
empty.setEnabled(false);
|
||||
groupCategory.addPreference(empty);
|
||||
} else {
|
||||
populateGroupList();
|
||||
}
|
||||
}
|
||||
|
||||
private void populateGroupList() {
|
||||
for (NotificationChannelGroup group : mChannelGroupList) {
|
||||
PreferenceCategory groupCategory = new PreferenceCategory(mContext);
|
||||
groupCategory.setOrderingAsAdded(true);
|
||||
mPreference.addPreference(groupCategory);
|
||||
if (group.getId() == null) {
|
||||
groupCategory.setTitle(R.string.notification_channels_other);
|
||||
groupCategory.setKey(KEY_GENERAL_CATEGORY);
|
||||
/**
|
||||
* 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 {
|
||||
groupCategory.setTitle(group.getName());
|
||||
groupCategory.setKey(group.getId());
|
||||
populateGroupToggle(groupCategory, group);
|
||||
// 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);
|
||||
empty.setTitle(R.string.no_channels);
|
||||
empty.setEnabled(false);
|
||||
groupCategory.addPreference(empty);
|
||||
}
|
||||
if (!group.isBlocked()) {
|
||||
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);
|
||||
// conversations get their own section
|
||||
if (TextUtils.isEmpty(channel.getConversationId()) || channel.isDemoted()) {
|
||||
populateSingleChannelPrefs(groupCategory, channel, group.isBlocked());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateGroupList(groupPrefsList, channelGroups);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void populateGroupToggle(final PreferenceGroup parent,
|
||||
NotificationChannelGroup group) {
|
||||
RestrictedSwitchPreference preference =
|
||||
new RestrictedSwitchPreference(mContext);
|
||||
/**
|
||||
* 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) {
|
||||
// For the 'null' group, set the "Other" title.
|
||||
groupPrefGroup.setTitle(R.string.notification_channels_other);
|
||||
} else {
|
||||
// For an app-defined group, set their name and create a row to toggle 'isBlocked'.
|
||||
groupPrefGroup.setTitle(group.getName());
|
||||
finalOrderedPrefs.add(addOrUpdateGroupToggle(groupPrefGroup, group));
|
||||
}
|
||||
// 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);
|
||||
for (NotificationChannel channel : channels) {
|
||||
if (!TextUtils.isEmpty(channel.getConversationId()) && !channel.isDemoted()) {
|
||||
// conversations get their own section
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
});
|
||||
|
||||
parent.addPreference(preference);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user