• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2018, 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.server.notification;
18 
19 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
20 import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
21 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
22 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
23 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
24 import static android.app.NotificationManager.IMPORTANCE_NONE;
25 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
26 import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
27 
28 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
29 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
30 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
31 
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.annotation.UserIdInt;
36 import android.app.ActivityManager;
37 import android.app.AppOpsManager;
38 import android.app.Notification;
39 import android.app.NotificationChannel;
40 import android.app.NotificationChannelGroup;
41 import android.app.NotificationManager;
42 import android.content.Context;
43 import android.content.pm.ApplicationInfo;
44 import android.content.pm.PackageManager;
45 import android.content.pm.ParceledListSlice;
46 import android.metrics.LogMaker;
47 import android.os.Build;
48 import android.os.UserHandle;
49 import android.provider.Settings;
50 import android.service.notification.ConversationChannelWrapper;
51 import android.service.notification.NotificationListenerService;
52 import android.service.notification.RankingHelperProto;
53 import android.text.TextUtils;
54 import android.text.format.DateUtils;
55 import android.util.ArrayMap;
56 import android.util.ArraySet;
57 import android.util.IntArray;
58 import android.util.Pair;
59 import android.util.Slog;
60 import android.util.SparseBooleanArray;
61 import android.util.StatsEvent;
62 import android.util.TypedXmlPullParser;
63 import android.util.TypedXmlSerializer;
64 import android.util.proto.ProtoOutputStream;
65 
66 import com.android.internal.R;
67 import com.android.internal.annotations.VisibleForTesting;
68 import com.android.internal.logging.MetricsLogger;
69 import com.android.internal.util.Preconditions;
70 import com.android.internal.util.XmlUtils;
71 
72 import org.json.JSONArray;
73 import org.json.JSONException;
74 import org.json.JSONObject;
75 import org.xmlpull.v1.XmlPullParser;
76 import org.xmlpull.v1.XmlPullParserException;
77 
78 import java.io.IOException;
79 import java.io.PrintWriter;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.Collection;
83 import java.util.HashMap;
84 import java.util.List;
85 import java.util.Map;
86 import java.util.Objects;
87 import java.util.Set;
88 import java.util.concurrent.ConcurrentHashMap;
89 
90 public class PreferencesHelper implements RankingConfig {
91     private static final String TAG = "NotificationPrefHelper";
92     private static final int XML_VERSION = 2;
93     /** What version to check to do the upgrade for bubbles. */
94     private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
95     @VisibleForTesting
96     static final int UNKNOWN_UID = UserHandle.USER_NULL;
97     private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
98 
99     @VisibleForTesting
100     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000;
101 
102     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
103     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
104     private static final int NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT = 1000;
105     private static final int NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS = 30;
106 
107     @VisibleForTesting
108     static final String TAG_RANKING = "ranking";
109     private static final String TAG_PACKAGE = "package";
110     private static final String TAG_CHANNEL = "channel";
111     private static final String TAG_GROUP = "channelGroup";
112     private static final String TAG_DELEGATE = "delegate";
113     private static final String TAG_STATUS_ICONS = "silent_status_icons";
114 
115     private static final String ATT_VERSION = "version";
116     private static final String ATT_NAME = "name";
117     private static final String ATT_UID = "uid";
118     private static final String ATT_ID = "id";
119     private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
120     private static final String ATT_PRIORITY = "priority";
121     private static final String ATT_VISIBILITY = "visibility";
122     private static final String ATT_IMPORTANCE = "importance";
123     private static final String ATT_SHOW_BADGE = "show_badge";
124     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
125     private static final String ATT_ENABLED = "enabled";
126     private static final String ATT_USER_ALLOWED = "allowed";
127     private static final String ATT_HIDE_SILENT = "hide_gentle";
128     private static final String ATT_SENT_INVALID_MESSAGE = "sent_invalid_msg";
129     private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
130     private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
131 
132     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
133     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
134     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
135     @VisibleForTesting
136     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
137     private static final boolean DEFAULT_SHOW_BADGE = true;
138 
139     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
140     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
141 
142     static final boolean DEFAULT_BUBBLES_ENABLED = true;
143     @VisibleForTesting
144     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
145     static final boolean DEFAULT_MEDIA_NOTIFICATION_FILTERING = true;
146 
147     /**
148      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
149      * fields.
150      */
151     private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
152     private final SysUiStatsEvent.BuilderFactory mStatsEventBuilderFactory;
153 
154     /**
155      * All user-lockable fields for a given application.
156      */
157     @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
158     public @interface LockableAppFields {
159         int USER_LOCKED_IMPORTANCE = 0x00000001;
160         int USER_LOCKED_BUBBLE = 0x00000002;
161     }
162 
163     // pkg|uid => PackagePreferences
164     private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
165     // pkg|userId => PackagePreferences
166     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
167 
168     private final Context mContext;
169     private final PackageManager mPm;
170     private final RankingHandler mRankingHandler;
171     private final ZenModeHelper mZenModeHelper;
172     private final NotificationChannelLogger mNotificationChannelLogger;
173     private final AppOpsManager mAppOps;
174 
175     private SparseBooleanArray mBadgingEnabled;
176     private SparseBooleanArray mBubblesEnabled;
177     private SparseBooleanArray mLockScreenShowNotifications;
178     private SparseBooleanArray mLockScreenPrivateNotifications;
179     private boolean mIsMediaNotificationFilteringEnabled = DEFAULT_MEDIA_NOTIFICATION_FILTERING;
180     private boolean mAreChannelsBypassingDnd;
181     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
182 
183     private boolean mAllowInvalidShortcuts = false;
184 
185     private Map<String, List<String>> mOemLockedApps = new HashMap();
186 
187     private int mCurrentUserId = UserHandle.USER_NULL;
188 
PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, SysUiStatsEvent.BuilderFactory statsEventBuilderFactory)189     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
190             ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger,
191             AppOpsManager appOpsManager,
192             SysUiStatsEvent.BuilderFactory statsEventBuilderFactory) {
193         mContext = context;
194         mZenModeHelper = zenHelper;
195         mRankingHandler = rankingHandler;
196         mPm = pm;
197         mNotificationChannelLogger = notificationChannelLogger;
198         mAppOps = appOpsManager;
199         mStatsEventBuilderFactory = statsEventBuilderFactory;
200 
201         updateBadgingEnabled();
202         updateBubblesEnabled();
203         updateMediaNotificationFilteringEnabled();
204         mCurrentUserId = ActivityManager.getCurrentUser();
205         syncChannelsBypassingDnd();
206     }
207 
readXml(TypedXmlPullParser parser, boolean forRestore, int userId)208     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
209             throws XmlPullParserException, IOException {
210         int type = parser.getEventType();
211         if (type != XmlPullParser.START_TAG) return;
212         String tag = parser.getName();
213         if (!TAG_RANKING.equals(tag)) return;
214 
215         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
216         boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
217         synchronized (mPackagePreferences) {
218             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
219                 tag = parser.getName();
220                 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
221                     return;
222                 }
223                 if (type == XmlPullParser.START_TAG) {
224                     if (TAG_STATUS_ICONS.equals(tag)) {
225                         if (forRestore && userId != UserHandle.USER_SYSTEM) {
226                             continue;
227                         }
228                         mHideSilentStatusBarIcons = parser.getAttributeBoolean(null,
229                                 ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
230                     } else if (TAG_PACKAGE.equals(tag)) {
231                         int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
232                         String name = parser.getAttributeValue(null, ATT_NAME);
233                         if (!TextUtils.isEmpty(name)) {
234                             if (forRestore) {
235                                 try {
236                                     uid = mPm.getPackageUidAsUser(name, userId);
237                                 } catch (PackageManager.NameNotFoundException e) {
238                                     // noop
239                                 }
240                             }
241                             boolean skipWarningLogged = false;
242                             boolean hasSAWPermission = false;
243                             if (upgradeForBubbles && uid != UNKNOWN_UID) {
244                                 hasSAWPermission = mAppOps.noteOpNoThrow(
245                                         OP_SYSTEM_ALERT_WINDOW, uid, name, null,
246                                         "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
247                             }
248                             int bubblePref = hasSAWPermission
249                                     ? BUBBLE_PREFERENCE_ALL
250                                     : parser.getAttributeInt(
251                                             null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
252 
253                             PackagePreferences r = getOrCreatePackagePreferencesLocked(
254                                     name, userId, uid,
255                                     parser.getAttributeInt(
256                                             null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
257                                     parser.getAttributeInt(
258                                             null, ATT_PRIORITY, DEFAULT_PRIORITY),
259                                     parser.getAttributeInt(
260                                             null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
261                                     parser.getAttributeBoolean(
262                                             null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
263                                     bubblePref);
264                             r.importance = parser.getAttributeInt(
265                                     null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
266                             r.priority = parser.getAttributeInt(
267                                     null, ATT_PRIORITY, DEFAULT_PRIORITY);
268                             r.visibility = parser.getAttributeInt(
269                                     null, ATT_VISIBILITY, DEFAULT_VISIBILITY);
270                             r.showBadge = parser.getAttributeBoolean(
271                                     null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
272                             r.lockedAppFields = parser.getAttributeInt(
273                                     null, ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
274                             r.hasSentInvalidMessage = parser.getAttributeBoolean(
275                                     null, ATT_SENT_INVALID_MESSAGE, false);
276                             r.hasSentValidMessage = parser.getAttributeBoolean(
277                                     null, ATT_SENT_VALID_MESSAGE, false);
278                             r.userDemotedMsgApp = parser.getAttributeBoolean(
279                                     null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
280 
281                             final int innerDepth = parser.getDepth();
282                             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
283                                     && (type != XmlPullParser.END_TAG
284                                     || parser.getDepth() > innerDepth)) {
285                                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
286                                     continue;
287                                 }
288 
289                                 String tagName = parser.getName();
290                                 // Channel groups
291                                 if (TAG_GROUP.equals(tagName)) {
292                                     String id = parser.getAttributeValue(null, ATT_ID);
293                                     CharSequence groupName = parser.getAttributeValue(null,
294                                             ATT_NAME);
295                                     if (!TextUtils.isEmpty(id)) {
296                                         NotificationChannelGroup group
297                                                 = new NotificationChannelGroup(id, groupName);
298                                         group.populateFromXml(parser);
299                                         r.groups.put(id, group);
300                                     }
301                                 }
302                                 // Channels
303                                 if (TAG_CHANNEL.equals(tagName)) {
304                                     if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
305                                         if (!skipWarningLogged) {
306                                             Slog.w(TAG, "Skipping further channels for " + r.pkg
307                                                     + "; app has too many");
308                                             skipWarningLogged = true;
309                                         }
310                                         continue;
311                                     }
312                                     String id = parser.getAttributeValue(null, ATT_ID);
313                                     String channelName = parser.getAttributeValue(null, ATT_NAME);
314                                     int channelImportance = parser.getAttributeInt(
315                                             null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
316                                     if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
317                                         NotificationChannel channel = new NotificationChannel(id,
318                                                 channelName, channelImportance);
319                                         if (forRestore) {
320                                             channel.populateFromXmlForRestore(parser, mContext);
321                                         } else {
322                                             channel.populateFromXml(parser);
323                                         }
324                                         channel.setImportanceLockedByCriticalDeviceFunction(
325                                                 r.defaultAppLockedImportance);
326                                         channel.setImportanceLockedByOEM(r.oemLockedImportance);
327                                         if (!channel.isImportanceLockedByOEM()) {
328                                             if (r.oemLockedChannels.contains(channel.getId())) {
329                                                 channel.setImportanceLockedByOEM(true);
330                                             }
331                                         }
332 
333                                         if (isShortcutOk(channel) && isDeletionOk(channel)
334                                                 && !channel.isSoundMissing()) {
335                                             r.channels.put(id, channel);
336                                         }
337                                     }
338                                 }
339                                 // Delegate
340                                 if (TAG_DELEGATE.equals(tagName)) {
341                                     int delegateId =
342                                             parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
343                                     String delegateName =
344                                             XmlUtils.readStringAttribute(parser, ATT_NAME);
345                                     boolean delegateEnabled = parser.getAttributeBoolean(
346                                             null, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
347                                     boolean userAllowed = parser.getAttributeBoolean(
348                                             null, ATT_USER_ALLOWED, Delegate.DEFAULT_USER_ALLOWED);
349                                     Delegate d = null;
350                                     if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
351                                             delegateName)) {
352                                         d = new Delegate(
353                                                 delegateName, delegateId, delegateEnabled,
354                                                 userAllowed);
355                                     }
356                                     r.delegate = d;
357                                 }
358 
359                             }
360 
361                             try {
362                                 deleteDefaultChannelIfNeededLocked(r);
363                             } catch (PackageManager.NameNotFoundException e) {
364                                 Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e);
365                             }
366                         }
367                     }
368                 }
369             }
370         }
371         throw new IllegalStateException("Failed to reach END_DOCUMENT");
372     }
373 
isShortcutOk(NotificationChannel channel)374     private boolean isShortcutOk(NotificationChannel channel) {
375         boolean isInvalidShortcutChannel =
376                 channel.getConversationId() != null &&
377                         channel.getConversationId().contains(
378                                 PLACEHOLDER_CONVERSATION_ID);
379         return mAllowInvalidShortcuts || (!mAllowInvalidShortcuts && !isInvalidShortcutChannel);
380     }
381 
isDeletionOk(NotificationChannel nc)382     private boolean isDeletionOk(NotificationChannel nc) {
383         if (!nc.isDeleted()) {
384             return true;
385         }
386         long boundary = System.currentTimeMillis() - (
387                 DateUtils.DAY_IN_MILLIS * NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS);
388         if (nc.getDeletedTimeMs() <= boundary) {
389             return false;
390         }
391         return true;
392     }
393 
getPackagePreferencesLocked(String pkg, int uid)394     private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) {
395         final String key = packagePreferencesKey(pkg, uid);
396         return mPackagePreferences.get(key);
397     }
398 
getOrCreatePackagePreferencesLocked(String pkg, int uid)399     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
400             int uid) {
401         return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
402                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
403                 DEFAULT_BUBBLE_PREFERENCE);
404     }
405 
getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid)406     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
407             @UserIdInt int userId, int uid) {
408         return getOrCreatePackagePreferencesLocked(pkg, userId, uid,
409                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
410                 DEFAULT_BUBBLE_PREFERENCE);
411     }
412 
getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference)413     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
414             @UserIdInt int userId, int uid, int importance, int priority, int visibility,
415             boolean showBadge, int bubblePreference) {
416         final String key = packagePreferencesKey(pkg, uid);
417         PackagePreferences
418                 r = (uid == UNKNOWN_UID)
419                 ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
420                 : mPackagePreferences.get(key);
421         if (r == null) {
422             r = new PackagePreferences();
423             r.pkg = pkg;
424             r.uid = uid;
425             r.importance = importance;
426             r.priority = priority;
427             r.visibility = visibility;
428             r.showBadge = showBadge;
429             r.bubblePreference = bubblePreference;
430             if (mOemLockedApps.containsKey(r.pkg)) {
431                 List<String> channels = mOemLockedApps.get(r.pkg);
432                 if (channels == null || channels.isEmpty()) {
433                     r.oemLockedImportance = true;
434                 } else {
435                     r.oemLockedChannels = channels;
436                 }
437             }
438 
439             try {
440                 createDefaultChannelIfNeededLocked(r);
441             } catch (PackageManager.NameNotFoundException e) {
442                 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
443             }
444 
445             if (r.uid == UNKNOWN_UID) {
446                 mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
447             } else {
448                 mPackagePreferences.put(key, r);
449             }
450         }
451         return r;
452     }
453 
shouldHaveDefaultChannel(PackagePreferences r)454     private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
455             PackageManager.NameNotFoundException {
456         final int userId = UserHandle.getUserId(r.uid);
457         final ApplicationInfo applicationInfo =
458                 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
459         if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
460             // O apps should not have the default channel.
461             return false;
462         }
463 
464         // Otherwise, this app should have the default channel.
465         return true;
466     }
467 
deleteDefaultChannelIfNeededLocked(PackagePreferences r)468     private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
469             PackageManager.NameNotFoundException {
470         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
471             // Not present
472             return false;
473         }
474 
475         if (shouldHaveDefaultChannel(r)) {
476             // Keep the default channel until upgraded.
477             return false;
478         }
479 
480         // Remove Default Channel.
481         r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
482 
483         return true;
484     }
485 
createDefaultChannelIfNeededLocked(PackagePreferences r)486     private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws
487             PackageManager.NameNotFoundException {
488         if (r.uid == UNKNOWN_UID) {
489             return false;
490         }
491 
492         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
493             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
494                     com.android.internal.R.string.default_notification_channel_label));
495             return false;
496         }
497 
498         if (!shouldHaveDefaultChannel(r)) {
499             // Keep the default channel until upgraded.
500             return false;
501         }
502 
503         // Create Default Channel
504         NotificationChannel channel;
505         channel = new NotificationChannel(
506                 NotificationChannel.DEFAULT_CHANNEL_ID,
507                 mContext.getString(R.string.default_notification_channel_label),
508                 r.importance);
509         channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
510         channel.setLockscreenVisibility(r.visibility);
511         if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
512             channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
513         }
514         if (r.priority != DEFAULT_PRIORITY) {
515             channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
516         }
517         if (r.visibility != DEFAULT_VISIBILITY) {
518             channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
519         }
520         r.channels.put(channel.getId(), channel);
521 
522         return true;
523     }
524 
writeXml(TypedXmlSerializer out, boolean forBackup, int userId)525     public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
526         out.startTag(null, TAG_RANKING);
527         out.attributeInt(null, ATT_VERSION, XML_VERSION);
528         if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS
529                 && (!forBackup || userId == UserHandle.USER_SYSTEM)) {
530             out.startTag(null, TAG_STATUS_ICONS);
531             out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
532             out.endTag(null, TAG_STATUS_ICONS);
533         }
534 
535         synchronized (mPackagePreferences) {
536             final int N = mPackagePreferences.size();
537             for (int i = 0; i < N; i++) {
538                 final PackagePreferences r = mPackagePreferences.valueAt(i);
539                 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
540                     continue;
541                 }
542                 final boolean hasNonDefaultSettings =
543                         r.importance != DEFAULT_IMPORTANCE
544                                 || r.priority != DEFAULT_PRIORITY
545                                 || r.visibility != DEFAULT_VISIBILITY
546                                 || r.showBadge != DEFAULT_SHOW_BADGE
547                                 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
548                                 || r.channels.size() > 0
549                                 || r.groups.size() > 0
550                                 || r.delegate != null
551                                 || r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE
552                                 || r.hasSentInvalidMessage
553                                 || r.userDemotedMsgApp
554                                 || r.hasSentValidMessage;
555                 if (hasNonDefaultSettings) {
556                     out.startTag(null, TAG_PACKAGE);
557                     out.attribute(null, ATT_NAME, r.pkg);
558                     if (r.importance != DEFAULT_IMPORTANCE) {
559                         out.attributeInt(null, ATT_IMPORTANCE, r.importance);
560                     }
561                     if (r.priority != DEFAULT_PRIORITY) {
562                         out.attributeInt(null, ATT_PRIORITY, r.priority);
563                     }
564                     if (r.visibility != DEFAULT_VISIBILITY) {
565                         out.attributeInt(null, ATT_VISIBILITY, r.visibility);
566                     }
567                     if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
568                         out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
569                     }
570                     out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
571                     out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
572                             r.lockedAppFields);
573                     out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
574                             r.hasSentInvalidMessage);
575                     out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
576                             r.hasSentValidMessage);
577                     out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
578                             r.userDemotedMsgApp);
579 
580                     if (!forBackup) {
581                         out.attributeInt(null, ATT_UID, r.uid);
582                     }
583 
584                     if (r.delegate != null) {
585                         out.startTag(null, TAG_DELEGATE);
586 
587                         out.attribute(null, ATT_NAME, r.delegate.mPkg);
588                         out.attributeInt(null, ATT_UID, r.delegate.mUid);
589                         if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
590                             out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
591                         }
592                         if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
593                             out.attributeBoolean(null, ATT_USER_ALLOWED, r.delegate.mUserAllowed);
594                         }
595                         out.endTag(null, TAG_DELEGATE);
596                     }
597 
598                     for (NotificationChannelGroup group : r.groups.values()) {
599                         group.writeXml(out);
600                     }
601 
602                     for (NotificationChannel channel : r.channels.values()) {
603                         if (forBackup) {
604                             if (!channel.isDeleted()) {
605                                 channel.writeXmlForBackup(out, mContext);
606                             }
607                         } else {
608                             channel.writeXml(out);
609                         }
610                     }
611 
612                     out.endTag(null, TAG_PACKAGE);
613                 }
614             }
615         }
616         out.endTag(null, TAG_RANKING);
617     }
618 
619     /**
620      * Sets whether bubbles are allowed.
621      *
622      * @param pkg the package to allow or not allow bubbles for.
623      * @param uid the uid to allow or not allow bubbles for.
624      * @param bubblePreference whether bubbles are allowed.
625      */
setBubblesAllowed(String pkg, int uid, int bubblePreference)626     public void setBubblesAllowed(String pkg, int uid, int bubblePreference) {
627         boolean changed = false;
628         synchronized (mPackagePreferences) {
629             PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
630             changed = p.bubblePreference != bubblePreference;
631             p.bubblePreference = bubblePreference;
632             p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
633         }
634         if (changed) {
635             updateConfig();
636         }
637     }
638 
639     /**
640      * Whether bubbles are allowed.
641      *
642      * @param pkg the package to check if bubbles are allowed for
643      * @param uid the uid to check if bubbles are allowed for.
644      * @return whether bubbles are allowed.
645      */
646     @Override
getBubblePreference(String pkg, int uid)647     public int getBubblePreference(String pkg, int uid) {
648         synchronized (mPackagePreferences) {
649             return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference;
650         }
651     }
652 
getAppLockedFields(String pkg, int uid)653     public int getAppLockedFields(String pkg, int uid) {
654         synchronized (mPackagePreferences) {
655             return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields;
656         }
657     }
658 
659     /**
660      * Gets importance.
661      */
662     @Override
getImportance(String packageName, int uid)663     public int getImportance(String packageName, int uid) {
664         synchronized (mPackagePreferences) {
665             return getOrCreatePackagePreferencesLocked(packageName, uid).importance;
666         }
667     }
668 
669     /**
670      * Returns whether the importance of the corresponding notification is user-locked and shouldn't
671      * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
672      * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
673      */
getIsAppImportanceLocked(String packageName, int uid)674     public boolean getIsAppImportanceLocked(String packageName, int uid) {
675         synchronized (mPackagePreferences) {
676             int userLockedFields = getOrCreatePackagePreferencesLocked(packageName, uid).lockedAppFields;
677             return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
678         }
679     }
680 
681     @Override
canShowBadge(String packageName, int uid)682     public boolean canShowBadge(String packageName, int uid) {
683         synchronized (mPackagePreferences) {
684             return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;
685         }
686     }
687 
688     @Override
setShowBadge(String packageName, int uid, boolean showBadge)689     public void setShowBadge(String packageName, int uid, boolean showBadge) {
690         synchronized (mPackagePreferences) {
691             getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge;
692         }
693         updateConfig();
694     }
695 
isInInvalidMsgState(String packageName, int uid)696     public boolean isInInvalidMsgState(String packageName, int uid) {
697         synchronized (mPackagePreferences) {
698             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
699             return r.hasSentInvalidMessage && !r.hasSentValidMessage;
700         }
701     }
702 
hasUserDemotedInvalidMsgApp(String packageName, int uid)703     public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) {
704         synchronized (mPackagePreferences) {
705             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
706             return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false;
707         }
708     }
709 
setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted)710     public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) {
711         synchronized (mPackagePreferences) {
712             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
713             r.userDemotedMsgApp = isDemoted;
714         }
715     }
716 
setInvalidMessageSent(String packageName, int uid)717     public boolean setInvalidMessageSent(String packageName, int uid) {
718         synchronized (mPackagePreferences) {
719             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
720             boolean valueChanged = r.hasSentInvalidMessage == false;
721             r.hasSentInvalidMessage = true;
722 
723             return valueChanged;
724         }
725     }
726 
setValidMessageSent(String packageName, int uid)727     public boolean setValidMessageSent(String packageName, int uid) {
728         synchronized (mPackagePreferences) {
729             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
730             boolean valueChanged = r.hasSentValidMessage == false;
731             r.hasSentValidMessage = true;
732 
733             return valueChanged;
734         }
735     }
736 
737     @VisibleForTesting
hasSentInvalidMsg(String packageName, int uid)738     boolean hasSentInvalidMsg(String packageName, int uid) {
739         synchronized (mPackagePreferences) {
740             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
741             return r.hasSentInvalidMessage;
742         }
743     }
744 
745     @VisibleForTesting
hasSentValidMsg(String packageName, int uid)746     boolean hasSentValidMsg(String packageName, int uid) {
747         synchronized (mPackagePreferences) {
748             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
749             return r.hasSentValidMessage;
750         }
751     }
752 
753     @VisibleForTesting
didUserEverDemoteInvalidMsgApp(String packageName, int uid)754     boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) {
755         synchronized (mPackagePreferences) {
756             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
757             return r.userDemotedMsgApp;
758         }
759     }
760 
761     @Override
isGroupBlocked(String packageName, int uid, String groupId)762     public boolean isGroupBlocked(String packageName, int uid, String groupId) {
763         if (groupId == null) {
764             return false;
765         }
766         synchronized (mPackagePreferences) {
767             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
768             NotificationChannelGroup group = r.groups.get(groupId);
769             if (group == null) {
770                 return false;
771             }
772             return group.isBlocked();
773         }
774     }
775 
getPackagePriority(String pkg, int uid)776     int getPackagePriority(String pkg, int uid) {
777         synchronized (mPackagePreferences) {
778             return getOrCreatePackagePreferencesLocked(pkg, uid).priority;
779         }
780     }
781 
getPackageVisibility(String pkg, int uid)782     int getPackageVisibility(String pkg, int uid) {
783         synchronized (mPackagePreferences) {
784             return getOrCreatePackagePreferencesLocked(pkg, uid).visibility;
785         }
786     }
787 
788     @Override
createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp)789     public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
790             boolean fromTargetApp) {
791         Objects.requireNonNull(pkg);
792         Objects.requireNonNull(group);
793         Objects.requireNonNull(group.getId());
794         Objects.requireNonNull(!TextUtils.isEmpty(group.getName()));
795         synchronized (mPackagePreferences) {
796             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
797             if (r == null) {
798                 throw new IllegalArgumentException("Invalid package");
799             }
800             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
801             if (oldGroup != null) {
802                 group.setChannels(oldGroup.getChannels());
803 
804                 // apps can't update the blocked status or app overlay permission
805                 if (fromTargetApp) {
806                     group.setBlocked(oldGroup.isBlocked());
807                     group.unlockFields(group.getUserLockedFields());
808                     group.lockFields(oldGroup.getUserLockedFields());
809                 } else {
810                     // but the system can
811                     if (group.isBlocked() != oldGroup.isBlocked()) {
812                         group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
813                         updateChannelsBypassingDnd();
814                     }
815                 }
816             }
817             if (!group.equals(oldGroup)) {
818                 // will log for new entries as well as name/description changes
819                 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
820                 mNotificationChannelLogger.logNotificationChannelGroup(group, uid, pkg,
821                         oldGroup == null,
822                         (oldGroup != null) && oldGroup.isBlocked());
823             }
824             r.groups.put(group.getId(), group);
825         }
826     }
827 
828     @Override
createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess)829     public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
830             boolean fromTargetApp, boolean hasDndAccess) {
831         Objects.requireNonNull(pkg);
832         Objects.requireNonNull(channel);
833         Objects.requireNonNull(channel.getId());
834         Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
835         boolean needsPolicyFileChange = false, wasUndeleted = false;
836         synchronized (mPackagePreferences) {
837             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
838             if (r == null) {
839                 throw new IllegalArgumentException("Invalid package");
840             }
841             if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
842                 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
843             }
844             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
845                 throw new IllegalArgumentException("Reserved id");
846             }
847             NotificationChannel existing = r.channels.get(channel.getId());
848             if (existing != null && fromTargetApp) {
849                 // Actually modifying an existing channel - keep most of the existing settings
850                 if (existing.isDeleted()) {
851                     // The existing channel was deleted - undelete it.
852                     existing.setDeleted(false);
853                     existing.setDeletedTimeMs(-1);
854                     needsPolicyFileChange = true;
855                     wasUndeleted = true;
856 
857                     // log a resurrected channel as if it's new again
858                     MetricsLogger.action(getChannelLog(channel, pkg).setType(
859                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
860                     mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
861                 }
862 
863                 if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
864                     existing.setName(channel.getName().toString());
865                     needsPolicyFileChange = true;
866                 }
867                 if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
868                     existing.setDescription(channel.getDescription());
869                     needsPolicyFileChange = true;
870                 }
871                 if (channel.isBlockable() != existing.isBlockable()) {
872                     existing.setBlockable(channel.isBlockable());
873                     needsPolicyFileChange = true;
874                 }
875                 if (channel.getGroup() != null && existing.getGroup() == null) {
876                     existing.setGroup(channel.getGroup());
877                     needsPolicyFileChange = true;
878                 }
879 
880                 // Apps are allowed to downgrade channel importance if the user has not changed any
881                 // fields on this channel yet.
882                 final int previousExistingImportance = existing.getImportance();
883                 final int previousLoggingImportance =
884                         NotificationChannelLogger.getLoggingImportance(existing);
885                 if (existing.getUserLockedFields() == 0 &&
886                         channel.getImportance() < existing.getImportance()) {
887                     existing.setImportance(channel.getImportance());
888                     needsPolicyFileChange = true;
889                 }
890 
891                 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
892                 // fields on the channel yet
893                 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
894                     boolean bypassDnd = channel.canBypassDnd();
895                     if (bypassDnd != existing.canBypassDnd() || wasUndeleted) {
896                         existing.setBypassDnd(bypassDnd);
897                         needsPolicyFileChange = true;
898 
899                         if (bypassDnd != mAreChannelsBypassingDnd
900                                 || previousExistingImportance != existing.getImportance()) {
901                             updateChannelsBypassingDnd();
902                         }
903                     }
904                 }
905 
906                 if (existing.getOriginalImportance() == IMPORTANCE_UNSPECIFIED) {
907                     existing.setOriginalImportance(channel.getImportance());
908                     needsPolicyFileChange = true;
909                 }
910 
911                 updateConfig();
912                 if (needsPolicyFileChange && !wasUndeleted) {
913                     mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg,
914                             previousLoggingImportance, false);
915                 }
916                 return needsPolicyFileChange;
917             }
918 
919             if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
920                 throw new IllegalStateException("Limit exceed; cannot create more channels");
921             }
922 
923             needsPolicyFileChange = true;
924 
925             if (channel.getImportance() < IMPORTANCE_NONE
926                     || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
927                 throw new IllegalArgumentException("Invalid importance level");
928             }
929 
930             // Reset fields that apps aren't allowed to set.
931             if (fromTargetApp && !hasDndAccess) {
932                 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
933             }
934             if (fromTargetApp) {
935                 channel.setLockscreenVisibility(r.visibility);
936                 channel.setAllowBubbles(existing != null
937                         ? existing.getAllowBubbles()
938                         : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
939             }
940             clearLockedFieldsLocked(channel);
941             channel.setImportanceLockedByOEM(r.oemLockedImportance);
942             if (!channel.isImportanceLockedByOEM()) {
943                 if (r.oemLockedChannels.contains(channel.getId())) {
944                     channel.setImportanceLockedByOEM(true);
945                 }
946             }
947             channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
948             if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
949                 channel.setLockscreenVisibility(
950                         NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
951             }
952             if (!r.showBadge) {
953                 channel.setShowBadge(false);
954             }
955             channel.setOriginalImportance(channel.getImportance());
956 
957             // validate parent
958             if (channel.getParentChannelId() != null) {
959                 Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
960                         "Tried to create a conversation channel without a preexisting parent");
961             }
962 
963             r.channels.put(channel.getId(), channel);
964             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
965                 updateChannelsBypassingDnd();
966             }
967             MetricsLogger.action(getChannelLog(channel, pkg).setType(
968                     com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
969             mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
970         }
971 
972         return needsPolicyFileChange;
973     }
974 
clearLockedFieldsLocked(NotificationChannel channel)975     void clearLockedFieldsLocked(NotificationChannel channel) {
976         channel.unlockFields(channel.getUserLockedFields());
977     }
978 
unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId)979     void unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId) {
980         Objects.requireNonNull(updatedChannelId);
981         synchronized (mPackagePreferences) {
982             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
983             if (r == null) {
984                 throw new IllegalArgumentException("Invalid package");
985             }
986 
987             NotificationChannel channel = r.channels.get(updatedChannelId);
988             if (channel == null || channel.isDeleted()) {
989                 throw new IllegalArgumentException("Channel does not exist");
990             }
991             channel.unlockFields(USER_LOCKED_IMPORTANCE);
992         }
993     }
994 
995 
996     @Override
updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, boolean fromUser)997     public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
998             boolean fromUser) {
999         Objects.requireNonNull(updatedChannel);
1000         Objects.requireNonNull(updatedChannel.getId());
1001         synchronized (mPackagePreferences) {
1002             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1003             if (r == null) {
1004                 throw new IllegalArgumentException("Invalid package");
1005             }
1006             NotificationChannel channel = r.channels.get(updatedChannel.getId());
1007             if (channel == null || channel.isDeleted()) {
1008                 throw new IllegalArgumentException("Channel does not exist");
1009             }
1010             if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
1011                 updatedChannel.setLockscreenVisibility(
1012                         NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
1013             }
1014             if (fromUser) {
1015                 updatedChannel.lockFields(channel.getUserLockedFields());
1016                 lockFieldsForUpdateLocked(channel, updatedChannel);
1017             } else {
1018                 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
1019             }
1020             // no importance updates are allowed if OEM blocked it
1021             updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
1022             if (updatedChannel.isImportanceLockedByOEM()) {
1023                 updatedChannel.setImportance(channel.getImportance());
1024             }
1025             updatedChannel.setImportanceLockedByCriticalDeviceFunction(
1026                     r.defaultAppLockedImportance);
1027             if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()
1028                     && updatedChannel.getImportance() == IMPORTANCE_NONE) {
1029                 updatedChannel.setImportance(channel.getImportance());
1030             }
1031 
1032             r.channels.put(updatedChannel.getId(), updatedChannel);
1033 
1034             if (onlyHasDefaultChannel(pkg, uid)) {
1035                 // copy settings to app level so they are inherited by new channels
1036                 // when the app migrates
1037                 r.importance = updatedChannel.getImportance();
1038                 r.priority = updatedChannel.canBypassDnd()
1039                         ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
1040                 r.visibility = updatedChannel.getLockscreenVisibility();
1041                 r.showBadge = updatedChannel.canShowBadge();
1042             }
1043 
1044             if (!channel.equals(updatedChannel)) {
1045                 // only log if there are real changes
1046                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
1047                         .setSubtype(fromUser ? 1 : 0));
1048                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
1049                         NotificationChannelLogger.getLoggingImportance(channel), fromUser);
1050             }
1051 
1052             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
1053                     || channel.getImportance() != updatedChannel.getImportance()) {
1054                 updateChannelsBypassingDnd();
1055             }
1056         }
1057         updateConfig();
1058     }
1059 
1060     @Override
getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)1061     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
1062             boolean includeDeleted) {
1063         Objects.requireNonNull(pkg);
1064         return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
1065     }
1066 
1067     @Override
getConversationNotificationChannel(String pkg, int uid, String channelId, String conversationId, boolean returnParentIfNoConversationChannel, boolean includeDeleted)1068     public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
1069             String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
1070             boolean includeDeleted) {
1071         Preconditions.checkNotNull(pkg);
1072         synchronized (mPackagePreferences) {
1073             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1074             if (r == null) {
1075                 return null;
1076             }
1077             if (channelId == null) {
1078                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
1079             }
1080             NotificationChannel channel = null;
1081             if (conversationId != null) {
1082                 // look for an automatically created conversation specific channel
1083                 channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
1084             }
1085             if (channel == null && returnParentIfNoConversationChannel) {
1086                 // look for it just based on its id
1087                 final NotificationChannel nc = r.channels.get(channelId);
1088                 if (nc != null && (includeDeleted || !nc.isDeleted())) {
1089                     return nc;
1090                 }
1091             }
1092             return channel;
1093         }
1094     }
1095 
findConversationChannel(PackagePreferences p, String parentId, String conversationId, boolean includeDeleted)1096     private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
1097             String conversationId, boolean includeDeleted) {
1098         for (NotificationChannel nc : p.channels.values()) {
1099             if (conversationId.equals(nc.getConversationId())
1100                     && parentId.equals(nc.getParentChannelId())
1101                     && (includeDeleted || !nc.isDeleted())) {
1102                 return nc;
1103             }
1104         }
1105         return null;
1106     }
1107 
getNotificationChannelsByConversationId(String pkg, int uid, String conversationId)1108     public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
1109             String conversationId) {
1110         Preconditions.checkNotNull(pkg);
1111         Preconditions.checkNotNull(conversationId);
1112         List<NotificationChannel> channels = new ArrayList<>();
1113         synchronized (mPackagePreferences) {
1114             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1115             if (r == null) {
1116                 return channels;
1117             }
1118             for (NotificationChannel nc : r.channels.values()) {
1119                 if (conversationId.equals(nc.getConversationId())
1120                         && !nc.isDeleted()) {
1121                     channels.add(nc);
1122                 }
1123             }
1124             return channels;
1125         }
1126     }
1127 
1128     @Override
deleteNotificationChannel(String pkg, int uid, String channelId)1129     public boolean deleteNotificationChannel(String pkg, int uid, String channelId) {
1130         synchronized (mPackagePreferences) {
1131             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1132             if (r == null) {
1133                 return false;
1134             }
1135             NotificationChannel channel = r.channels.get(channelId);
1136             if (channel != null) {
1137                 return deleteNotificationChannelLocked(channel, pkg, uid);
1138             }
1139             return false;
1140         }
1141     }
1142 
deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid)1143     private boolean deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid) {
1144         if (!channel.isDeleted()) {
1145             channel.setDeleted(true);
1146             channel.setDeletedTimeMs(System.currentTimeMillis());
1147             LogMaker lm = getChannelLog(channel, pkg);
1148             lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
1149             MetricsLogger.action(lm);
1150             mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
1151 
1152             if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
1153                 updateChannelsBypassingDnd();
1154             }
1155             return true;
1156         }
1157         return false;
1158     }
1159 
1160     @Override
1161     @VisibleForTesting
permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)1162     public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
1163         Objects.requireNonNull(pkg);
1164         Objects.requireNonNull(channelId);
1165         synchronized (mPackagePreferences) {
1166             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1167             if (r == null) {
1168                 return;
1169             }
1170             r.channels.remove(channelId);
1171         }
1172     }
1173 
1174     @Override
permanentlyDeleteNotificationChannels(String pkg, int uid)1175     public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
1176         Objects.requireNonNull(pkg);
1177         synchronized (mPackagePreferences) {
1178             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1179             if (r == null) {
1180                 return;
1181             }
1182             int N = r.channels.size() - 1;
1183             for (int i = N; i >= 0; i--) {
1184                 String key = r.channels.keyAt(i);
1185                 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
1186                     r.channels.remove(key);
1187                 }
1188             }
1189         }
1190     }
1191 
shouldHideSilentStatusIcons()1192     public boolean shouldHideSilentStatusIcons() {
1193         return mHideSilentStatusBarIcons;
1194     }
1195 
setHideSilentStatusIcons(boolean hide)1196     public void setHideSilentStatusIcons(boolean hide) {
1197         mHideSilentStatusBarIcons = hide;
1198     }
1199 
lockChannelsForOEM(String[] appOrChannelList)1200     public void lockChannelsForOEM(String[] appOrChannelList) {
1201         if (appOrChannelList == null) {
1202             return;
1203         }
1204         for (String appOrChannel : appOrChannelList) {
1205             if (!TextUtils.isEmpty(appOrChannel)) {
1206                 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
1207                 if (appSplit != null && appSplit.length > 0) {
1208                     String appName = appSplit[0];
1209                     String channelId = appSplit.length == 2 ? appSplit[1] : null;
1210 
1211                     synchronized (mPackagePreferences) {
1212                         boolean foundApp = false;
1213                         for (PackagePreferences r : mPackagePreferences.values()) {
1214                             if (r.pkg.equals(appName)) {
1215                                 foundApp = true;
1216                                 if (channelId == null) {
1217                                     // lock all channels for the app
1218                                     r.oemLockedImportance = true;
1219                                     for (NotificationChannel channel : r.channels.values()) {
1220                                         channel.setImportanceLockedByOEM(true);
1221                                     }
1222                                 } else {
1223                                     NotificationChannel channel = r.channels.get(channelId);
1224                                     if (channel != null) {
1225                                         channel.setImportanceLockedByOEM(true);
1226                                     }
1227                                     // Also store the locked channels on the record, so they aren't
1228                                     // temporarily lost when data is cleared on the package
1229                                     r.oemLockedChannels.add(channelId);
1230                                 }
1231                             }
1232                         }
1233                         if (!foundApp) {
1234                             List<String> channels =
1235                                     mOemLockedApps.getOrDefault(appName, new ArrayList<>());
1236                             if (channelId != null) {
1237                                 channels.add(channelId);
1238                             }
1239                             mOemLockedApps.put(appName, channels);
1240                         }
1241                     }
1242                 }
1243             }
1244         }
1245     }
1246 
updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd)1247     public void updateDefaultApps(int userId, ArraySet<String> toRemove,
1248             ArraySet<Pair<String, Integer>> toAdd) {
1249         synchronized (mPackagePreferences) {
1250             for (PackagePreferences p : mPackagePreferences.values()) {
1251                 if (userId == UserHandle.getUserId(p.uid)) {
1252                     if (toRemove != null && toRemove.contains(p.pkg)) {
1253                         p.defaultAppLockedImportance = false;
1254                         for (NotificationChannel channel : p.channels.values()) {
1255                             channel.setImportanceLockedByCriticalDeviceFunction(false);
1256                         }
1257                     }
1258                 }
1259             }
1260             if (toAdd != null) {
1261                 for (Pair<String, Integer> approvedApp : toAdd) {
1262                     PackagePreferences p = getOrCreatePackagePreferencesLocked(approvedApp.first,
1263                             approvedApp.second);
1264                     p.defaultAppLockedImportance = true;
1265                     for (NotificationChannel channel : p.channels.values()) {
1266                         channel.setImportanceLockedByCriticalDeviceFunction(true);
1267                     }
1268                 }
1269             }
1270         }
1271     }
1272 
getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted)1273     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
1274             int uid, String groupId, boolean includeDeleted) {
1275         Objects.requireNonNull(pkg);
1276         synchronized (mPackagePreferences) {
1277             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1278             if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
1279                 return null;
1280             }
1281             NotificationChannelGroup group = r.groups.get(groupId).clone();
1282             group.setChannels(new ArrayList<>());
1283             int N = r.channels.size();
1284             for (int i = 0; i < N; i++) {
1285                 final NotificationChannel nc = r.channels.valueAt(i);
1286                 if (includeDeleted || !nc.isDeleted()) {
1287                     if (groupId.equals(nc.getGroup())) {
1288                         group.addChannel(nc);
1289                     }
1290                 }
1291             }
1292             return group;
1293         }
1294     }
1295 
getNotificationChannelGroup(String groupId, String pkg, int uid)1296     public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
1297             int uid) {
1298         Objects.requireNonNull(pkg);
1299         synchronized (mPackagePreferences) {
1300             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1301             if (r == null) {
1302                 return null;
1303             }
1304             return r.groups.get(groupId);
1305         }
1306     }
1307 
1308     @Override
getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty)1309     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
1310             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
1311         Objects.requireNonNull(pkg);
1312         Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
1313         synchronized (mPackagePreferences) {
1314             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1315             if (r == null) {
1316                 return ParceledListSlice.emptyList();
1317             }
1318             NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
1319             int N = r.channels.size();
1320             for (int i = 0; i < N; i++) {
1321                 final NotificationChannel nc = r.channels.valueAt(i);
1322                 if (includeDeleted || !nc.isDeleted()) {
1323                     if (nc.getGroup() != null) {
1324                         if (r.groups.get(nc.getGroup()) != null) {
1325                             NotificationChannelGroup ncg = groups.get(nc.getGroup());
1326                             if (ncg == null) {
1327                                 ncg = r.groups.get(nc.getGroup()).clone();
1328                                 ncg.setChannels(new ArrayList<>());
1329                                 groups.put(nc.getGroup(), ncg);
1330 
1331                             }
1332                             ncg.addChannel(nc);
1333                         }
1334                     } else {
1335                         nonGrouped.addChannel(nc);
1336                     }
1337                 }
1338             }
1339             if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
1340                 groups.put(null, nonGrouped);
1341             }
1342             if (includeEmpty) {
1343                 for (NotificationChannelGroup group : r.groups.values()) {
1344                     if (!groups.containsKey(group.getId())) {
1345                         groups.put(group.getId(), group);
1346                     }
1347                 }
1348             }
1349             return new ParceledListSlice<>(new ArrayList<>(groups.values()));
1350         }
1351     }
1352 
deleteNotificationChannelGroup(String pkg, int uid, String groupId)1353     public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
1354             String groupId) {
1355         List<NotificationChannel> deletedChannels = new ArrayList<>();
1356         synchronized (mPackagePreferences) {
1357             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1358             if (r == null || TextUtils.isEmpty(groupId)) {
1359                 return deletedChannels;
1360             }
1361 
1362             NotificationChannelGroup channelGroup = r.groups.remove(groupId);
1363             if (channelGroup != null) {
1364                 mNotificationChannelLogger.logNotificationChannelGroupDeleted(channelGroup, uid,
1365                         pkg);
1366             }
1367 
1368             int N = r.channels.size();
1369             for (int i = 0; i < N; i++) {
1370                 final NotificationChannel nc = r.channels.valueAt(i);
1371                 if (groupId.equals(nc.getGroup())) {
1372                     deleteNotificationChannelLocked(nc, pkg, uid);
1373                     deletedChannels.add(nc);
1374                 }
1375             }
1376         }
1377         return deletedChannels;
1378     }
1379 
1380     @Override
getNotificationChannelGroups(String pkg, int uid)1381     public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
1382             int uid) {
1383         List<NotificationChannelGroup> groups = new ArrayList<>();
1384         synchronized (mPackagePreferences) {
1385             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1386             if (r == null) {
1387                 return groups;
1388             }
1389             groups.addAll(r.groups.values());
1390         }
1391         return groups;
1392     }
1393 
getGroupForChannel(String pkg, int uid, String channelId)1394     public NotificationChannelGroup getGroupForChannel(String pkg, int uid, String channelId) {
1395         synchronized (mPackagePreferences) {
1396             PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
1397             if (p != null) {
1398                 NotificationChannel nc = p.channels.get(channelId);
1399                 if (nc != null && !nc.isDeleted()) {
1400                     if (nc.getGroup() != null) {
1401                         NotificationChannelGroup group = p.groups.get(nc.getGroup());
1402                         if (group != null) {
1403                             return group;
1404                         }
1405                     }
1406                 }
1407             }
1408         }
1409         return null;
1410     }
1411 
getConversations(IntArray userIds, boolean onlyImportant)1412     public ArrayList<ConversationChannelWrapper> getConversations(IntArray userIds,
1413             boolean onlyImportant) {
1414         synchronized (mPackagePreferences) {
1415             ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
1416             for (PackagePreferences p : mPackagePreferences.values()) {
1417                 if (userIds.binarySearch(UserHandle.getUserId(p.uid)) >= 0) {
1418                     int N = p.channels.size();
1419                     for (int i = 0; i < N; i++) {
1420                         final NotificationChannel nc = p.channels.valueAt(i);
1421                         if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()
1422                                 && !nc.isDemoted()
1423                                 && (nc.isImportantConversation() || !onlyImportant)) {
1424                             ConversationChannelWrapper conversation =
1425                                     new ConversationChannelWrapper();
1426                             conversation.setPkg(p.pkg);
1427                             conversation.setUid(p.uid);
1428                             conversation.setNotificationChannel(nc);
1429                             NotificationChannel parent = p.channels.get(nc.getParentChannelId());
1430                             conversation.setParentChannelLabel(parent == null
1431                                     ? null
1432                                     : parent.getName());
1433                             boolean blockedByGroup = false;
1434                             if (nc.getGroup() != null) {
1435                                 NotificationChannelGroup group = p.groups.get(nc.getGroup());
1436                                 if (group != null) {
1437                                     if (group.isBlocked()) {
1438                                         blockedByGroup = true;
1439                                     } else {
1440                                         conversation.setGroupLabel(group.getName());
1441                                     }
1442                                 }
1443                             }
1444                             if (!blockedByGroup) {
1445                                 conversations.add(conversation);
1446                             }
1447                         }
1448                     }
1449                 }
1450             }
1451 
1452             return conversations;
1453         }
1454     }
1455 
getConversations(String pkg, int uid)1456     public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) {
1457         Objects.requireNonNull(pkg);
1458         synchronized (mPackagePreferences) {
1459             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1460             if (r == null) {
1461                 return new ArrayList<>();
1462             }
1463             ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
1464             int N = r.channels.size();
1465             for (int i = 0; i < N; i++) {
1466                 final NotificationChannel nc = r.channels.valueAt(i);
1467                 if (!TextUtils.isEmpty(nc.getConversationId())
1468                         && !nc.isDeleted()
1469                         && !nc.isDemoted()) {
1470                     ConversationChannelWrapper conversation = new ConversationChannelWrapper();
1471                     conversation.setPkg(r.pkg);
1472                     conversation.setUid(r.uid);
1473                     conversation.setNotificationChannel(nc);
1474                     conversation.setParentChannelLabel(
1475                             r.channels.get(nc.getParentChannelId()).getName());
1476                     boolean blockedByGroup = false;
1477                     if (nc.getGroup() != null) {
1478                         NotificationChannelGroup group = r.groups.get(nc.getGroup());
1479                         if (group != null) {
1480                             if (group.isBlocked()) {
1481                                 blockedByGroup = true;
1482                             } else {
1483                                 conversation.setGroupLabel(group.getName());
1484                             }
1485                         }
1486                     }
1487                     if (!blockedByGroup) {
1488                         conversations.add(conversation);
1489                     }
1490                 }
1491             }
1492 
1493             return conversations;
1494         }
1495     }
1496 
deleteConversations(String pkg, int uid, Set<String> conversationIds)1497     public @NonNull List<String> deleteConversations(String pkg, int uid,
1498             Set<String> conversationIds) {
1499         synchronized (mPackagePreferences) {
1500             List<String> deletedChannelIds = new ArrayList<>();
1501             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1502             if (r == null) {
1503                 return deletedChannelIds;
1504             }
1505             int N = r.channels.size();
1506             for (int i = 0; i < N; i++) {
1507                 final NotificationChannel nc = r.channels.valueAt(i);
1508                 if (nc.getConversationId() != null
1509                         && conversationIds.contains(nc.getConversationId())) {
1510                     nc.setDeleted(true);
1511                     nc.setDeletedTimeMs(System.currentTimeMillis());
1512                     LogMaker lm = getChannelLog(nc, pkg);
1513                     lm.setType(
1514                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
1515                     MetricsLogger.action(lm);
1516                     mNotificationChannelLogger.logNotificationChannelDeleted(nc, uid, pkg);
1517 
1518                     deletedChannelIds.add(nc.getId());
1519                 }
1520             }
1521             if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
1522                 updateChannelsBypassingDnd();
1523             }
1524             return deletedChannelIds;
1525         }
1526     }
1527 
1528     @Override
getNotificationChannels(String pkg, int uid, boolean includeDeleted)1529     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
1530             boolean includeDeleted) {
1531         Objects.requireNonNull(pkg);
1532         List<NotificationChannel> channels = new ArrayList<>();
1533         synchronized (mPackagePreferences) {
1534             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1535             if (r == null) {
1536                 return ParceledListSlice.emptyList();
1537             }
1538             int N = r.channels.size();
1539             for (int i = 0; i < N; i++) {
1540                 final NotificationChannel nc = r.channels.valueAt(i);
1541                 if (includeDeleted || !nc.isDeleted()) {
1542                     channels.add(nc);
1543                 }
1544             }
1545             return new ParceledListSlice<>(channels);
1546         }
1547     }
1548 
1549     /**
1550      * Gets all notification channels associated with the given pkg and userId that can bypass dnd
1551      */
getNotificationChannelsBypassingDnd(String pkg, int userId)1552     public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
1553             int userId) {
1554         List<NotificationChannel> channels = new ArrayList<>();
1555         synchronized (mPackagePreferences) {
1556             final PackagePreferences r = mPackagePreferences.get(
1557                     packagePreferencesKey(pkg, userId));
1558             // notifications from this package aren't blocked
1559             if (r != null && r.importance != IMPORTANCE_NONE) {
1560                 for (NotificationChannel channel : r.channels.values()) {
1561                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1562                         channels.add(channel);
1563                     }
1564                 }
1565             }
1566         }
1567         return new ParceledListSlice<>(channels);
1568     }
1569 
1570     /**
1571      * True for pre-O apps that only have the default channel, or pre O apps that have no
1572      * channels yet. This method will create the default channel for pre-O apps that don't have it.
1573      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
1574      * upgrades.
1575      */
onlyHasDefaultChannel(String pkg, int uid)1576     public boolean onlyHasDefaultChannel(String pkg, int uid) {
1577         synchronized (mPackagePreferences) {
1578             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1579             if (r.channels.size() == 1
1580                     && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1581                 return true;
1582             }
1583             return false;
1584         }
1585     }
1586 
getDeletedChannelCount(String pkg, int uid)1587     public int getDeletedChannelCount(String pkg, int uid) {
1588         Objects.requireNonNull(pkg);
1589         int deletedCount = 0;
1590         synchronized (mPackagePreferences) {
1591             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1592             if (r == null) {
1593                 return deletedCount;
1594             }
1595             int N = r.channels.size();
1596             for (int i = 0; i < N; i++) {
1597                 final NotificationChannel nc = r.channels.valueAt(i);
1598                 if (nc.isDeleted()) {
1599                     deletedCount++;
1600                 }
1601             }
1602             return deletedCount;
1603         }
1604     }
1605 
getBlockedChannelCount(String pkg, int uid)1606     public int getBlockedChannelCount(String pkg, int uid) {
1607         Objects.requireNonNull(pkg);
1608         int blockedCount = 0;
1609         synchronized (mPackagePreferences) {
1610             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1611             if (r == null) {
1612                 return blockedCount;
1613             }
1614             int N = r.channels.size();
1615             for (int i = 0; i < N; i++) {
1616                 final NotificationChannel nc = r.channels.valueAt(i);
1617                 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1618                     blockedCount++;
1619                 }
1620             }
1621             return blockedCount;
1622         }
1623     }
1624 
getBlockedAppCount(int userId)1625     public int getBlockedAppCount(int userId) {
1626         int count = 0;
1627         synchronized (mPackagePreferences) {
1628             final int N = mPackagePreferences.size();
1629             for (int i = 0; i < N; i++) {
1630                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1631                 if (userId == UserHandle.getUserId(r.uid)
1632                         && r.importance == IMPORTANCE_NONE) {
1633                     count++;
1634                 }
1635             }
1636         }
1637         return count;
1638     }
1639 
1640     /**
1641      * Returns the number of apps that have at least one notification channel that can bypass DND
1642      * for given particular user
1643      */
getAppsBypassingDndCount(int userId)1644     public int getAppsBypassingDndCount(int userId) {
1645         int count = 0;
1646         synchronized (mPackagePreferences) {
1647             final int numPackagePreferences = mPackagePreferences.size();
1648             for (int i = 0; i < numPackagePreferences; i++) {
1649                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1650                 // Package isn't associated with this userId or notifications from this package are
1651                 // blocked
1652                 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1653                     continue;
1654                 }
1655 
1656                 for (NotificationChannel channel : r.channels.values()) {
1657                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1658                         count++;
1659                         break;
1660                     }
1661                 }
1662             }
1663         }
1664         return count;
1665     }
1666 
1667     /**
1668      * Syncs {@link #mAreChannelsBypassingDnd} with the current user's notification policy before
1669      * updating
1670      */
syncChannelsBypassingDnd()1671     private void syncChannelsBypassingDnd() {
1672         mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1673                 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1674         updateChannelsBypassingDnd();
1675     }
1676 
1677     /**
1678      * Updates the user's NotificationPolicy based on whether the current userId
1679      * has channels bypassing DND
1680      * @param userId
1681      */
updateChannelsBypassingDnd()1682     private void updateChannelsBypassingDnd() {
1683         synchronized (mPackagePreferences) {
1684             final int numPackagePreferences = mPackagePreferences.size();
1685             for (int i = 0; i < numPackagePreferences; i++) {
1686                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1687                 // Package isn't associated with the current userId or notifications from this
1688                 // package are blocked
1689                 if (mCurrentUserId != UserHandle.getUserId(r.uid)
1690                         || r.importance == IMPORTANCE_NONE) {
1691                     continue;
1692                 }
1693 
1694                 for (NotificationChannel channel : r.channels.values()) {
1695                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1696                         if (!mAreChannelsBypassingDnd) {
1697                             mAreChannelsBypassingDnd = true;
1698                             updateZenPolicy(true);
1699                         }
1700                         return;
1701                     }
1702                 }
1703             }
1704         }
1705         // If no channels bypass DND, update the zen policy once to disable DND bypass.
1706         if (mAreChannelsBypassingDnd) {
1707             mAreChannelsBypassingDnd = false;
1708             updateZenPolicy(false);
1709         }
1710     }
1711 
channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel)1712     private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) {
1713         // Channel is in a group that's blocked
1714         if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1715             return false;
1716         }
1717 
1718         // Channel is deleted or is blocked
1719         if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1720             return false;
1721         }
1722 
1723         return true;
1724     }
1725 
updateZenPolicy(boolean areChannelsBypassingDnd)1726     public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1727         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1728         mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1729                 policy.priorityCategories, policy.priorityCallSenders,
1730                 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1731                 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1732                         : 0),
1733                 policy.priorityConversationSenders));
1734     }
1735 
areChannelsBypassingDnd()1736     public boolean areChannelsBypassingDnd() {
1737         return mAreChannelsBypassingDnd;
1738     }
1739 
1740     /**
1741      * Sets importance.
1742      */
1743     @Override
setImportance(String pkgName, int uid, int importance)1744     public void setImportance(String pkgName, int uid, int importance) {
1745         synchronized (mPackagePreferences) {
1746             getOrCreatePackagePreferencesLocked(pkgName, uid).importance = importance;
1747         }
1748         updateConfig();
1749     }
1750 
setEnabled(String packageName, int uid, boolean enabled)1751     public void setEnabled(String packageName, int uid, boolean enabled) {
1752         boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1753         if (wasEnabled == enabled) {
1754             return;
1755         }
1756         setImportance(packageName, uid,
1757                 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1758         mNotificationChannelLogger.logAppNotificationsAllowed(uid, packageName, enabled);
1759     }
1760 
1761     /**
1762      * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1763      * {@code uid}, have their importance locked by the user. Locked notifications don't get
1764      * considered for sentiment adjustments (and thus never show a blocking helper).
1765      */
setAppImportanceLocked(String packageName, int uid)1766     public void setAppImportanceLocked(String packageName, int uid) {
1767         synchronized (mPackagePreferences) {
1768             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid);
1769             if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1770                 return;
1771             }
1772 
1773             prefs.lockedAppFields =
1774                     prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1775         }
1776         updateConfig();
1777     }
1778 
1779     /**
1780      * Returns the delegate for a given package, if it's allowed by the package and the user.
1781      */
getNotificationDelegate(String sourcePkg, int sourceUid)1782     public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1783         synchronized (mPackagePreferences) {
1784             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1785 
1786             if (prefs == null || prefs.delegate == null) {
1787                 return null;
1788             }
1789             if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1790                 return null;
1791             }
1792             return prefs.delegate.mPkg;
1793         }
1794     }
1795 
1796     /**
1797      * Used by an app to delegate notification posting privileges to another apps.
1798      */
setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid)1799     public void setNotificationDelegate(String sourcePkg, int sourceUid,
1800             String delegatePkg, int delegateUid) {
1801         synchronized (mPackagePreferences) {
1802             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
1803 
1804             boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1805             Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1806             prefs.delegate = delegate;
1807         }
1808         updateConfig();
1809     }
1810 
1811     /**
1812      * Used by an app to turn off its notification delegate.
1813      */
revokeNotificationDelegate(String sourcePkg, int sourceUid)1814     public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1815         boolean changed = false;
1816         synchronized (mPackagePreferences) {
1817             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1818             if (prefs != null && prefs.delegate != null) {
1819                 prefs.delegate.mEnabled = false;
1820                 changed = true;
1821             }
1822         }
1823         if (changed) {
1824             updateConfig();
1825         }
1826     }
1827 
1828     /**
1829      * Toggles whether an app can have a notification delegate on behalf of a user.
1830      */
toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed)1831     public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1832         boolean changed = false;
1833         synchronized (mPackagePreferences) {
1834             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1835             if (prefs != null && prefs.delegate != null) {
1836                 prefs.delegate.mUserAllowed = userAllowed;
1837                 changed = true;
1838             }
1839         }
1840         if (changed) {
1841             updateConfig();
1842         }
1843     }
1844 
1845     /**
1846      * Returns whether the given app is allowed on post notifications on behalf of the other given
1847      * app.
1848      */
isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid)1849     public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1850             String potentialDelegatePkg, int potentialDelegateUid) {
1851         synchronized (mPackagePreferences) {
1852             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1853 
1854             return prefs != null && prefs.isValidDelegate(potentialDelegatePkg,
1855                     potentialDelegateUid);
1856         }
1857     }
1858 
1859     @VisibleForTesting
lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update)1860     void lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update) {
1861         if (original.canBypassDnd() != update.canBypassDnd()) {
1862             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1863         }
1864         if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1865             update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1866         }
1867         if (original.getImportance() != update.getImportance()) {
1868             update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1869         }
1870         if (original.shouldShowLights() != update.shouldShowLights()
1871                 || original.getLightColor() != update.getLightColor()) {
1872             update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1873         }
1874         if (!Objects.equals(original.getSound(), update.getSound())) {
1875             update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1876         }
1877         if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1878                 || original.shouldVibrate() != update.shouldVibrate()) {
1879             update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1880         }
1881         if (original.canShowBadge() != update.canShowBadge()) {
1882             update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1883         }
1884         if (original.getAllowBubbles() != update.getAllowBubbles()) {
1885             update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
1886         }
1887     }
1888 
dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter)1889     public void dump(PrintWriter pw, String prefix,
1890             @NonNull NotificationManagerService.DumpFilter filter) {
1891         pw.print(prefix);
1892         pw.println("per-package config:");
1893 
1894         pw.println("PackagePreferences:");
1895         synchronized (mPackagePreferences) {
1896             dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences);
1897         }
1898         pw.println("Restored without uid:");
1899         dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids);
1900     }
1901 
dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter)1902     public void dump(ProtoOutputStream proto,
1903             @NonNull NotificationManagerService.DumpFilter filter) {
1904         synchronized (mPackagePreferences) {
1905             dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
1906                     mPackagePreferences);
1907         }
1908         dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1909                 mRestoredWithoutUids);
1910     }
1911 
dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1912     private static void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
1913             @NonNull NotificationManagerService.DumpFilter filter,
1914             ArrayMap<String, PackagePreferences> packagePreferences) {
1915         final int N = packagePreferences.size();
1916         for (int i = 0; i < N; i++) {
1917             final PackagePreferences r = packagePreferences.valueAt(i);
1918             if (filter.matches(r.pkg)) {
1919                 pw.print(prefix);
1920                 pw.print("  AppSettings: ");
1921                 pw.print(r.pkg);
1922                 pw.print(" (");
1923                 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
1924                 pw.print(')');
1925                 if (r.importance != DEFAULT_IMPORTANCE) {
1926                     pw.print(" importance=");
1927                     pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1928                 }
1929                 if (r.priority != DEFAULT_PRIORITY) {
1930                     pw.print(" priority=");
1931                     pw.print(Notification.priorityToString(r.priority));
1932                 }
1933                 if (r.visibility != DEFAULT_VISIBILITY) {
1934                     pw.print(" visibility=");
1935                     pw.print(Notification.visibilityToString(r.visibility));
1936                 }
1937                 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1938                     pw.print(" showBadge=");
1939                     pw.print(r.showBadge);
1940                 }
1941                 if (r.defaultAppLockedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) {
1942                     pw.print(" defaultAppLocked=");
1943                     pw.print(r.defaultAppLockedImportance);
1944                 }
1945                 if (r.oemLockedImportance != DEFAULT_OEM_LOCKED_IMPORTANCE) {
1946                     pw.print(" oemLocked=");
1947                     pw.print(r.oemLockedImportance);
1948                 }
1949                 if (!r.oemLockedChannels.isEmpty()) {
1950                     pw.print(" futureLockedChannels=");
1951                     pw.print(r.oemLockedChannels);
1952                 }
1953                 pw.println();
1954                 for (NotificationChannel channel : r.channels.values()) {
1955                     pw.print(prefix);
1956                     channel.dump(pw, "    ", filter.redact);
1957                 }
1958                 for (NotificationChannelGroup group : r.groups.values()) {
1959                     pw.print(prefix);
1960                     pw.print("  ");
1961                     pw.print("  ");
1962                     pw.println(group);
1963                 }
1964             }
1965         }
1966     }
1967 
dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1968     private static void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId,
1969             @NonNull NotificationManagerService.DumpFilter filter,
1970             ArrayMap<String, PackagePreferences> packagePreferences) {
1971         final int N = packagePreferences.size();
1972         long fToken;
1973         for (int i = 0; i < N; i++) {
1974             final PackagePreferences r = packagePreferences.valueAt(i);
1975             if (filter.matches(r.pkg)) {
1976                 fToken = proto.start(fieldId);
1977 
1978                 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1979                 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1980                 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1981                 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1982                 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1983                 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1984 
1985                 for (NotificationChannel channel : r.channels.values()) {
1986                     channel.dumpDebug(proto, RankingHelperProto.RecordProto.CHANNELS);
1987                 }
1988                 for (NotificationChannelGroup group : r.groups.values()) {
1989                     group.dumpDebug(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1990                 }
1991 
1992                 proto.end(fToken);
1993             }
1994         }
1995     }
1996 
1997     /**
1998      * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
1999      */
pullPackagePreferencesStats(List<StatsEvent> events)2000     public void pullPackagePreferencesStats(List<StatsEvent> events) {
2001         synchronized (mPackagePreferences) {
2002             for (int i = 0; i < mPackagePreferences.size(); i++) {
2003                 if (i > NOTIFICATION_PREFERENCES_PULL_LIMIT) {
2004                     break;
2005                 }
2006                 SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
2007                         .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
2008                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2009                 event.writeInt(r.uid);
2010                 event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
2011                 event.writeInt(r.importance);
2012                 event.writeInt(r.visibility);
2013                 event.writeInt(r.lockedAppFields);
2014                 events.add(event.build());
2015             }
2016         }
2017     }
2018 
2019     /**
2020      * Fills out {@link PackageNotificationChannelPreferences} proto and wraps it in a
2021      * {@link StatsEvent}.
2022      */
pullPackageChannelPreferencesStats(List<StatsEvent> events)2023     public void pullPackageChannelPreferencesStats(List<StatsEvent> events) {
2024         synchronized (mPackagePreferences) {
2025             int totalChannelsPulled = 0;
2026             for (int i = 0; i < mPackagePreferences.size(); i++) {
2027                 if (totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
2028                     break;
2029                 }
2030                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2031                 for (NotificationChannel channel : r.channels.values()) {
2032                     if (++totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
2033                         break;
2034                     }
2035                     SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
2036                             .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
2037                     event.writeInt(r.uid);
2038                     event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
2039                     event.writeString(channel.getId());
2040                     event.writeString(channel.getName().toString());
2041                     event.writeString(channel.getDescription());
2042                     event.writeInt(channel.getImportance());
2043                     event.writeInt(channel.getUserLockedFields());
2044                     event.writeBoolean(channel.isDeleted());
2045                     event.writeBoolean(channel.getConversationId() != null);
2046                     event.writeBoolean(channel.isDemoted());
2047                     event.writeBoolean(channel.isImportantConversation());
2048                     events.add(event.build());
2049                 }
2050             }
2051         }
2052     }
2053 
2054     /**
2055      * Fills out {@link PackageNotificationChannelGroupPreferences} proto and wraps it in a
2056      * {@link StatsEvent}.
2057      */
pullPackageChannelGroupPreferencesStats(List<StatsEvent> events)2058     public void pullPackageChannelGroupPreferencesStats(List<StatsEvent> events) {
2059         synchronized (mPackagePreferences) {
2060             int totalGroupsPulled = 0;
2061             for (int i = 0; i < mPackagePreferences.size(); i++) {
2062                 if (totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
2063                     break;
2064                 }
2065                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2066                 for (NotificationChannelGroup groupChannel : r.groups.values()) {
2067                     if (++totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
2068                         break;
2069                     }
2070                     SysUiStatsEvent.Builder event = mStatsEventBuilderFactory.newBuilder()
2071                             .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
2072                     event.writeInt(r.uid);
2073                     event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
2074                     event.writeString(groupChannel.getId());
2075                     event.writeString(groupChannel.getName().toString());
2076                     event.writeString(groupChannel.getDescription());
2077                     event.writeBoolean(groupChannel.isBlocked());
2078                     event.writeInt(groupChannel.getUserLockedFields());
2079                     events.add(event.build());
2080                 }
2081             }
2082         }
2083     }
2084 
dumpJson(NotificationManagerService.DumpFilter filter)2085     public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
2086         JSONObject ranking = new JSONObject();
2087         JSONArray PackagePreferencess = new JSONArray();
2088         try {
2089             ranking.put("noUid", mRestoredWithoutUids.size());
2090         } catch (JSONException e) {
2091             // pass
2092         }
2093         synchronized (mPackagePreferences) {
2094             final int N = mPackagePreferences.size();
2095             for (int i = 0; i < N; i++) {
2096                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2097                 if (filter == null || filter.matches(r.pkg)) {
2098                     JSONObject PackagePreferences = new JSONObject();
2099                     try {
2100                         PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
2101                         PackagePreferences.put("packageName", r.pkg);
2102                         if (r.importance != DEFAULT_IMPORTANCE) {
2103                             PackagePreferences.put("importance",
2104                                     NotificationListenerService.Ranking.importanceToString(
2105                                             r.importance));
2106                         }
2107                         if (r.priority != DEFAULT_PRIORITY) {
2108                             PackagePreferences.put("priority",
2109                                     Notification.priorityToString(r.priority));
2110                         }
2111                         if (r.visibility != DEFAULT_VISIBILITY) {
2112                             PackagePreferences.put("visibility",
2113                                     Notification.visibilityToString(r.visibility));
2114                         }
2115                         if (r.showBadge != DEFAULT_SHOW_BADGE) {
2116                             PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
2117                         }
2118                         JSONArray channels = new JSONArray();
2119                         for (NotificationChannel channel : r.channels.values()) {
2120                             channels.put(channel.toJson());
2121                         }
2122                         PackagePreferences.put("channels", channels);
2123                         JSONArray groups = new JSONArray();
2124                         for (NotificationChannelGroup group : r.groups.values()) {
2125                             groups.put(group.toJson());
2126                         }
2127                         PackagePreferences.put("groups", groups);
2128                     } catch (JSONException e) {
2129                         // pass
2130                     }
2131                     PackagePreferencess.put(PackagePreferences);
2132                 }
2133             }
2134         }
2135         try {
2136             ranking.put("PackagePreferencess", PackagePreferencess);
2137         } catch (JSONException e) {
2138             // pass
2139         }
2140         return ranking;
2141     }
2142 
2143     /**
2144      * Dump only the ban information as structured JSON for the stats collector.
2145      *
2146      * This is intentionally redundant with {#link dumpJson} because the old
2147      * scraper will expect this format.
2148      *
2149      * @param filter
2150      * @return
2151      */
dumpBansJson(NotificationManagerService.DumpFilter filter)2152     public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
2153         JSONArray bans = new JSONArray();
2154         Map<Integer, String> packageBans = getPackageBans();
2155         for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
2156             final int userId = UserHandle.getUserId(ban.getKey());
2157             final String packageName = ban.getValue();
2158             if (filter == null || filter.matches(packageName)) {
2159                 JSONObject banJson = new JSONObject();
2160                 try {
2161                     banJson.put("userId", userId);
2162                     banJson.put("packageName", packageName);
2163                 } catch (JSONException e) {
2164                     e.printStackTrace();
2165                 }
2166                 bans.put(banJson);
2167             }
2168         }
2169         return bans;
2170     }
2171 
getPackageBans()2172     public Map<Integer, String> getPackageBans() {
2173         synchronized (mPackagePreferences) {
2174             final int N = mPackagePreferences.size();
2175             ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
2176             for (int i = 0; i < N; i++) {
2177                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2178                 if (r.importance == IMPORTANCE_NONE) {
2179                     packageBans.put(r.uid, r.pkg);
2180                 }
2181             }
2182 
2183             return packageBans;
2184         }
2185     }
2186 
2187     /**
2188      * Dump only the channel information as structured JSON for the stats collector.
2189      *
2190      * This is intentionally redundant with {#link dumpJson} because the old
2191      * scraper will expect this format.
2192      *
2193      * @param filter
2194      * @return
2195      */
dumpChannelsJson(NotificationManagerService.DumpFilter filter)2196     public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
2197         JSONArray channels = new JSONArray();
2198         Map<String, Integer> packageChannels = getPackageChannels();
2199         for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
2200             final String packageName = channelCount.getKey();
2201             if (filter == null || filter.matches(packageName)) {
2202                 JSONObject channelCountJson = new JSONObject();
2203                 try {
2204                     channelCountJson.put("packageName", packageName);
2205                     channelCountJson.put("channelCount", channelCount.getValue());
2206                 } catch (JSONException e) {
2207                     e.printStackTrace();
2208                 }
2209                 channels.put(channelCountJson);
2210             }
2211         }
2212         return channels;
2213     }
2214 
getPackageChannels()2215     private Map<String, Integer> getPackageChannels() {
2216         ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
2217         synchronized (mPackagePreferences) {
2218             for (int i = 0; i < mPackagePreferences.size(); i++) {
2219                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2220                 int channelCount = 0;
2221                 for (int j = 0; j < r.channels.size(); j++) {
2222                     if (!r.channels.valueAt(j).isDeleted()) {
2223                         channelCount++;
2224                     }
2225                 }
2226                 packageChannels.put(r.pkg, channelCount);
2227             }
2228         }
2229         return packageChannels;
2230     }
2231 
2232     /**
2233      * Called when user switches
2234      */
onUserSwitched(int userId)2235     public void onUserSwitched(int userId) {
2236         mCurrentUserId = userId;
2237         syncChannelsBypassingDnd();
2238     }
2239 
2240     /**
2241      * Called when user is unlocked
2242      */
onUserUnlocked(int userId)2243     public void onUserUnlocked(int userId) {
2244         mCurrentUserId = userId;
2245         syncChannelsBypassingDnd();
2246     }
2247 
onUserRemoved(int userId)2248     public void onUserRemoved(int userId) {
2249         synchronized (mPackagePreferences) {
2250             int N = mPackagePreferences.size();
2251             for (int i = N - 1; i >= 0; i--) {
2252                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
2253                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
2254                     mPackagePreferences.removeAt(i);
2255                 }
2256             }
2257         }
2258     }
2259 
onLocaleChanged(Context context, int userId)2260     protected void onLocaleChanged(Context context, int userId) {
2261         synchronized (mPackagePreferences) {
2262             int N = mPackagePreferences.size();
2263             for (int i = 0; i < N; i++) {
2264                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
2265                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
2266                     if (PackagePreferences.channels.containsKey(
2267                             NotificationChannel.DEFAULT_CHANNEL_ID)) {
2268                         PackagePreferences.channels.get(
2269                                 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
2270                                 context.getResources().getString(
2271                                         R.string.default_notification_channel_label));
2272                     }
2273                 }
2274             }
2275         }
2276     }
2277 
onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)2278     public boolean onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
2279             int[] uidList) {
2280         if (pkgList == null || pkgList.length == 0) {
2281             return false; // nothing to do
2282         }
2283         boolean updated = false;
2284         if (removingPackage) {
2285             // Remove notification settings for uninstalled package
2286             int size = Math.min(pkgList.length, uidList.length);
2287             for (int i = 0; i < size; i++) {
2288                 final String pkg = pkgList[i];
2289                 final int uid = uidList[i];
2290                 synchronized (mPackagePreferences) {
2291                     mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
2292                 }
2293                 mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
2294                 updated = true;
2295             }
2296         } else {
2297             for (String pkg : pkgList) {
2298                 // Package install
2299                 final PackagePreferences r =
2300                         mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
2301                 if (r != null) {
2302                     try {
2303                         r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
2304                         mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
2305                         synchronized (mPackagePreferences) {
2306                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
2307                         }
2308                         updated = true;
2309                     } catch (PackageManager.NameNotFoundException e) {
2310                         // noop
2311                     }
2312                 }
2313                 // Package upgrade
2314                 try {
2315                     synchronized (mPackagePreferences) {
2316                         PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg,
2317                                 mPm.getPackageUidAsUser(pkg, changeUserId));
2318                         if (fullPackagePreferences != null) {
2319                             updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences);
2320                             updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences);
2321                         }
2322                     }
2323                 } catch (PackageManager.NameNotFoundException e) {
2324                 }
2325             }
2326         }
2327 
2328         if (updated) {
2329             updateConfig();
2330         }
2331         return updated;
2332     }
2333 
clearData(String pkg, int uid)2334     public void clearData(String pkg, int uid) {
2335         synchronized (mPackagePreferences) {
2336             PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
2337             if (p != null) {
2338                 p.channels = new ArrayMap<>();
2339                 p.groups = new ArrayMap<>();
2340                 p.delegate = null;
2341                 p.lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
2342                 p.bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
2343                 p.importance = DEFAULT_IMPORTANCE;
2344                 p.priority = DEFAULT_PRIORITY;
2345                 p.visibility = DEFAULT_VISIBILITY;
2346                 p.showBadge = DEFAULT_SHOW_BADGE;
2347             }
2348         }
2349     }
2350 
getChannelLog(NotificationChannel channel, String pkg)2351     private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
2352         return new LogMaker(
2353                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
2354                         .ACTION_NOTIFICATION_CHANNEL)
2355                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
2356                 .setPackageName(pkg)
2357                 .addTaggedData(
2358                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2359                                 .FIELD_NOTIFICATION_CHANNEL_ID,
2360                         channel.getId())
2361                 .addTaggedData(
2362                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2363                                 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
2364                         channel.getImportance());
2365     }
2366 
getChannelGroupLog(String groupId, String pkg)2367     private LogMaker getChannelGroupLog(String groupId, String pkg) {
2368         return new LogMaker(
2369                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
2370                         .ACTION_NOTIFICATION_CHANNEL_GROUP)
2371                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
2372                 .addTaggedData(
2373                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2374                                 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
2375                         groupId)
2376                 .setPackageName(pkg);
2377     }
2378 
2379     /** Requests check of the feature setting for showing media notifications in quick settings. */
updateMediaNotificationFilteringEnabled()2380     public void updateMediaNotificationFilteringEnabled() {
2381         final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
2382                 Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0;
2383         if (newValue != mIsMediaNotificationFilteringEnabled) {
2384             mIsMediaNotificationFilteringEnabled = newValue;
2385             updateConfig();
2386         }
2387     }
2388 
2389     /** Returns true if the setting is enabled for showing media notifications in quick settings. */
isMediaNotificationFilteringEnabled()2390     public boolean isMediaNotificationFilteringEnabled() {
2391         return mIsMediaNotificationFilteringEnabled;
2392     }
2393 
updateBadgingEnabled()2394     public void updateBadgingEnabled() {
2395         if (mBadgingEnabled == null) {
2396             mBadgingEnabled = new SparseBooleanArray();
2397         }
2398         boolean changed = false;
2399         // update the cached values
2400         for (int index = 0; index < mBadgingEnabled.size(); index++) {
2401             int userId = mBadgingEnabled.keyAt(index);
2402             final boolean oldValue = mBadgingEnabled.get(userId);
2403             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2404                     Settings.Secure.NOTIFICATION_BADGING,
2405                     DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
2406             mBadgingEnabled.put(userId, newValue);
2407             changed |= oldValue != newValue;
2408         }
2409         if (changed) {
2410             updateConfig();
2411         }
2412     }
2413 
badgingEnabled(UserHandle userHandle)2414     public boolean badgingEnabled(UserHandle userHandle) {
2415         int userId = userHandle.getIdentifier();
2416         if (userId == UserHandle.USER_ALL) {
2417             return false;
2418         }
2419         if (mBadgingEnabled.indexOfKey(userId) < 0) {
2420             mBadgingEnabled.put(userId,
2421                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
2422                             Settings.Secure.NOTIFICATION_BADGING,
2423                             DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
2424         }
2425         return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
2426     }
2427 
2428     /** Updates whether bubbles are enabled for this user. */
updateBubblesEnabled()2429     public void updateBubblesEnabled() {
2430         if (mBubblesEnabled == null) {
2431             mBubblesEnabled = new SparseBooleanArray();
2432         }
2433         boolean changed = false;
2434         // update the cached values
2435         for (int index = 0; index < mBubblesEnabled.size(); index++) {
2436             int userId = mBubblesEnabled.keyAt(index);
2437             final boolean oldValue = mBubblesEnabled.get(userId);
2438             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2439                     Settings.Secure.NOTIFICATION_BUBBLES,
2440                     DEFAULT_BUBBLES_ENABLED ? 1 : 0, userId) != 0;
2441             mBubblesEnabled.put(userId, newValue);
2442             changed |= oldValue != newValue;
2443         }
2444         if (changed) {
2445             updateConfig();
2446         }
2447     }
2448 
2449     /** Returns true if bubbles are enabled for this user. */
bubblesEnabled(UserHandle userHandle)2450     public boolean bubblesEnabled(UserHandle userHandle) {
2451         int userId = userHandle.getIdentifier();
2452         if (userId == UserHandle.USER_ALL) {
2453             return false;
2454         }
2455         if (mBubblesEnabled.indexOfKey(userId) < 0) {
2456             mBubblesEnabled.put(userId,
2457                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
2458                             Settings.Secure.NOTIFICATION_BUBBLES,
2459                             DEFAULT_BUBBLES_ENABLED ? 1 : 0, userId) != 0);
2460         }
2461         return mBubblesEnabled.get(userId, DEFAULT_BUBBLES_ENABLED);
2462     }
2463 
updateLockScreenPrivateNotifications()2464     public void updateLockScreenPrivateNotifications() {
2465         if (mLockScreenPrivateNotifications == null) {
2466             mLockScreenPrivateNotifications = new SparseBooleanArray();
2467         }
2468         boolean changed = false;
2469         // update the cached values
2470         for (int index = 0; index < mLockScreenPrivateNotifications.size(); index++) {
2471             int userId = mLockScreenPrivateNotifications.keyAt(index);
2472             final boolean oldValue = mLockScreenPrivateNotifications.get(userId);
2473             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2474                     Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, userId) != 0;
2475             mLockScreenPrivateNotifications.put(userId, newValue);
2476             changed |= oldValue != newValue;
2477         }
2478         if (changed) {
2479             updateConfig();
2480         }
2481     }
2482 
updateLockScreenShowNotifications()2483     public void updateLockScreenShowNotifications() {
2484         if (mLockScreenShowNotifications == null) {
2485             mLockScreenShowNotifications = new SparseBooleanArray();
2486         }
2487         boolean changed = false;
2488         // update the cached values
2489         for (int index = 0; index < mLockScreenShowNotifications.size(); index++) {
2490             int userId = mLockScreenShowNotifications.keyAt(index);
2491             final boolean oldValue = mLockScreenShowNotifications.get(userId);
2492             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2493                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, userId) != 0;
2494             mLockScreenShowNotifications.put(userId, newValue);
2495             changed |= oldValue != newValue;
2496         }
2497         if (changed) {
2498             updateConfig();
2499         }
2500     }
2501 
2502     @Override
canShowNotificationsOnLockscreen(int userId)2503     public boolean canShowNotificationsOnLockscreen(int userId) {
2504         if (mLockScreenShowNotifications == null) {
2505             mLockScreenShowNotifications = new SparseBooleanArray();
2506         }
2507         return mLockScreenShowNotifications.get(userId, true);
2508     }
2509 
2510     @Override
canShowPrivateNotificationsOnLockScreen(int userId)2511     public boolean canShowPrivateNotificationsOnLockScreen(int userId) {
2512         if (mLockScreenPrivateNotifications == null) {
2513             mLockScreenPrivateNotifications = new SparseBooleanArray();
2514         }
2515         return mLockScreenPrivateNotifications.get(userId, true);
2516     }
2517 
unlockAllNotificationChannels()2518     public void unlockAllNotificationChannels() {
2519         synchronized (mPackagePreferences) {
2520             final int numPackagePreferences = mPackagePreferences.size();
2521             for (int i = 0; i < numPackagePreferences; i++) {
2522                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2523                 for (NotificationChannel channel : r.channels.values()) {
2524                     channel.unlockFields(USER_LOCKED_IMPORTANCE);
2525                 }
2526             }
2527         }
2528     }
2529 
updateConfig()2530     private void updateConfig() {
2531         mRankingHandler.requestSort();
2532     }
2533 
packagePreferencesKey(String pkg, int uid)2534     private static String packagePreferencesKey(String pkg, int uid) {
2535         return pkg + "|" + uid;
2536     }
2537 
unrestoredPackageKey(String pkg, @UserIdInt int userId)2538     private static String unrestoredPackageKey(String pkg, @UserIdInt int userId) {
2539         return pkg + "|" + userId;
2540     }
2541 
2542     private static class PackagePreferences {
2543         String pkg;
2544         int uid = UNKNOWN_UID;
2545         int importance = DEFAULT_IMPORTANCE;
2546         int priority = DEFAULT_PRIORITY;
2547         int visibility = DEFAULT_VISIBILITY;
2548         boolean showBadge = DEFAULT_SHOW_BADGE;
2549         int bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
2550         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
2551         // these fields are loaded on boot from a different source of truth and so are not
2552         // written to notification policy xml
2553         boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
2554         List<String> oemLockedChannels = new ArrayList<>();
2555         boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
2556 
2557         boolean hasSentInvalidMessage = false;
2558         boolean hasSentValidMessage = false;
2559         // notE: only valid while hasSentMessage is false and hasSentInvalidMessage is true
2560         boolean userDemotedMsgApp = false;
2561 
2562         Delegate delegate = null;
2563         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
2564         Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
2565 
isValidDelegate(String pkg, int uid)2566         public boolean isValidDelegate(String pkg, int uid) {
2567             return delegate != null && delegate.isAllowed(pkg, uid);
2568         }
2569     }
2570 
2571     private static class Delegate {
2572         static final boolean DEFAULT_ENABLED = true;
2573         static final boolean DEFAULT_USER_ALLOWED = true;
2574         String mPkg;
2575         int mUid = UNKNOWN_UID;
2576         boolean mEnabled = DEFAULT_ENABLED;
2577         boolean mUserAllowed = DEFAULT_USER_ALLOWED;
2578 
Delegate(String pkg, int uid, boolean enabled, boolean userAllowed)2579         Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
2580             mPkg = pkg;
2581             mUid = uid;
2582             mEnabled = enabled;
2583             mUserAllowed = userAllowed;
2584         }
2585 
isAllowed(String pkg, int uid)2586         public boolean isAllowed(String pkg, int uid) {
2587             if (pkg == null || uid == UNKNOWN_UID) {
2588                 return false;
2589             }
2590             return pkg.equals(mPkg)
2591                     && uid == mUid
2592                     && (mUserAllowed && mEnabled);
2593         }
2594     }
2595 }
2596