• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.notification.app;
18 
19 import static android.app.NotificationManager.IMPORTANCE_NONE;
20 
21 import android.app.people.ConversationChannel;
22 import android.app.people.IPeopleManager;
23 import android.app.settings.SettingsEnums;
24 import android.content.Context;
25 import android.content.pm.ShortcutInfo;
26 import android.os.Bundle;
27 import android.os.RemoteException;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.util.Slog;
31 import android.widget.Button;
32 
33 import androidx.annotation.VisibleForTesting;
34 import androidx.preference.Preference;
35 import androidx.preference.PreferenceGroup;
36 import androidx.preference.PreferenceScreen;
37 
38 import com.android.settings.R;
39 import com.android.settings.applications.AppInfoBase;
40 import com.android.settings.core.SubSettingLauncher;
41 import com.android.settings.notification.NotificationBackend;
42 import com.android.settingslib.core.AbstractPreferenceController;
43 import com.android.settingslib.widget.ButtonPreference;
44 import com.android.settingslib.widget.LayoutPreference;
45 
46 import java.text.Collator;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.List;
50 import java.util.concurrent.atomic.AtomicBoolean;
51 import java.util.concurrent.atomic.AtomicInteger;
52 
53 public class RecentConversationsPreferenceController extends AbstractPreferenceController {
54 
55     private static final String TAG = "RecentConversationsPC";
56     private static final String KEY = "recent_conversations";
57     private static final String CLEAR_ALL_KEY_SUFFIX = "_clear_all";
58     private final IPeopleManager mPs;
59     private final NotificationBackend mBackend;
60     private PreferenceGroup mPreferenceGroup;
61 
RecentConversationsPreferenceController( Context context, NotificationBackend backend, IPeopleManager ps)62     public RecentConversationsPreferenceController(
63             Context context, NotificationBackend backend, IPeopleManager ps) {
64         super(context);
65         mBackend = backend;
66         mPs = ps;
67     }
68 
69     @Override
getPreferenceKey()70     public String getPreferenceKey() {
71         return KEY;
72     }
73 
74     @Override
isAvailable()75     public boolean isAvailable() {
76         return true;
77     }
78 
getClearAll(PreferenceGroup parent)79     ButtonPreference getClearAll(PreferenceGroup parent) {
80         ButtonPreference pref = new ButtonPreference(mContext);
81         pref.setTitle(R.string.conversation_settings_clear_recents);
82         pref.setKey(getPreferenceKey() + CLEAR_ALL_KEY_SUFFIX);
83         pref.setOrder(1);
84         pref.setOnClickListener(v -> {
85             try {
86                 mPs.removeAllRecentConversations();
87                 // Removing recents is asynchronous, so we can't immediately reload the list from
88                 // the backend. Instead, proactively remove all of items that were marked as
89                 // clearable, so long as we didn't get an error
90 
91                 for (int i = parent.getPreferenceCount() - 1; i >= 0; i--) {
92                     Preference p = parent.getPreference(i);
93                     if (p instanceof RecentConversationPreference) {
94                         if (((RecentConversationPreference) p).hasClearListener()) {
95                             parent.removePreference(p);
96                         }
97                     }
98                 }
99                 pref.getButton().announceForAccessibility(
100                         mContext.getString(R.string.recent_convos_removed));
101             } catch (RemoteException e) {
102                 Slog.w(TAG, "Could not clear recents", e);
103             }
104         });
105         return pref;
106     }
107 
108     @Override
displayPreference(PreferenceScreen screen)109     public void displayPreference(PreferenceScreen screen) {
110         super.displayPreference(screen);
111         mPreferenceGroup = screen.findPreference(getPreferenceKey());
112     }
113 
114     /**
115      * Updates the conversation list.
116      *
117      * @return true if this controller has content to display.
118      */
updateList()119     boolean updateList() {
120         // Load conversations
121         List<ConversationChannel> conversations = Collections.emptyList();
122         try {
123             conversations = mPs.getRecentConversations().getList();
124         } catch (RemoteException e) {
125             Slog.w(TAG, "Could not get recent conversations", e);
126         }
127 
128         return populateList(conversations);
129     }
130 
131     @VisibleForTesting
populateList(List<ConversationChannel> conversations)132     boolean populateList(List<ConversationChannel> conversations) {
133         mPreferenceGroup.removeAll();
134         boolean hasClearable = false;
135         if (conversations != null) {
136             hasClearable = populateConversations(conversations);
137         }
138 
139         boolean hashContent = mPreferenceGroup.getPreferenceCount() != 0;
140         mPreferenceGroup.setVisible(hashContent);
141         if (hashContent && hasClearable) {
142             Preference clearAll = getClearAll(mPreferenceGroup);
143             if (clearAll != null) {
144                 mPreferenceGroup.addPreference(clearAll);
145             }
146         }
147         return hashContent;
148     }
149 
populateConversations(List<ConversationChannel> conversations)150     protected boolean populateConversations(List<ConversationChannel> conversations) {
151         AtomicInteger order = new AtomicInteger(100);
152         AtomicBoolean hasClearable = new AtomicBoolean(false);
153         conversations.stream()
154                 .filter(conversation ->
155                         conversation.getNotificationChannel().getImportance() != IMPORTANCE_NONE
156                                 && (conversation.getNotificationChannelGroup() == null
157                                 || !conversation.getNotificationChannelGroup().isBlocked()))
158                 .sorted(mConversationComparator)
159                 .map(this::createConversationPref)
160                 .forEachOrdered(pref -> {
161                     pref.setOrder(order.getAndIncrement());
162                     mPreferenceGroup.addPreference(pref);
163                     if (pref instanceof RecentConversationPreference
164                             && ((RecentConversationPreference) pref).hasClearListener()) {
165                         hasClearable.set(true);
166                     }
167                 });
168         return hasClearable.get();
169     }
170 
createConversationPref( final ConversationChannel conversation)171     protected Preference createConversationPref(
172             final ConversationChannel conversation) {
173         final String pkg = conversation.getShortcutInfo().getPackage();
174         final int uid = conversation.getUid();
175         final String conversationId = conversation.getShortcutInfo().getId();
176         Preference pref = conversation.hasActiveNotifications() ? new Preference(mContext)
177                 : new RecentConversationPreference(mContext);
178 
179         if (!conversation.hasActiveNotifications()) {
180             ((RecentConversationPreference) pref).setOnClearClickListener(() -> {
181                 try {
182                     mPs.removeRecentConversation(pkg, UserHandle.getUserId(uid), conversationId);
183                     ((RecentConversationPreference) pref).getClearView().announceForAccessibility(
184                             mContext.getString(R.string.recent_convo_removed));
185                     mPreferenceGroup.removePreference(pref);
186                 } catch (RemoteException e) {
187                     Slog.w(TAG, "Could not clear recent", e);
188                 }
189             });
190         }
191 
192         pref.setTitle(getTitle(conversation));
193         pref.setSummary(getSummary(conversation));
194         pref.setIcon(mBackend.getConversationDrawable(mContext, conversation.getShortcutInfo(),
195                 pkg, uid, false));
196         pref.setKey(conversation.getNotificationChannel().getId()
197                 + ":" + conversationId);
198         pref.setOnPreferenceClickListener(preference -> {
199             mBackend.createConversationNotificationChannel(
200                     pkg, uid,
201                     conversation.getNotificationChannel(),
202                     conversationId);
203             getSubSettingLauncher(conversation, pref.getTitle()).launch();
204             return true;
205         });
206 
207         return pref;
208     }
209 
getSummary(ConversationChannel conversation)210     CharSequence getSummary(ConversationChannel conversation) {
211         return conversation.getNotificationChannelGroup() == null
212                 ? conversation.getNotificationChannel().getName()
213                 : mContext.getString(R.string.notification_conversation_summary,
214                         conversation.getNotificationChannel().getName(),
215                         conversation.getNotificationChannelGroup().getName());
216     }
217 
getTitle(ConversationChannel conversation)218     CharSequence getTitle(ConversationChannel conversation) {
219         ShortcutInfo si = conversation.getShortcutInfo();
220         return si.getLabel();
221     }
222 
getSubSettingLauncher(ConversationChannel conversation, CharSequence title)223     SubSettingLauncher getSubSettingLauncher(ConversationChannel conversation,
224             CharSequence title) {
225         Bundle channelArgs = new Bundle();
226         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, conversation.getUid());
227         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME,
228                 conversation.getShortcutInfo().getPackage());
229         channelArgs.putString(Settings.EXTRA_CHANNEL_ID,
230                 conversation.getNotificationChannel().getId());
231         channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
232                 conversation.getShortcutInfo().getId());
233 
234         return new SubSettingLauncher(mContext)
235                 .setDestination(ChannelNotificationSettings.class.getName())
236                 .setArguments(channelArgs)
237                 .setExtras(channelArgs)
238                 .setUserHandle(UserHandle.getUserHandleForUid(conversation.getUid()))
239                 .setTitleText(title)
240                 .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS);
241     }
242 
243     @VisibleForTesting
244     Comparator<ConversationChannel> mConversationComparator =
245             new Comparator<ConversationChannel>() {
246                 private final Collator sCollator = Collator.getInstance();
247 
248                 @Override
249                 public int compare(ConversationChannel o1, ConversationChannel o2) {
250                     int labelComparison = 0;
251                     if (o1.getShortcutInfo().getLabel() != null
252                             && o2.getShortcutInfo().getLabel() != null) {
253                         labelComparison = sCollator.compare(
254                                 o1.getShortcutInfo().getLabel().toString(),
255                                 o2.getShortcutInfo().getLabel().toString());
256                     }
257 
258                     if (labelComparison == 0) {
259                         return o1.getNotificationChannel().getId().compareTo(
260                                 o2.getNotificationChannel().getId());
261                     }
262 
263                     return labelComparison;
264                 }
265             };
266 }
267