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