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 android.app.NotificationChannel;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.pm.ParceledListSlice;
23 import android.content.pm.ShortcutInfo;
24 import android.os.AsyncTask;
25 import android.os.Bundle;
26 import android.provider.Settings;
27 import android.service.notification.ConversationChannelWrapper;
28 
29 import androidx.preference.Preference;
30 import androidx.preference.PreferenceCategory;
31 
32 import com.android.settings.R;
33 import com.android.settings.applications.AppInfoBase;
34 import com.android.settings.core.SubSettingLauncher;
35 import com.android.settings.notification.NotificationBackend;
36 import com.android.settingslib.widget.AppPreference;
37 
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Comparator;
41 import java.util.List;
42 
43 public class AppConversationListPreferenceController extends NotificationPreferenceController {
44 
45     private static final String KEY = "conversations";
46     public static final String ARG_FROM_SETTINGS = "fromSettings";
47 
48     protected List<ConversationChannelWrapper> mConversations = new ArrayList<>();
49     protected PreferenceCategory mPreference;
50 
AppConversationListPreferenceController(Context context, NotificationBackend backend)51     public AppConversationListPreferenceController(Context context, NotificationBackend backend) {
52         super(context, backend);
53     }
54 
55     @Override
getPreferenceKey()56     public String getPreferenceKey() {
57         return KEY;
58     }
59 
60     @Override
isAvailable()61     public boolean isAvailable() {
62         if (mAppRow == null) {
63             return false;
64         }
65         if (mAppRow.banned) {
66             return false;
67         }
68         if (mChannel != null) {
69             if (mBackend.onlyHasDefaultChannel(mAppRow.pkg, mAppRow.uid)
70                     || NotificationChannel.DEFAULT_CHANNEL_ID.equals(mChannel.getId())) {
71                 return false;
72             }
73         }
74         return mBackend.hasSentValidMsg(mAppRow.pkg, mAppRow.uid) || mBackend.isInInvalidMsgState(
75                 mAppRow.pkg, mAppRow.uid);
76     }
77 
78     @Override
isIncludedInFilter()79     boolean isIncludedInFilter() {
80         return false;
81     }
82 
83     @Override
updateState(Preference preference)84     public void updateState(Preference preference) {
85         mPreference = (PreferenceCategory) preference;
86         loadConversationsAndPopulate();
87     }
88 
loadConversationsAndPopulate()89     protected void loadConversationsAndPopulate() {
90         if (mAppRow == null) {
91             return;
92         }
93         // Load channel settings
94         new AsyncTask<Void, Void, Void>() {
95             @Override
96             protected Void doInBackground(Void... unused) {
97                 ParceledListSlice<ConversationChannelWrapper> list =
98                         mBackend.getConversations(mAppRow.pkg, mAppRow.uid);
99                 if (list != null) {
100                     mConversations = filterAndSortConversations(list.getList());
101                 }
102                 return null;
103             }
104 
105             @Override
106             protected void onPostExecute(Void unused) {
107                 if (mContext == null) {
108                     return;
109                 }
110                 populateList();
111             }
112         }.execute();
113     }
114 
filterAndSortConversations( List<ConversationChannelWrapper> conversations)115     protected List<ConversationChannelWrapper> filterAndSortConversations(
116             List<ConversationChannelWrapper> conversations) {
117         Collections.sort(conversations, mConversationComparator);
118         return conversations;
119     }
120 
getTitleResId()121     protected int getTitleResId() {
122         return R.string.conversations_category_title;
123     }
124 
populateList()125     protected void populateList() {
126         if (mPreference == null) {
127             return;
128         }
129 
130         if (!mConversations.isEmpty()) {
131             // TODO: if preference has children, compare with newly loaded list
132             mPreference.removeAll();
133             mPreference.setTitle(getTitleResId());
134             populateConversations();
135         }
136     }
137 
populateConversations()138     private void populateConversations() {
139         for (ConversationChannelWrapper conversation : mConversations) {
140             if (conversation.getNotificationChannel().isDemoted()) {
141                 continue;
142             }
143             mPreference.addPreference(createConversationPref(conversation));
144         }
145     }
146 
createConversationPref(final ConversationChannelWrapper conversation)147     protected Preference createConversationPref(final ConversationChannelWrapper conversation) {
148         AppPreference pref = new AppPreference(mContext);
149         populateConversationPreference(conversation, pref);
150         return pref;
151     }
152 
populateConversationPreference(final ConversationChannelWrapper conversation, final Preference pref)153     protected void populateConversationPreference(final ConversationChannelWrapper conversation,
154             final Preference pref) {
155         ShortcutInfo si = conversation.getShortcutInfo();
156 
157         pref.setTitle(si != null
158                 ? si.getLabel()
159                 : conversation.getNotificationChannel().getName());
160         pref.setSummary(conversation.getNotificationChannel().getGroup() != null
161                 ? mContext.getString(R.string.notification_conversation_summary,
162                 conversation.getParentChannelLabel(), conversation.getGroupLabel())
163                 : conversation.getParentChannelLabel());
164         if (si != null) {
165             pref.setIcon(mBackend.getConversationDrawable(mContext, si, mAppRow.pkg, mAppRow.uid,
166                     conversation.getNotificationChannel().isImportantConversation()));
167         }
168         pref.setKey(conversation.getNotificationChannel().getId());
169 
170         Bundle channelArgs = new Bundle();
171         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid);
172         channelArgs.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg);
173         channelArgs.putString(Settings.EXTRA_CHANNEL_ID,
174                 conversation.getNotificationChannel().getParentChannelId());
175         channelArgs.putString(Settings.EXTRA_CONVERSATION_ID,
176                 conversation.getNotificationChannel().getConversationId());
177         channelArgs.putBoolean(ARG_FROM_SETTINGS, true);
178         pref.setIntent(new SubSettingLauncher(mContext)
179                 .setDestination(ChannelNotificationSettings.class.getName())
180                 .setArguments(channelArgs)
181                 .setExtras(channelArgs)
182                 .setTitleText(pref.getTitle())
183                 .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_APP_NOTIFICATION)
184                 .toIntent());
185     }
186 
187     protected Comparator<ConversationChannelWrapper> mConversationComparator =
188             (left, right) -> {
189                 if (left.getNotificationChannel().isImportantConversation()
190                         != right.getNotificationChannel().isImportantConversation()) {
191                     // important first
192                     return Boolean.compare(right.getNotificationChannel().isImportantConversation(),
193                             left.getNotificationChannel().isImportantConversation());
194                 }
195                 return left.getNotificationChannel().getId().compareTo(
196                         right.getNotificationChannel().getId());
197             };
198 }
199