Files
packages_apps_Settings/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java
Julia Reynolds 41896428ea Layout improvements for notification pages
Changes for upcoming theming changes in Settings:
- replacing some margins with padding
- merging some (switch pref + info pref) combos into a single preference.
I flagged the one that was already launched, but changed the other directly
- added some section headers (unflagged)
- moved all app wide notification settings to a single section (unflagged)
- changed two plain text prefs into TopIntroPreference, the dedicated pref
type for that sort of UI
- fixed some UI issues with 'Show more categories' appearing too often
- removed a duplicate notifcation channel label (unflagged)
- replaced a button layout preference with ButtonPreference (unflagged)

Test: manual review with is_expressive_design_enabled on and off
Test: atest com.android.settings.notification.app
Flag: EXEMPT this feature is not using aconfig for flagging
Bug: 349652992
Change-Id: I2acd7b2eb9dbcf6929143bfde99cd67163f1f95d
2025-01-09 17:07:53 -05:00

267 lines
11 KiB
Java

/*
* Copyright (C) 2020 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.app;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.widget.Button;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.ButtonPreference;
import com.android.settingslib.widget.LayoutPreference;
import java.text.Collator;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class RecentConversationsPreferenceController extends AbstractPreferenceController {
private static final String TAG = "RecentConversationsPC";
private static final String KEY = "recent_conversations";
private static final String CLEAR_ALL_KEY_SUFFIX = "_clear_all";
private final IPeopleManager mPs;
private final NotificationBackend mBackend;
private PreferenceGroup mPreferenceGroup;
public RecentConversationsPreferenceController(
Context context, NotificationBackend backend, IPeopleManager ps) {
super(context);
mBackend = backend;
mPs = ps;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public boolean isAvailable() {
return true;
}
ButtonPreference getClearAll(PreferenceGroup parent) {
ButtonPreference pref = new ButtonPreference(mContext);
pref.setTitle(R.string.conversation_settings_clear_recents);
pref.setKey(getPreferenceKey() + CLEAR_ALL_KEY_SUFFIX);
pref.setOrder(1);
pref.setOnClickListener(v -> {
try {
mPs.removeAllRecentConversations();
// Removing recents is asynchronous, so we can't immediately reload the list from
// the backend. Instead, proactively remove all of items that were marked as
// clearable, so long as we didn't get an error
for (int i = parent.getPreferenceCount() - 1; i >= 0; i--) {
Preference p = parent.getPreference(i);
if (p instanceof RecentConversationPreference) {
if (((RecentConversationPreference) p).hasClearListener()) {
parent.removePreference(p);
}
}
}
pref.getButton().announceForAccessibility(
mContext.getString(R.string.recent_convos_removed));
} catch (RemoteException e) {
Slog.w(TAG, "Could not clear recents", e);
}
});
return pref;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreferenceGroup = screen.findPreference(getPreferenceKey());
}
/**
* Updates the conversation list.
*
* @return true if this controller has content to display.
*/
boolean updateList() {
// Load conversations
List<ConversationChannel> conversations = Collections.emptyList();
try {
conversations = mPs.getRecentConversations().getList();
} catch (RemoteException e) {
Slog.w(TAG, "Could not get recent conversations", e);
}
return populateList(conversations);
}
@VisibleForTesting
boolean populateList(List<ConversationChannel> conversations) {
mPreferenceGroup.removeAll();
boolean hasClearable = false;
if (conversations != null) {
hasClearable = populateConversations(conversations);
}
boolean hashContent = mPreferenceGroup.getPreferenceCount() != 0;
mPreferenceGroup.setVisible(hashContent);
if (hashContent && hasClearable) {
Preference clearAll = getClearAll(mPreferenceGroup);
if (clearAll != null) {
mPreferenceGroup.addPreference(clearAll);
}
}
return hashContent;
}
protected boolean populateConversations(List<ConversationChannel> conversations) {
AtomicInteger order = new AtomicInteger(100);
AtomicBoolean hasClearable = new AtomicBoolean(false);
conversations.stream()
.filter(conversation ->
conversation.getNotificationChannel().getImportance() != IMPORTANCE_NONE
&& (conversation.getNotificationChannelGroup() == null
|| !conversation.getNotificationChannelGroup().isBlocked()))
.sorted(mConversationComparator)
.map(this::createConversationPref)
.forEachOrdered(pref -> {
pref.setOrder(order.getAndIncrement());
mPreferenceGroup.addPreference(pref);
if (pref instanceof RecentConversationPreference
&& ((RecentConversationPreference) pref).hasClearListener()) {
hasClearable.set(true);
}
});
return hasClearable.get();
}
protected Preference createConversationPref(
final ConversationChannel conversation) {
final String pkg = conversation.getShortcutInfo().getPackage();
final int uid = conversation.getUid();
final String conversationId = conversation.getShortcutInfo().getId();
Preference pref = conversation.hasActiveNotifications() ? new Preference(mContext)
: new RecentConversationPreference(mContext);
if (!conversation.hasActiveNotifications()) {
((RecentConversationPreference) pref).setOnClearClickListener(() -> {
try {
mPs.removeRecentConversation(pkg, UserHandle.getUserId(uid), conversationId);
((RecentConversationPreference) pref).getClearView().announceForAccessibility(
mContext.getString(R.string.recent_convo_removed));
mPreferenceGroup.removePreference(pref);
} catch (RemoteException e) {
Slog.w(TAG, "Could not clear recent", e);
}
});
}
pref.setTitle(getTitle(conversation));
pref.setSummary(getSummary(conversation));
pref.setIcon(mBackend.getConversationDrawable(mContext, conversation.getShortcutInfo(),
pkg, uid, false));
pref.setKey(conversation.getNotificationChannel().getId()
+ ":" + conversationId);
pref.setOnPreferenceClickListener(preference -> {
mBackend.createConversationNotificationChannel(
pkg, uid,
conversation.getNotificationChannel(),
conversationId);
getSubSettingLauncher(conversation, pref.getTitle()).launch();
return true;
});
return pref;
}
CharSequence getSummary(ConversationChannel conversation) {
return conversation.getNotificationChannelGroup() == null
? conversation.getNotificationChannel().getName()
: mContext.getString(R.string.notification_conversation_summary,
conversation.getNotificationChannel().getName(),
conversation.getNotificationChannelGroup().getName());
}
CharSequence getTitle(ConversationChannel conversation) {
ShortcutInfo si = conversation.getShortcutInfo();
return si.getLabel();
}
SubSettingLauncher getSubSettingLauncher(ConversationChannel conversation,
CharSequence title) {
Bundle channelArgs = new Bundle();
channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, conversation.getUid());
channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME,
conversation.getShortcutInfo().getPackage());
channelArgs.putString(Settings.EXTRA_CHANNEL_ID,
conversation.getNotificationChannel().getId());
channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
conversation.getShortcutInfo().getId());
return new SubSettingLauncher(mContext)
.setDestination(ChannelNotificationSettings.class.getName())
.setArguments(channelArgs)
.setExtras(channelArgs)
.setUserHandle(UserHandle.getUserHandleForUid(conversation.getUid()))
.setTitleText(title)
.setSourceMetricsCategory(SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS);
}
@VisibleForTesting
Comparator<ConversationChannel> mConversationComparator =
new Comparator<ConversationChannel>() {
private final Collator sCollator = Collator.getInstance();
@Override
public int compare(ConversationChannel o1, ConversationChannel o2) {
int labelComparison = 0;
if (o1.getShortcutInfo().getLabel() != null
&& o2.getShortcutInfo().getLabel() != null) {
labelComparison = sCollator.compare(
o1.getShortcutInfo().getLabel().toString(),
o2.getShortcutInfo().getLabel().toString());
}
if (labelComparison == 0) {
return o1.getNotificationChannel().getId().compareTo(
o2.getNotificationChannel().getId());
}
return labelComparison;
}
};
}