1 /*
2  * Copyright (C) 2017 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.NotificationChannel;
22 import android.app.NotificationChannelGroup;
23 import android.app.NotificationManager;
24 import android.content.Context;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ShortcutInfo;
27 import android.graphics.drawable.Drawable;
28 import android.os.UserManager;
29 import android.util.Log;
30 
31 import androidx.annotation.Nullable;
32 import androidx.preference.Preference;
33 
34 import com.android.settings.notification.NotificationBackend;
35 import com.android.settingslib.RestrictedLockUtils;
36 import com.android.settingslib.core.AbstractPreferenceController;
37 
38 import java.util.Comparator;
39 import java.util.List;
40 import java.util.Objects;
41 
42 /**
43  * Parent class for preferences appearing on notification setting pages at the app,
44  * notification channel group, or notification channel level.
45  */
46 public abstract class NotificationPreferenceController extends AbstractPreferenceController {
47     private static final String TAG = "ChannelPrefContr";
48     @Nullable
49     protected NotificationChannel mChannel;
50     @Nullable
51     protected NotificationChannelGroup mChannelGroup;
52     protected RestrictedLockUtils.EnforcedAdmin mAdmin;
53     protected NotificationBackend.AppRow mAppRow;
54     protected final NotificationManager mNm;
55     protected final NotificationBackend mBackend;
56     protected final Context mContext;
57     protected final UserManager mUm;
58     protected final PackageManager mPm;
59     protected Preference mPreference;
60     @Nullable
61     protected Drawable mConversationDrawable;
62     @Nullable
63     protected ShortcutInfo mConversationInfo;
64     protected List<String> mPreferenceFilter;
65 
66     boolean overrideCanBlock;
67     boolean overrideCanConfigure;
68     boolean overrideCanBlockValue;
69     boolean overrideCanConfigureValue;
70 
NotificationPreferenceController(Context context, NotificationBackend backend)71     public NotificationPreferenceController(Context context, NotificationBackend backend) {
72         super(context);
73         mContext = context;
74         mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
75         mBackend = backend;
76         mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
77         mPm = mContext.getPackageManager();
78     }
79 
80     /**
81      * Returns true if field's parent object is not blocked.
82      */
83     @Override
isAvailable()84     public boolean isAvailable() {
85         if (mAppRow == null) {
86             return false;
87         }
88         if (mAppRow.banned) {
89             return false;
90         }
91         if (mChannelGroup != null) {
92             if (mChannelGroup.isBlocked()) {
93                 return false;
94             }
95         }
96         if (mChannel != null) {
97             if (mPreferenceFilter != null && !isIncludedInFilter()) {
98                 return false;
99             }
100             return mChannel.getImportance() != IMPORTANCE_NONE;
101         }
102         return true;
103     }
104 
onResume(NotificationBackend.AppRow appRow, @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group, Drawable conversationDrawable, ShortcutInfo conversationInfo, RestrictedLockUtils.EnforcedAdmin admin, List<String> preferenceFilter)105     protected void onResume(NotificationBackend.AppRow appRow,
106             @Nullable NotificationChannel channel, @Nullable NotificationChannelGroup group,
107             Drawable conversationDrawable,
108             ShortcutInfo conversationInfo,
109             RestrictedLockUtils.EnforcedAdmin admin,
110             List<String> preferenceFilter) {
111         mAppRow = appRow;
112         mChannel = channel;
113         mChannelGroup = group;
114         mAdmin = admin;
115         mConversationDrawable = conversationDrawable;
116         mConversationInfo = conversationInfo;
117         mPreferenceFilter = preferenceFilter;
118     }
119 
isIncludedInFilter()120     abstract boolean isIncludedInFilter();
121 
checkCanBeVisible(int minImportanceVisible)122     protected boolean checkCanBeVisible(int minImportanceVisible) {
123         if (mChannel == null) {
124             Log.w(TAG, "No channel");
125             return false;
126         }
127 
128         int importance = mChannel.getImportance();
129         if (importance == NotificationManager.IMPORTANCE_UNSPECIFIED) {
130             return true;
131         }
132         return importance >= minImportanceVisible;
133     }
134 
saveChannel()135     protected void saveChannel() {
136         if (mChannel != null && mAppRow != null) {
137             mBackend.updateChannel(mAppRow.pkg, mAppRow.uid, mChannel);
138         }
139     }
140 
isChannelBlockable()141     protected boolean isChannelBlockable() {
142         return isChannelBlockable(mChannel);
143     }
144 
isChannelBlockable(NotificationChannel channel)145     protected boolean isChannelBlockable(NotificationChannel channel) {
146         if (overrideCanBlock) {
147             return overrideCanBlockValue;
148         }
149         if (overrideCanConfigure) {
150             return overrideCanConfigureValue;
151         }
152         if (channel != null && mAppRow != null) {
153             boolean locked = mAppRow.lockedImportance;
154             if (locked) {
155                 return channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE;
156             }
157 
158             return channel.isBlockable() || !mAppRow.systemApp
159                     || channel.getImportance() == IMPORTANCE_NONE;
160         }
161         return false;
162     }
163 
isAppBlockable()164     protected boolean isAppBlockable() {
165         if (overrideCanBlock) {
166             return overrideCanBlockValue;
167         }
168         if (overrideCanConfigure) {
169             return overrideCanConfigureValue;
170         }
171         if (mAppRow != null) {
172             boolean systemBlockable = !mAppRow.systemApp || (mAppRow.systemApp && mAppRow.banned);
173             return systemBlockable && !mAppRow.lockedImportance;
174         }
175         return true;
176     }
177 
isChannelConfigurable(NotificationChannel channel)178     protected boolean isChannelConfigurable(NotificationChannel channel) {
179         if (overrideCanConfigure) {
180             return overrideCanConfigureValue;
181         }
182         if (channel != null && mAppRow != null) {
183             boolean locked = mAppRow.lockedImportance;
184             return !locked || channel.isBlockable();
185         }
186         return false;
187     }
188 
isChannelGroupBlockable()189     protected boolean isChannelGroupBlockable() {
190         return isChannelGroupBlockable(mChannelGroup);
191     }
192 
isChannelGroupBlockable(NotificationChannelGroup group)193     protected boolean isChannelGroupBlockable(NotificationChannelGroup group) {
194         if (overrideCanBlock) {
195             return overrideCanBlockValue;
196         }
197         if (overrideCanConfigure) {
198             return overrideCanConfigureValue;
199         }
200         if (group != null && mAppRow != null) {
201             if (!mAppRow.systemApp && !mAppRow.lockedImportance) {
202                 return true;
203             }
204 
205             return group.isBlocked();
206         }
207         return false;
208     }
209 
hasValidGroup()210     protected boolean hasValidGroup() {
211         return mChannelGroup != null;
212     }
213 
isDefaultChannel()214     protected final boolean isDefaultChannel() {
215         if (mChannel == null) {
216             return false;
217         }
218         return Objects.equals(NotificationChannel.DEFAULT_CHANNEL_ID, mChannel.getId());
219     }
220 
setOverrideCanBlock(boolean canBlock)221     protected final void setOverrideCanBlock(boolean canBlock) {
222         overrideCanBlock = true;
223         overrideCanBlockValue = canBlock;
224     }
225 
setOverrideCanConfigure(boolean canConfigure)226     protected final void setOverrideCanConfigure(boolean canConfigure) {
227         overrideCanConfigure = true;
228         overrideCanConfigureValue = canConfigure;
229     }
230 
231     public static final Comparator<NotificationChannelGroup> CHANNEL_GROUP_COMPARATOR =
232             new Comparator<NotificationChannelGroup>() {
233         @Override
234         public int compare(NotificationChannelGroup left, NotificationChannelGroup right) {
235             // Non-grouped channels (in placeholder group with a null id) come last
236             if (left.getId() == null && right.getId() != null) {
237                 return 1;
238             } else if (right.getId() == null && left.getId() != null) {
239                 return -1;
240             }
241             return left.getId().compareTo(right.getId());
242         }
243     };
244 
245     public static final Comparator<NotificationChannel> CHANNEL_COMPARATOR = (left, right) -> {
246         if (left.isDeleted() != right.isDeleted()) {
247             return Boolean.compare(left.isDeleted(), right.isDeleted());
248         } else if (left.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
249             // Uncategorized/miscellaneous legacy channel goes last
250             return 1;
251         } else if (right.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
252             return -1;
253         }
254 
255         return left.getId().compareTo(right.getId());
256     };
257 }
258