• 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.LayoutPreference;
44 
45 import java.text.Collator;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.List;
49 import java.util.concurrent.atomic.AtomicBoolean;
50 import java.util.concurrent.atomic.AtomicInteger;
51 
52 public class RecentConversationsPreferenceController extends AbstractPreferenceController {
53 
54     private static final String TAG = "RecentConversationsPC";
55     private static final String KEY = "recent_conversations";
56     private static final String CLEAR_ALL_KEY_SUFFIX = "_clear_all";
57     private final IPeopleManager mPs;
58     private final NotificationBackend mBackend;
59     private PreferenceGroup mPreferenceGroup;
60 
RecentConversationsPreferenceController( Context context, NotificationBackend backend, IPeopleManager ps)61     public RecentConversationsPreferenceController(
62             Context context, NotificationBackend backend, IPeopleManager ps) {
63         super(context);
64         mBackend = backend;
65         mPs = ps;
66     }
67 
68     @Override
getPreferenceKey()69     public String getPreferenceKey() {
70         return KEY;
71     }
72 
73     @Override
isAvailable()74     public boolean isAvailable() {
75         return true;
76     }
77 
78     //TODO(b/233325816): Use ButtonPreference instead.
getClearAll(PreferenceGroup parent)79     LayoutPreference getClearAll(PreferenceGroup parent) {
80         LayoutPreference pref = new LayoutPreference(
81                 mContext, R.layout.conversations_clear_recents);
82         pref.setKey(getPreferenceKey() + CLEAR_ALL_KEY_SUFFIX);
83         pref.setOrder(1);
84         Button button = pref.findViewById(R.id.conversation_settings_clear_recents);
85         button.setOnClickListener(v -> {
86             try {
87                 mPs.removeAllRecentConversations();
88                 // Removing recents is asynchronous, so we can't immediately reload the list from
89                 // the backend. Instead, proactively remove all of items that were marked as
90                 // clearable, so long as we didn't get an error
91 
92                 for (int i = parent.getPreferenceCount() - 1; i >= 0; i--) {
93                     Preference p = parent.getPreference(i);
94                     if (p instanceof RecentConversationPreference) {
95                         if (((RecentConversationPreference) p).hasClearListener()) {
96                             parent.removePreference(p);
97                         }
98                     }
99                 }
100                 button.announceForAccessibility(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.hasClearListener()) {
164                         hasClearable.set(true);
165                     }
166                 });
167         return hasClearable.get();
168     }
169 
createConversationPref( final ConversationChannel conversation)170     protected RecentConversationPreference createConversationPref(
171             final ConversationChannel conversation) {
172         final String pkg = conversation.getShortcutInfo().getPackage();
173         final int uid = conversation.getUid();
174         final String conversationId = conversation.getShortcutInfo().getId();
175         RecentConversationPreference pref = new RecentConversationPreference(mContext);
176 
177         if (!conversation.hasActiveNotifications()) {
178             pref.setOnClearClickListener(() -> {
179                 try {
180                     mPs.removeRecentConversation(pkg, UserHandle.getUserId(uid), conversationId);
181                     pref.getClearView().announceForAccessibility(
182                             mContext.getString(R.string.recent_convo_removed));
183                     mPreferenceGroup.removePreference(pref);
184                 } catch (RemoteException e) {
185                     Slog.w(TAG, "Could not clear recent", e);
186                 }
187             });
188         }
189 
190         pref.setTitle(getTitle(conversation));
191         pref.setSummary(getSummary(conversation));
192         pref.setIcon(mBackend.getConversationDrawable(mContext, conversation.getShortcutInfo(),
193                 pkg, uid, false));
194         pref.setKey(conversation.getNotificationChannel().getId()
195                 + ":" + conversationId);
196         pref.setOnPreferenceClickListener(preference -> {
197             mBackend.createConversationNotificationChannel(
198                     pkg, uid,
199                     conversation.getNotificationChannel(),
200                     conversationId);
201             getSubSettingLauncher(conversation, pref.getTitle()).launch();
202             return true;
203         });
204 
205         return pref;
206     }
207 
getSummary(ConversationChannel conversation)208     CharSequence getSummary(ConversationChannel conversation) {
209         return conversation.getNotificationChannelGroup() == null
210                 ? conversation.getNotificationChannel().getName()
211                 : mContext.getString(R.string.notification_conversation_summary,
212                         conversation.getNotificationChannel().getName(),
213                         conversation.getNotificationChannelGroup().getName());
214     }
215 
getTitle(ConversationChannel conversation)216     CharSequence getTitle(ConversationChannel conversation) {
217         ShortcutInfo si = conversation.getShortcutInfo();
218         return si.getLabel();
219     }
220 
getSubSettingLauncher(ConversationChannel conversation, CharSequence title)221     SubSettingLauncher getSubSettingLauncher(ConversationChannel conversation,
222             CharSequence title) {
223         Bundle channelArgs = new Bundle();
224         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, conversation.getUid());
225         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME,
226                 conversation.getShortcutInfo().getPackage());
227         channelArgs.putString(Settings.EXTRA_CHANNEL_ID,
228                 conversation.getNotificationChannel().getId());
229         channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
230                 conversation.getShortcutInfo().getId());
231 
232         return new SubSettingLauncher(mContext)
233                 .setDestination(ChannelNotificationSettings.class.getName())
234                 .setArguments(channelArgs)
235                 .setExtras(channelArgs)
236                 .setUserHandle(UserHandle.getUserHandleForUid(conversation.getUid()))
237                 .setTitleText(title)
238                 .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_CONVERSATION_LIST_SETTINGS);
239     }
240 
241     @VisibleForTesting
242     Comparator<ConversationChannel> mConversationComparator =
243             new Comparator<ConversationChannel>() {
244                 private final Collator sCollator = Collator.getInstance();
245 
246                 @Override
247                 public int compare(ConversationChannel o1, ConversationChannel o2) {
248                     int labelComparison = 0;
249                     if (o1.getShortcutInfo().getLabel() != null
250                             && o2.getShortcutInfo().getLabel() != null) {
251                         labelComparison = sCollator.compare(
252                                 o1.getShortcutInfo().getLabel().toString(),
253                                 o2.getShortcutInfo().getLabel().toString());
254                     }
255 
256                     if (labelComparison == 0) {
257                         return o1.getNotificationChannel().getId().compareTo(
258                                 o2.getNotificationChannel().getId());
259                     }
260 
261                     return labelComparison;
262                 }
263             };
264 }
265