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.NotificationManager.IMPORTANCE_NONE;
20 
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.app.NotificationChannelGroup;
27 import android.app.NotificationManager;
28 import android.content.Context;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ParceledListSlice;
32 import android.metrics.LogMaker;
33 import android.os.Build;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.service.notification.NotificationListenerService;
37 import android.service.notification.RankingHelperProto;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.ArraySet;
41 import android.util.Pair;
42 import android.util.Slog;
43 import android.util.SparseBooleanArray;
44 import android.util.proto.ProtoOutputStream;
45 
46 import com.android.internal.R;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.logging.MetricsLogger;
49 import com.android.internal.util.Preconditions;
50 import com.android.internal.util.XmlUtils;
51 
52 import org.json.JSONArray;
53 import org.json.JSONException;
54 import org.json.JSONObject;
55 import org.xmlpull.v1.XmlPullParser;
56 import org.xmlpull.v1.XmlPullParserException;
57 import org.xmlpull.v1.XmlSerializer;
58 
59 import java.io.IOException;
60 import java.io.PrintWriter;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collection;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Objects;
67 import java.util.concurrent.ConcurrentHashMap;
68 
69 public class PreferencesHelper implements RankingConfig {
70     private static final String TAG = "NotificationPrefHelper";
71     private static final int XML_VERSION = 1;
72     private static final int UNKNOWN_UID = UserHandle.USER_NULL;
73     private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
74 
75     @VisibleForTesting
76     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 50000;
77 
78     @VisibleForTesting
79     static final String TAG_RANKING = "ranking";
80     private static final String TAG_PACKAGE = "package";
81     private static final String TAG_CHANNEL = "channel";
82     private static final String TAG_GROUP = "channelGroup";
83     private static final String TAG_DELEGATE = "delegate";
84     private static final String TAG_STATUS_ICONS = "silent_status_icons";
85 
86     private static final String ATT_VERSION = "version";
87     private static final String ATT_NAME = "name";
88     private static final String ATT_UID = "uid";
89     private static final String ATT_ID = "id";
90     private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
91     private static final String ATT_PRIORITY = "priority";
92     private static final String ATT_VISIBILITY = "visibility";
93     private static final String ATT_IMPORTANCE = "importance";
94     private static final String ATT_SHOW_BADGE = "show_badge";
95     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
96     private static final String ATT_ENABLED = "enabled";
97     private static final String ATT_USER_ALLOWED = "allowed";
98     private static final String ATT_HIDE_SILENT = "hide_gentle";
99 
100     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
101     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
102     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
103     @VisibleForTesting
104     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
105     private static final boolean DEFAULT_SHOW_BADGE = true;
106     static final boolean DEFAULT_ALLOW_BUBBLE = true;
107     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
108     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
109 
110     /**
111      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
112      * fields.
113      */
114     private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
115 
116     /**
117      * All user-lockable fields for a given application.
118      */
119     @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
120     public @interface LockableAppFields {
121         int USER_LOCKED_IMPORTANCE = 0x00000001;
122         int USER_LOCKED_BUBBLE = 0x00000002;
123     }
124 
125     // pkg|uid => PackagePreferences
126     private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
127     // pkg => PackagePreferences
128     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
129 
130     private final Context mContext;
131     private final PackageManager mPm;
132     private final RankingHandler mRankingHandler;
133     private final ZenModeHelper mZenModeHelper;
134 
135     private SparseBooleanArray mBadgingEnabled;
136     private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE;
137     private boolean mAreChannelsBypassingDnd;
138     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
139 
PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper)140     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
141             ZenModeHelper zenHelper) {
142         mContext = context;
143         mZenModeHelper = zenHelper;
144         mRankingHandler = rankingHandler;
145         mPm = pm;
146 
147         updateBadgingEnabled();
148         updateBubblesEnabled();
149         syncChannelsBypassingDnd(mContext.getUserId());
150     }
151 
readXml(XmlPullParser parser, boolean forRestore, int userId)152     public void readXml(XmlPullParser parser, boolean forRestore, int userId)
153             throws XmlPullParserException, IOException {
154         int type = parser.getEventType();
155         if (type != XmlPullParser.START_TAG) return;
156         String tag = parser.getName();
157         if (!TAG_RANKING.equals(tag)) return;
158         synchronized (mPackagePreferences) {
159             // Clobber groups and channels with the xml, but don't delete other data that wasn't
160             // present at the time of serialization.
161             mRestoredWithoutUids.clear();
162             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
163                 tag = parser.getName();
164                 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
165                     return;
166                 }
167                 if (type == XmlPullParser.START_TAG) {
168                     if (TAG_STATUS_ICONS.equals(tag)) {
169                         if (forRestore && userId != UserHandle.USER_SYSTEM) {
170                             continue;
171                         }
172                         mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
173                                 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
174                     } else if (TAG_PACKAGE.equals(tag)) {
175                         int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
176                         String name = parser.getAttributeValue(null, ATT_NAME);
177                         if (!TextUtils.isEmpty(name)) {
178                             if (forRestore) {
179                                 try {
180                                     uid = mPm.getPackageUidAsUser(name, userId);
181                                 } catch (PackageManager.NameNotFoundException e) {
182                                     // noop
183                                 }
184                             }
185                             boolean skipWarningLogged = false;
186 
187                             PackagePreferences r = getOrCreatePackagePreferencesLocked(name, uid,
188                                     XmlUtils.readIntAttribute(
189                                             parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
190                                     XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
191                                             DEFAULT_PRIORITY),
192                                     XmlUtils.readIntAttribute(
193                                             parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
194                                     XmlUtils.readBooleanAttribute(
195                                             parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
196                                     XmlUtils.readBooleanAttribute(
197                                             parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
198                             r.importance = XmlUtils.readIntAttribute(
199                                     parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
200                             r.priority = XmlUtils.readIntAttribute(
201                                     parser, ATT_PRIORITY, DEFAULT_PRIORITY);
202                             r.visibility = XmlUtils.readIntAttribute(
203                                     parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
204                             r.showBadge = XmlUtils.readBooleanAttribute(
205                                     parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
206                             r.lockedAppFields = XmlUtils.readIntAttribute(parser,
207                                     ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
208 
209                             final int innerDepth = parser.getDepth();
210                             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
211                                     && (type != XmlPullParser.END_TAG
212                                     || parser.getDepth() > innerDepth)) {
213                                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
214                                     continue;
215                                 }
216 
217                                 String tagName = parser.getName();
218                                 // Channel groups
219                                 if (TAG_GROUP.equals(tagName)) {
220                                     String id = parser.getAttributeValue(null, ATT_ID);
221                                     CharSequence groupName = parser.getAttributeValue(null,
222                                             ATT_NAME);
223                                     if (!TextUtils.isEmpty(id)) {
224                                         NotificationChannelGroup group
225                                                 = new NotificationChannelGroup(id, groupName);
226                                         group.populateFromXml(parser);
227                                         r.groups.put(id, group);
228                                     }
229                                 }
230                                 // Channels
231                                 if (TAG_CHANNEL.equals(tagName)) {
232                                     if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
233                                         if (!skipWarningLogged) {
234                                             Slog.w(TAG, "Skipping further channels for " + r.pkg
235                                                     + "; app has too many");
236                                             skipWarningLogged = true;
237                                         }
238                                         continue;
239                                     }
240                                     String id = parser.getAttributeValue(null, ATT_ID);
241                                     String channelName = parser.getAttributeValue(null, ATT_NAME);
242                                     int channelImportance = XmlUtils.readIntAttribute(
243                                             parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
244                                     if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
245                                         NotificationChannel channel = new NotificationChannel(id,
246                                                 channelName, channelImportance);
247                                         if (forRestore) {
248                                             channel.populateFromXmlForRestore(parser, mContext);
249                                         } else {
250                                             channel.populateFromXml(parser);
251                                         }
252                                         channel.setImportanceLockedByCriticalDeviceFunction(
253                                                 r.defaultAppLockedImportance);
254                                         r.channels.put(id, channel);
255                                     }
256                                 }
257                                 // Delegate
258                                 if (TAG_DELEGATE.equals(tagName)) {
259                                     int delegateId =
260                                             XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
261                                     String delegateName =
262                                             XmlUtils.readStringAttribute(parser, ATT_NAME);
263                                     boolean delegateEnabled = XmlUtils.readBooleanAttribute(
264                                             parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
265                                     boolean userAllowed = XmlUtils.readBooleanAttribute(
266                                             parser, ATT_USER_ALLOWED,
267                                             Delegate.DEFAULT_USER_ALLOWED);
268                                     Delegate d = null;
269                                     if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
270                                             delegateName)) {
271                                         d = new Delegate(
272                                                 delegateName, delegateId, delegateEnabled,
273                                                 userAllowed);
274                                     }
275                                     r.delegate = d;
276                                 }
277 
278                             }
279 
280                             try {
281                                 deleteDefaultChannelIfNeededLocked(r);
282                             } catch (PackageManager.NameNotFoundException e) {
283                                 Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e);
284                             }
285                         }
286                     }
287                 }
288             }
289         }
290         throw new IllegalStateException("Failed to reach END_DOCUMENT");
291     }
292 
getPackagePreferencesLocked(String pkg, int uid)293     private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) {
294         final String key = packagePreferencesKey(pkg, uid);
295         return mPackagePreferences.get(key);
296     }
297 
getOrCreatePackagePreferencesLocked(String pkg, int uid)298     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid) {
299         return getOrCreatePackagePreferencesLocked(pkg, uid,
300                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
301                 DEFAULT_ALLOW_BUBBLE);
302     }
303 
getOrCreatePackagePreferencesLocked(String pkg, int uid, int importance, int priority, int visibility, boolean showBadge, boolean allowBubble)304     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, int uid,
305             int importance, int priority, int visibility, boolean showBadge, boolean allowBubble) {
306         final String key = packagePreferencesKey(pkg, uid);
307         PackagePreferences
308                 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
309                 : mPackagePreferences.get(key);
310         if (r == null) {
311             r = new PackagePreferences();
312             r.pkg = pkg;
313             r.uid = uid;
314             r.importance = importance;
315             r.priority = priority;
316             r.visibility = visibility;
317             r.showBadge = showBadge;
318             r.allowBubble = allowBubble;
319 
320             try {
321                 createDefaultChannelIfNeededLocked(r);
322             } catch (PackageManager.NameNotFoundException e) {
323                 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
324             }
325 
326             if (r.uid == UNKNOWN_UID) {
327                 mRestoredWithoutUids.put(pkg, r);
328             } else {
329                 mPackagePreferences.put(key, r);
330             }
331         }
332         return r;
333     }
334 
shouldHaveDefaultChannel(PackagePreferences r)335     private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
336             PackageManager.NameNotFoundException {
337         final int userId = UserHandle.getUserId(r.uid);
338         final ApplicationInfo applicationInfo =
339                 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
340         if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
341             // O apps should not have the default channel.
342             return false;
343         }
344 
345         // Otherwise, this app should have the default channel.
346         return true;
347     }
348 
deleteDefaultChannelIfNeededLocked(PackagePreferences r)349     private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
350             PackageManager.NameNotFoundException {
351         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
352             // Not present
353             return false;
354         }
355 
356         if (shouldHaveDefaultChannel(r)) {
357             // Keep the default channel until upgraded.
358             return false;
359         }
360 
361         // Remove Default Channel.
362         r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
363 
364         return true;
365     }
366 
createDefaultChannelIfNeededLocked(PackagePreferences r)367     private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws
368             PackageManager.NameNotFoundException {
369         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
370             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
371                     com.android.internal.R.string.default_notification_channel_label));
372             return false;
373         }
374 
375         if (!shouldHaveDefaultChannel(r)) {
376             // Keep the default channel until upgraded.
377             return false;
378         }
379 
380         // Create Default Channel
381         NotificationChannel channel;
382         channel = new NotificationChannel(
383                 NotificationChannel.DEFAULT_CHANNEL_ID,
384                 mContext.getString(R.string.default_notification_channel_label),
385                 r.importance);
386         channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
387         channel.setLockscreenVisibility(r.visibility);
388         if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
389             channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
390         }
391         if (r.priority != DEFAULT_PRIORITY) {
392             channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
393         }
394         if (r.visibility != DEFAULT_VISIBILITY) {
395             channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
396         }
397         r.channels.put(channel.getId(), channel);
398 
399         return true;
400     }
401 
writeXml(XmlSerializer out, boolean forBackup, int userId)402     public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
403         out.startTag(null, TAG_RANKING);
404         out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
405         if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS
406                 && (!forBackup || userId == UserHandle.USER_SYSTEM)) {
407             out.startTag(null, TAG_STATUS_ICONS);
408             out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
409             out.endTag(null, TAG_STATUS_ICONS);
410         }
411 
412         synchronized (mPackagePreferences) {
413             final int N = mPackagePreferences.size();
414             for (int i = 0; i < N; i++) {
415                 final PackagePreferences r = mPackagePreferences.valueAt(i);
416                 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
417                     continue;
418                 }
419                 final boolean hasNonDefaultSettings =
420                         r.importance != DEFAULT_IMPORTANCE
421                                 || r.priority != DEFAULT_PRIORITY
422                                 || r.visibility != DEFAULT_VISIBILITY
423                                 || r.showBadge != DEFAULT_SHOW_BADGE
424                                 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
425                                 || r.channels.size() > 0
426                                 || r.groups.size() > 0
427                                 || r.delegate != null
428                                 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
429                 if (hasNonDefaultSettings) {
430                     out.startTag(null, TAG_PACKAGE);
431                     out.attribute(null, ATT_NAME, r.pkg);
432                     if (r.importance != DEFAULT_IMPORTANCE) {
433                         out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
434                     }
435                     if (r.priority != DEFAULT_PRIORITY) {
436                         out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
437                     }
438                     if (r.visibility != DEFAULT_VISIBILITY) {
439                         out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
440                     }
441                     if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
442                         out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
443                     }
444                     out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
445                     out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
446                             Integer.toString(r.lockedAppFields));
447 
448                     if (!forBackup) {
449                         out.attribute(null, ATT_UID, Integer.toString(r.uid));
450                     }
451 
452                     if (r.delegate != null) {
453                         out.startTag(null, TAG_DELEGATE);
454 
455                         out.attribute(null, ATT_NAME, r.delegate.mPkg);
456                         out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
457                         if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
458                             out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
459                         }
460                         if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
461                             out.attribute(null, ATT_USER_ALLOWED,
462                                     Boolean.toString(r.delegate.mUserAllowed));
463                         }
464                         out.endTag(null, TAG_DELEGATE);
465                     }
466 
467                     for (NotificationChannelGroup group : r.groups.values()) {
468                         group.writeXml(out);
469                     }
470 
471                     for (NotificationChannel channel : r.channels.values()) {
472                         if (forBackup) {
473                             if (!channel.isDeleted()) {
474                                 channel.writeXmlForBackup(out, mContext);
475                             }
476                         } else {
477                             channel.writeXml(out);
478                         }
479                     }
480 
481                     out.endTag(null, TAG_PACKAGE);
482                 }
483             }
484         }
485         out.endTag(null, TAG_RANKING);
486     }
487 
488     /**
489      * Sets whether bubbles are allowed.
490      *
491      * @param pkg the package to allow or not allow bubbles for.
492      * @param uid the uid to allow or not allow bubbles for.
493      * @param allowed whether bubbles are allowed.
494      */
setBubblesAllowed(String pkg, int uid, boolean allowed)495     public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
496         boolean changed = false;
497         synchronized (mPackagePreferences) {
498             PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
499             changed = p.allowBubble != allowed;
500             p.allowBubble = allowed;
501             p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
502         }
503         if (changed) {
504             updateConfig();
505         }
506     }
507 
508     /**
509      * Whether bubbles are allowed.
510      *
511      * @param pkg the package to check if bubbles are allowed for
512      * @param uid the uid to check if bubbles are allowed for.
513      * @return whether bubbles are allowed.
514      */
515     @Override
areBubblesAllowed(String pkg, int uid)516     public boolean areBubblesAllowed(String pkg, int uid) {
517         synchronized (mPackagePreferences) {
518             return getOrCreatePackagePreferencesLocked(pkg, uid).allowBubble;
519         }
520     }
521 
getAppLockedFields(String pkg, int uid)522     public int getAppLockedFields(String pkg, int uid) {
523         synchronized (mPackagePreferences) {
524             return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields;
525         }
526     }
527 
528     /**
529      * Gets importance.
530      */
531     @Override
getImportance(String packageName, int uid)532     public int getImportance(String packageName, int uid) {
533         synchronized (mPackagePreferences) {
534             return getOrCreatePackagePreferencesLocked(packageName, uid).importance;
535         }
536     }
537 
538     /**
539      * Returns whether the importance of the corresponding notification is user-locked and shouldn't
540      * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
541      * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
542      */
getIsAppImportanceLocked(String packageName, int uid)543     public boolean getIsAppImportanceLocked(String packageName, int uid) {
544         synchronized (mPackagePreferences) {
545             int userLockedFields = getOrCreatePackagePreferencesLocked(packageName, uid).lockedAppFields;
546             return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
547         }
548     }
549 
550     @Override
canShowBadge(String packageName, int uid)551     public boolean canShowBadge(String packageName, int uid) {
552         synchronized (mPackagePreferences) {
553             return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;
554         }
555     }
556 
557     @Override
setShowBadge(String packageName, int uid, boolean showBadge)558     public void setShowBadge(String packageName, int uid, boolean showBadge) {
559         synchronized (mPackagePreferences) {
560             getOrCreatePackagePreferencesLocked(packageName, uid).showBadge = showBadge;
561         }
562         updateConfig();
563     }
564 
565     @Override
isGroupBlocked(String packageName, int uid, String groupId)566     public boolean isGroupBlocked(String packageName, int uid, String groupId) {
567         if (groupId == null) {
568             return false;
569         }
570         synchronized (mPackagePreferences) {
571             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
572             NotificationChannelGroup group = r.groups.get(groupId);
573             if (group == null) {
574                 return false;
575             }
576             return group.isBlocked();
577         }
578     }
579 
getPackagePriority(String pkg, int uid)580     int getPackagePriority(String pkg, int uid) {
581         synchronized (mPackagePreferences) {
582             return getOrCreatePackagePreferencesLocked(pkg, uid).priority;
583         }
584     }
585 
getPackageVisibility(String pkg, int uid)586     int getPackageVisibility(String pkg, int uid) {
587         synchronized (mPackagePreferences) {
588             return getOrCreatePackagePreferencesLocked(pkg, uid).visibility;
589         }
590     }
591 
592     @Override
createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp)593     public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
594             boolean fromTargetApp) {
595         Preconditions.checkNotNull(pkg);
596         Preconditions.checkNotNull(group);
597         Preconditions.checkNotNull(group.getId());
598         Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
599         synchronized (mPackagePreferences) {
600             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
601             if (r == null) {
602                 throw new IllegalArgumentException("Invalid package");
603             }
604             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
605             if (!group.equals(oldGroup)) {
606                 // will log for new entries as well as name/description changes
607                 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
608             }
609             if (oldGroup != null) {
610                 group.setChannels(oldGroup.getChannels());
611 
612                 // apps can't update the blocked status or app overlay permission
613                 if (fromTargetApp) {
614                     group.setBlocked(oldGroup.isBlocked());
615                     group.unlockFields(group.getUserLockedFields());
616                     group.lockFields(oldGroup.getUserLockedFields());
617                 } else {
618                     // but the system can
619                     if (group.isBlocked() != oldGroup.isBlocked()) {
620                         group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
621                         updateChannelsBypassingDnd(mContext.getUserId());
622                     }
623                 }
624             }
625             r.groups.put(group.getId(), group);
626         }
627     }
628 
629     @Override
createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess)630     public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
631             boolean fromTargetApp, boolean hasDndAccess) {
632         Preconditions.checkNotNull(pkg);
633         Preconditions.checkNotNull(channel);
634         Preconditions.checkNotNull(channel.getId());
635         Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
636         boolean needsPolicyFileChange = false;
637         synchronized (mPackagePreferences) {
638             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
639             if (r == null) {
640                 throw new IllegalArgumentException("Invalid package");
641             }
642             if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
643                 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
644             }
645             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
646                 throw new IllegalArgumentException("Reserved id");
647             }
648             NotificationChannel existing = r.channels.get(channel.getId());
649             // Keep most of the existing settings
650             if (existing != null && fromTargetApp) {
651                 if (existing.isDeleted()) {
652                     existing.setDeleted(false);
653                     needsPolicyFileChange = true;
654 
655                     // log a resurrected channel as if it's new again
656                     MetricsLogger.action(getChannelLog(channel, pkg).setType(
657                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
658                 }
659 
660                 if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
661                     existing.setName(channel.getName().toString());
662                     needsPolicyFileChange = true;
663                 }
664                 if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
665                     existing.setDescription(channel.getDescription());
666                     needsPolicyFileChange = true;
667                 }
668                 if (channel.isBlockableSystem() != existing.isBlockableSystem()) {
669                     existing.setBlockableSystem(channel.isBlockableSystem());
670                     needsPolicyFileChange = true;
671                 }
672                 if (channel.getGroup() != null && existing.getGroup() == null) {
673                     existing.setGroup(channel.getGroup());
674                     needsPolicyFileChange = true;
675                 }
676 
677                 // Apps are allowed to downgrade channel importance if the user has not changed any
678                 // fields on this channel yet.
679                 final int previousExistingImportance = existing.getImportance();
680                 if (existing.getUserLockedFields() == 0 &&
681                         channel.getImportance() < existing.getImportance()) {
682                     existing.setImportance(channel.getImportance());
683                     needsPolicyFileChange = true;
684                 }
685 
686                 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
687                 // fields on the channel yet
688                 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
689                     boolean bypassDnd = channel.canBypassDnd();
690                     if (bypassDnd != existing.canBypassDnd()) {
691                         existing.setBypassDnd(bypassDnd);
692                         needsPolicyFileChange = true;
693 
694                         if (bypassDnd != mAreChannelsBypassingDnd
695                                 || previousExistingImportance != existing.getImportance()) {
696                             updateChannelsBypassingDnd(mContext.getUserId());
697                         }
698                     }
699                 }
700 
701                 updateConfig();
702                 return needsPolicyFileChange;
703             }
704 
705             if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
706                 throw new IllegalStateException("Limit exceed; cannot create more channels");
707             }
708 
709             needsPolicyFileChange = true;
710 
711             if (channel.getImportance() < IMPORTANCE_NONE
712                     || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
713                 throw new IllegalArgumentException("Invalid importance level");
714             }
715 
716             // Reset fields that apps aren't allowed to set.
717             if (fromTargetApp && !hasDndAccess) {
718                 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
719             }
720             if (fromTargetApp) {
721                 channel.setLockscreenVisibility(r.visibility);
722             }
723             clearLockedFieldsLocked(channel);
724             channel.setImportanceLockedByOEM(r.oemLockedImportance);
725             if (!channel.isImportanceLockedByOEM()) {
726                 if (r.futureOemLockedChannels.remove(channel.getId())) {
727                     channel.setImportanceLockedByOEM(true);
728                 }
729             }
730             channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
731             if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
732                 channel.setLockscreenVisibility(
733                         NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
734             }
735             if (!r.showBadge) {
736                 channel.setShowBadge(false);
737             }
738 
739             r.channels.put(channel.getId(), channel);
740             if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
741                 updateChannelsBypassingDnd(mContext.getUserId());
742             }
743             MetricsLogger.action(getChannelLog(channel, pkg).setType(
744                     com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
745         }
746 
747         return needsPolicyFileChange;
748     }
749 
clearLockedFieldsLocked(NotificationChannel channel)750     void clearLockedFieldsLocked(NotificationChannel channel) {
751         channel.unlockFields(channel.getUserLockedFields());
752     }
753 
754     @Override
updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, boolean fromUser)755     public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
756             boolean fromUser) {
757         Preconditions.checkNotNull(updatedChannel);
758         Preconditions.checkNotNull(updatedChannel.getId());
759         synchronized (mPackagePreferences) {
760             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
761             if (r == null) {
762                 throw new IllegalArgumentException("Invalid package");
763             }
764             NotificationChannel channel = r.channels.get(updatedChannel.getId());
765             if (channel == null || channel.isDeleted()) {
766                 throw new IllegalArgumentException("Channel does not exist");
767             }
768             if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
769                 updatedChannel.setLockscreenVisibility(
770                         NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
771             }
772             if (fromUser) {
773                 updatedChannel.lockFields(channel.getUserLockedFields());
774                 lockFieldsForUpdateLocked(channel, updatedChannel);
775             } else {
776                 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
777             }
778             // no importance updates are allowed if OEM blocked it
779             updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
780             if (updatedChannel.isImportanceLockedByOEM()) {
781                 updatedChannel.setImportance(channel.getImportance());
782             }
783             updatedChannel.setImportanceLockedByCriticalDeviceFunction(
784                     r.defaultAppLockedImportance);
785             if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()
786                     && updatedChannel.getImportance() == IMPORTANCE_NONE) {
787                 updatedChannel.setImportance(channel.getImportance());
788             }
789 
790             r.channels.put(updatedChannel.getId(), updatedChannel);
791 
792             if (onlyHasDefaultChannel(pkg, uid)) {
793                 // copy settings to app level so they are inherited by new channels
794                 // when the app migrates
795                 r.importance = updatedChannel.getImportance();
796                 r.priority = updatedChannel.canBypassDnd()
797                         ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
798                 r.visibility = updatedChannel.getLockscreenVisibility();
799                 r.showBadge = updatedChannel.canShowBadge();
800             }
801 
802             if (!channel.equals(updatedChannel)) {
803                 // only log if there are real changes
804                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
805                         .setSubtype(fromUser ? 1 : 0));
806             }
807 
808             if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
809                     || channel.getImportance() != updatedChannel.getImportance()) {
810                 updateChannelsBypassingDnd(mContext.getUserId());
811             }
812         }
813         updateConfig();
814     }
815 
816     @Override
getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)817     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
818             boolean includeDeleted) {
819         Preconditions.checkNotNull(pkg);
820         synchronized (mPackagePreferences) {
821             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
822             if (r == null) {
823                 return null;
824             }
825             if (channelId == null) {
826                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
827             }
828             final NotificationChannel nc = r.channels.get(channelId);
829             if (nc != null && (includeDeleted || !nc.isDeleted())) {
830                 return nc;
831             }
832             return null;
833         }
834     }
835 
836     @Override
deleteNotificationChannel(String pkg, int uid, String channelId)837     public void deleteNotificationChannel(String pkg, int uid, String channelId) {
838         synchronized (mPackagePreferences) {
839             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
840             if (r == null) {
841                 return;
842             }
843             NotificationChannel channel = r.channels.get(channelId);
844             if (channel != null) {
845                 channel.setDeleted(true);
846                 LogMaker lm = getChannelLog(channel, pkg);
847                 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
848                 MetricsLogger.action(lm);
849 
850                 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
851                     updateChannelsBypassingDnd(mContext.getUserId());
852                 }
853             }
854         }
855     }
856 
857     @Override
858     @VisibleForTesting
permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)859     public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
860         Preconditions.checkNotNull(pkg);
861         Preconditions.checkNotNull(channelId);
862         synchronized (mPackagePreferences) {
863             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
864             if (r == null) {
865                 return;
866             }
867             r.channels.remove(channelId);
868         }
869     }
870 
871     @Override
permanentlyDeleteNotificationChannels(String pkg, int uid)872     public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
873         Preconditions.checkNotNull(pkg);
874         synchronized (mPackagePreferences) {
875             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
876             if (r == null) {
877                 return;
878             }
879             int N = r.channels.size() - 1;
880             for (int i = N; i >= 0; i--) {
881                 String key = r.channels.keyAt(i);
882                 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
883                     r.channels.remove(key);
884                 }
885             }
886         }
887     }
888 
shouldHideSilentStatusIcons()889     public boolean shouldHideSilentStatusIcons() {
890         return mHideSilentStatusBarIcons;
891     }
892 
setHideSilentStatusIcons(boolean hide)893     public void setHideSilentStatusIcons(boolean hide) {
894         mHideSilentStatusBarIcons = hide;
895     }
896 
lockChannelsForOEM(String[] appOrChannelList)897     public void lockChannelsForOEM(String[] appOrChannelList) {
898         if (appOrChannelList == null) {
899             return;
900         }
901         for (String appOrChannel : appOrChannelList) {
902             if (!TextUtils.isEmpty(appOrChannel)) {
903                 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
904                 if (appSplit != null && appSplit.length > 0) {
905                     String appName = appSplit[0];
906                     String channelId = appSplit.length == 2 ? appSplit[1] : null;
907 
908                     synchronized (mPackagePreferences) {
909                         for (PackagePreferences r : mPackagePreferences.values()) {
910                             if (r.pkg.equals(appName)) {
911                                 if (channelId == null) {
912                                     // lock all channels for the app
913                                     r.oemLockedImportance = true;
914                                     for (NotificationChannel channel : r.channels.values()) {
915                                         channel.setImportanceLockedByOEM(true);
916                                     }
917                                 } else {
918                                     NotificationChannel channel = r.channels.get(channelId);
919                                     if (channel != null) {
920                                         channel.setImportanceLockedByOEM(true);
921                                     } else {
922                                         // if this channel shows up in the future, make sure it'll
923                                         // be locked immediately
924                                         r.futureOemLockedChannels.add(channelId);
925                                     }
926                                 }
927                             }
928                         }
929                     }
930                 }
931             }
932         }
933     }
934 
updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd)935     public void updateDefaultApps(int userId, ArraySet<String> toRemove,
936             ArraySet<Pair<String, Integer>> toAdd) {
937         synchronized (mPackagePreferences) {
938             for (PackagePreferences p : mPackagePreferences.values()) {
939                 if (userId == UserHandle.getUserId(p.uid)) {
940                     if (toRemove != null && toRemove.contains(p.pkg)) {
941                         p.defaultAppLockedImportance = false;
942                         for (NotificationChannel channel : p.channels.values()) {
943                             channel.setImportanceLockedByCriticalDeviceFunction(false);
944                         }
945                     }
946                 }
947             }
948             if (toAdd != null) {
949                 for (Pair<String, Integer> approvedApp : toAdd) {
950                     PackagePreferences p = getOrCreatePackagePreferencesLocked(approvedApp.first,
951                             approvedApp.second);
952                     p.defaultAppLockedImportance = true;
953                     for (NotificationChannel channel : p.channels.values()) {
954                         channel.setImportanceLockedByCriticalDeviceFunction(true);
955                     }
956                 }
957             }
958         }
959     }
960 
getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted)961     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
962             int uid, String groupId, boolean includeDeleted) {
963         Preconditions.checkNotNull(pkg);
964         synchronized (mPackagePreferences) {
965             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
966             if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
967                 return null;
968             }
969             NotificationChannelGroup group = r.groups.get(groupId).clone();
970             group.setChannels(new ArrayList<>());
971             int N = r.channels.size();
972             for (int i = 0; i < N; i++) {
973                 final NotificationChannel nc = r.channels.valueAt(i);
974                 if (includeDeleted || !nc.isDeleted()) {
975                     if (groupId.equals(nc.getGroup())) {
976                         group.addChannel(nc);
977                     }
978                 }
979             }
980             return group;
981         }
982     }
983 
getNotificationChannelGroup(String groupId, String pkg, int uid)984     public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
985             int uid) {
986         Preconditions.checkNotNull(pkg);
987         synchronized (mPackagePreferences) {
988             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
989             if (r == null) {
990                 return null;
991             }
992             return r.groups.get(groupId);
993         }
994     }
995 
996     @Override
getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty)997     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
998             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
999         Preconditions.checkNotNull(pkg);
1000         Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
1001         synchronized (mPackagePreferences) {
1002             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1003             if (r == null) {
1004                 return ParceledListSlice.emptyList();
1005             }
1006             NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
1007             int N = r.channels.size();
1008             for (int i = 0; i < N; i++) {
1009                 final NotificationChannel nc = r.channels.valueAt(i);
1010                 if (includeDeleted || !nc.isDeleted()) {
1011                     if (nc.getGroup() != null) {
1012                         if (r.groups.get(nc.getGroup()) != null) {
1013                             NotificationChannelGroup ncg = groups.get(nc.getGroup());
1014                             if (ncg == null) {
1015                                 ncg = r.groups.get(nc.getGroup()).clone();
1016                                 ncg.setChannels(new ArrayList<>());
1017                                 groups.put(nc.getGroup(), ncg);
1018 
1019                             }
1020                             ncg.addChannel(nc);
1021                         }
1022                     } else {
1023                         nonGrouped.addChannel(nc);
1024                     }
1025                 }
1026             }
1027             if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
1028                 groups.put(null, nonGrouped);
1029             }
1030             if (includeEmpty) {
1031                 for (NotificationChannelGroup group : r.groups.values()) {
1032                     if (!groups.containsKey(group.getId())) {
1033                         groups.put(group.getId(), group);
1034                     }
1035                 }
1036             }
1037             return new ParceledListSlice<>(new ArrayList<>(groups.values()));
1038         }
1039     }
1040 
deleteNotificationChannelGroup(String pkg, int uid, String groupId)1041     public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
1042             String groupId) {
1043         List<NotificationChannel> deletedChannels = new ArrayList<>();
1044         synchronized (mPackagePreferences) {
1045             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1046             if (r == null || TextUtils.isEmpty(groupId)) {
1047                 return deletedChannels;
1048             }
1049 
1050             r.groups.remove(groupId);
1051 
1052             int N = r.channels.size();
1053             for (int i = 0; i < N; i++) {
1054                 final NotificationChannel nc = r.channels.valueAt(i);
1055                 if (groupId.equals(nc.getGroup())) {
1056                     nc.setDeleted(true);
1057                     deletedChannels.add(nc);
1058                 }
1059             }
1060         }
1061         return deletedChannels;
1062     }
1063 
1064     @Override
getNotificationChannelGroups(String pkg, int uid)1065     public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
1066             int uid) {
1067         List<NotificationChannelGroup> groups = new ArrayList<>();
1068         synchronized (mPackagePreferences) {
1069             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1070             if (r == null) {
1071                 return groups;
1072             }
1073             groups.addAll(r.groups.values());
1074         }
1075         return groups;
1076     }
1077 
1078     @Override
getNotificationChannels(String pkg, int uid, boolean includeDeleted)1079     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
1080             boolean includeDeleted) {
1081         Preconditions.checkNotNull(pkg);
1082         List<NotificationChannel> channels = new ArrayList<>();
1083         synchronized (mPackagePreferences) {
1084             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1085             if (r == null) {
1086                 return ParceledListSlice.emptyList();
1087             }
1088             int N = r.channels.size();
1089             for (int i = 0; i < N; i++) {
1090                 final NotificationChannel nc = r.channels.valueAt(i);
1091                 if (includeDeleted || !nc.isDeleted()) {
1092                     channels.add(nc);
1093                 }
1094             }
1095             return new ParceledListSlice<>(channels);
1096         }
1097     }
1098 
1099     /**
1100      * Gets all notification channels associated with the given pkg and userId that can bypass dnd
1101      */
getNotificationChannelsBypassingDnd(String pkg, int userId)1102     public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
1103             int userId) {
1104         List<NotificationChannel> channels = new ArrayList<>();
1105         synchronized (mPackagePreferences) {
1106             final PackagePreferences r = mPackagePreferences.get(
1107                     packagePreferencesKey(pkg, userId));
1108             // notifications from this package aren't blocked
1109             if (r != null && r.importance != IMPORTANCE_NONE) {
1110                 for (NotificationChannel channel : r.channels.values()) {
1111                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1112                         channels.add(channel);
1113                     }
1114                 }
1115             }
1116         }
1117         return new ParceledListSlice<>(channels);
1118     }
1119 
1120     /**
1121      * True for pre-O apps that only have the default channel, or pre O apps that have no
1122      * channels yet. This method will create the default channel for pre-O apps that don't have it.
1123      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
1124      * upgrades.
1125      */
onlyHasDefaultChannel(String pkg, int uid)1126     public boolean onlyHasDefaultChannel(String pkg, int uid) {
1127         synchronized (mPackagePreferences) {
1128             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1129             if (r.channels.size() == 1
1130                     && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1131                 return true;
1132             }
1133             return false;
1134         }
1135     }
1136 
getDeletedChannelCount(String pkg, int uid)1137     public int getDeletedChannelCount(String pkg, int uid) {
1138         Preconditions.checkNotNull(pkg);
1139         int deletedCount = 0;
1140         synchronized (mPackagePreferences) {
1141             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1142             if (r == null) {
1143                 return deletedCount;
1144             }
1145             int N = r.channels.size();
1146             for (int i = 0; i < N; i++) {
1147                 final NotificationChannel nc = r.channels.valueAt(i);
1148                 if (nc.isDeleted()) {
1149                     deletedCount++;
1150                 }
1151             }
1152             return deletedCount;
1153         }
1154     }
1155 
getBlockedChannelCount(String pkg, int uid)1156     public int getBlockedChannelCount(String pkg, int uid) {
1157         Preconditions.checkNotNull(pkg);
1158         int blockedCount = 0;
1159         synchronized (mPackagePreferences) {
1160             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1161             if (r == null) {
1162                 return blockedCount;
1163             }
1164             int N = r.channels.size();
1165             for (int i = 0; i < N; i++) {
1166                 final NotificationChannel nc = r.channels.valueAt(i);
1167                 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1168                     blockedCount++;
1169                 }
1170             }
1171             return blockedCount;
1172         }
1173     }
1174 
getBlockedAppCount(int userId)1175     public int getBlockedAppCount(int userId) {
1176         int count = 0;
1177         synchronized (mPackagePreferences) {
1178             final int N = mPackagePreferences.size();
1179             for (int i = 0; i < N; i++) {
1180                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1181                 if (userId == UserHandle.getUserId(r.uid)
1182                         && r.importance == IMPORTANCE_NONE) {
1183                     count++;
1184                 }
1185             }
1186         }
1187         return count;
1188     }
1189 
1190     /**
1191      * Returns the number of apps that have at least one notification channel that can bypass DND
1192      * for given particular user
1193      */
getAppsBypassingDndCount(int userId)1194     public int getAppsBypassingDndCount(int userId) {
1195         int count = 0;
1196         synchronized (mPackagePreferences) {
1197             final int numPackagePreferences = mPackagePreferences.size();
1198             for (int i = 0; i < numPackagePreferences; i++) {
1199                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1200                 // Package isn't associated with this userId or notifications from this package are
1201                 // blocked
1202                 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1203                     continue;
1204                 }
1205 
1206                 for (NotificationChannel channel : r.channels.values()) {
1207                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1208                         count++;
1209                         break;
1210                     }
1211                 }
1212             }
1213         }
1214         return count;
1215     }
1216 
1217     /**
1218      * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1219      * updating
1220      * @param userId
1221      */
syncChannelsBypassingDnd(int userId)1222     private void syncChannelsBypassingDnd(int userId) {
1223         mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1224                 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1225         updateChannelsBypassingDnd(userId);
1226     }
1227 
1228     /**
1229      * Updates the user's NotificationPolicy based on whether the given userId
1230      * has channels bypassing DND
1231      * @param userId
1232      */
updateChannelsBypassingDnd(int userId)1233     private void updateChannelsBypassingDnd(int userId) {
1234         synchronized (mPackagePreferences) {
1235             final int numPackagePreferences = mPackagePreferences.size();
1236             for (int i = 0; i < numPackagePreferences; i++) {
1237                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1238                 // Package isn't associated with this userId or notifications from this package are
1239                 // blocked
1240                 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1241                     continue;
1242                 }
1243 
1244                 for (NotificationChannel channel : r.channels.values()) {
1245                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1246                         if (!mAreChannelsBypassingDnd) {
1247                             mAreChannelsBypassingDnd = true;
1248                             updateZenPolicy(true);
1249                         }
1250                         return;
1251                     }
1252                 }
1253             }
1254         }
1255         // If no channels bypass DND, update the zen policy once to disable DND bypass.
1256         if (mAreChannelsBypassingDnd) {
1257             mAreChannelsBypassingDnd = false;
1258             updateZenPolicy(false);
1259         }
1260     }
1261 
channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel)1262     private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) {
1263         // Channel is in a group that's blocked
1264         if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1265             return false;
1266         }
1267 
1268         // Channel is deleted or is blocked
1269         if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1270             return false;
1271         }
1272 
1273         return true;
1274     }
1275 
updateZenPolicy(boolean areChannelsBypassingDnd)1276     public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1277         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1278         mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1279                 policy.priorityCategories, policy.priorityCallSenders,
1280                 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1281                 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1282                         : 0)));
1283     }
1284 
areChannelsBypassingDnd()1285     public boolean areChannelsBypassingDnd() {
1286         return mAreChannelsBypassingDnd;
1287     }
1288 
1289     /**
1290      * Sets importance.
1291      */
1292     @Override
setImportance(String pkgName, int uid, int importance)1293     public void setImportance(String pkgName, int uid, int importance) {
1294         synchronized (mPackagePreferences) {
1295             getOrCreatePackagePreferencesLocked(pkgName, uid).importance = importance;
1296         }
1297         updateConfig();
1298     }
1299 
setEnabled(String packageName, int uid, boolean enabled)1300     public void setEnabled(String packageName, int uid, boolean enabled) {
1301         boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1302         if (wasEnabled == enabled) {
1303             return;
1304         }
1305         setImportance(packageName, uid,
1306                 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1307     }
1308 
1309     /**
1310      * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1311      * {@code uid}, have their importance locked by the user. Locked notifications don't get
1312      * considered for sentiment adjustments (and thus never show a blocking helper).
1313      */
setAppImportanceLocked(String packageName, int uid)1314     public void setAppImportanceLocked(String packageName, int uid) {
1315         synchronized (mPackagePreferences) {
1316             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid);
1317             if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1318                 return;
1319             }
1320 
1321             prefs.lockedAppFields =
1322                     prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1323         }
1324         updateConfig();
1325     }
1326 
1327     /**
1328      * Returns the delegate for a given package, if it's allowed by the package and the user.
1329      */
getNotificationDelegate(String sourcePkg, int sourceUid)1330     public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1331         synchronized (mPackagePreferences) {
1332             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1333 
1334             if (prefs == null || prefs.delegate == null) {
1335                 return null;
1336             }
1337             if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1338                 return null;
1339             }
1340             return prefs.delegate.mPkg;
1341         }
1342     }
1343 
1344     /**
1345      * Used by an app to delegate notification posting privileges to another apps.
1346      */
setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid)1347     public void setNotificationDelegate(String sourcePkg, int sourceUid,
1348             String delegatePkg, int delegateUid) {
1349         synchronized (mPackagePreferences) {
1350             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
1351 
1352             boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1353             Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1354             prefs.delegate = delegate;
1355         }
1356         updateConfig();
1357     }
1358 
1359     /**
1360      * Used by an app to turn off its notification delegate.
1361      */
revokeNotificationDelegate(String sourcePkg, int sourceUid)1362     public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1363         boolean changed = false;
1364         synchronized (mPackagePreferences) {
1365             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1366             if (prefs != null && prefs.delegate != null) {
1367                 prefs.delegate.mEnabled = false;
1368                 changed = true;
1369             }
1370         }
1371         if (changed) {
1372             updateConfig();
1373         }
1374     }
1375 
1376     /**
1377      * Toggles whether an app can have a notification delegate on behalf of a user.
1378      */
toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed)1379     public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1380         boolean changed = false;
1381         synchronized (mPackagePreferences) {
1382             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1383             if (prefs != null && prefs.delegate != null) {
1384                 prefs.delegate.mUserAllowed = userAllowed;
1385                 changed = true;
1386             }
1387         }
1388         if (changed) {
1389             updateConfig();
1390         }
1391     }
1392 
1393     /**
1394      * Returns whether the given app is allowed on post notifications on behalf of the other given
1395      * app.
1396      */
isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid)1397     public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1398             String potentialDelegatePkg, int potentialDelegateUid) {
1399         synchronized (mPackagePreferences) {
1400             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1401 
1402             return prefs != null && prefs.isValidDelegate(potentialDelegatePkg,
1403                     potentialDelegateUid);
1404         }
1405     }
1406 
1407     @VisibleForTesting
lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update)1408     void lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update) {
1409         if (original.canBypassDnd() != update.canBypassDnd()) {
1410             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1411         }
1412         if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1413             update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1414         }
1415         if (original.getImportance() != update.getImportance()) {
1416             update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1417         }
1418         if (original.shouldShowLights() != update.shouldShowLights()
1419                 || original.getLightColor() != update.getLightColor()) {
1420             update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1421         }
1422         if (!Objects.equals(original.getSound(), update.getSound())) {
1423             update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1424         }
1425         if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1426                 || original.shouldVibrate() != update.shouldVibrate()) {
1427             update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1428         }
1429         if (original.canShowBadge() != update.canShowBadge()) {
1430             update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1431         }
1432         if (original.canBubble() != update.canBubble()) {
1433             update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
1434         }
1435     }
1436 
dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter)1437     public void dump(PrintWriter pw, String prefix,
1438             @NonNull NotificationManagerService.DumpFilter filter) {
1439         pw.print(prefix);
1440         pw.println("per-package config:");
1441 
1442         pw.println("PackagePreferences:");
1443         synchronized (mPackagePreferences) {
1444             dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences);
1445         }
1446         pw.println("Restored without uid:");
1447         dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids);
1448     }
1449 
dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter)1450     public void dump(ProtoOutputStream proto,
1451             @NonNull NotificationManagerService.DumpFilter filter) {
1452         synchronized (mPackagePreferences) {
1453             dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
1454                     mPackagePreferences);
1455         }
1456         dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1457                 mRestoredWithoutUids);
1458     }
1459 
dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1460     private static void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
1461             @NonNull NotificationManagerService.DumpFilter filter,
1462             ArrayMap<String, PackagePreferences> packagePreferences) {
1463         final int N = packagePreferences.size();
1464         for (int i = 0; i < N; i++) {
1465             final PackagePreferences r = packagePreferences.valueAt(i);
1466             if (filter.matches(r.pkg)) {
1467                 pw.print(prefix);
1468                 pw.print("  AppSettings: ");
1469                 pw.print(r.pkg);
1470                 pw.print(" (");
1471                 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
1472                 pw.print(')');
1473                 if (r.importance != DEFAULT_IMPORTANCE) {
1474                     pw.print(" importance=");
1475                     pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1476                 }
1477                 if (r.priority != DEFAULT_PRIORITY) {
1478                     pw.print(" priority=");
1479                     pw.print(Notification.priorityToString(r.priority));
1480                 }
1481                 if (r.visibility != DEFAULT_VISIBILITY) {
1482                     pw.print(" visibility=");
1483                     pw.print(Notification.visibilityToString(r.visibility));
1484                 }
1485                 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1486                     pw.print(" showBadge=");
1487                     pw.print(r.showBadge);
1488                 }
1489                 if (r.defaultAppLockedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) {
1490                     pw.print(" defaultAppLocked=");
1491                     pw.print(r.defaultAppLockedImportance);
1492                 }
1493                 if (r.oemLockedImportance != DEFAULT_OEM_LOCKED_IMPORTANCE) {
1494                     pw.print(" oemLocked=");
1495                     pw.print(r.oemLockedImportance);
1496                 }
1497                 if (!r.futureOemLockedChannels.isEmpty()) {
1498                     pw.print(" futureLockedChannels=");
1499                     pw.print(r.futureOemLockedChannels);
1500                 }
1501                 pw.println();
1502                 for (NotificationChannel channel : r.channels.values()) {
1503                     pw.print(prefix);
1504                     channel.dump(pw, "    ", filter.redact);
1505                 }
1506                 for (NotificationChannelGroup group : r.groups.values()) {
1507                     pw.print(prefix);
1508                     pw.print("  ");
1509                     pw.print("  ");
1510                     pw.println(group);
1511                 }
1512             }
1513         }
1514     }
1515 
dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences)1516     private static void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId,
1517             @NonNull NotificationManagerService.DumpFilter filter,
1518             ArrayMap<String, PackagePreferences> packagePreferences) {
1519         final int N = packagePreferences.size();
1520         long fToken;
1521         for (int i = 0; i < N; i++) {
1522             final PackagePreferences r = packagePreferences.valueAt(i);
1523             if (filter.matches(r.pkg)) {
1524                 fToken = proto.start(fieldId);
1525 
1526                 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1527                 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1528                 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1529                 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1530                 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1531                 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1532 
1533                 for (NotificationChannel channel : r.channels.values()) {
1534                     channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1535                 }
1536                 for (NotificationChannelGroup group : r.groups.values()) {
1537                     group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1538                 }
1539 
1540                 proto.end(fToken);
1541             }
1542         }
1543     }
1544 
dumpJson(NotificationManagerService.DumpFilter filter)1545     public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1546         JSONObject ranking = new JSONObject();
1547         JSONArray PackagePreferencess = new JSONArray();
1548         try {
1549             ranking.put("noUid", mRestoredWithoutUids.size());
1550         } catch (JSONException e) {
1551             // pass
1552         }
1553         synchronized (mPackagePreferences) {
1554             final int N = mPackagePreferences.size();
1555             for (int i = 0; i < N; i++) {
1556                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1557                 if (filter == null || filter.matches(r.pkg)) {
1558                     JSONObject PackagePreferences = new JSONObject();
1559                     try {
1560                         PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1561                         PackagePreferences.put("packageName", r.pkg);
1562                         if (r.importance != DEFAULT_IMPORTANCE) {
1563                             PackagePreferences.put("importance",
1564                                     NotificationListenerService.Ranking.importanceToString(
1565                                             r.importance));
1566                         }
1567                         if (r.priority != DEFAULT_PRIORITY) {
1568                             PackagePreferences.put("priority",
1569                                     Notification.priorityToString(r.priority));
1570                         }
1571                         if (r.visibility != DEFAULT_VISIBILITY) {
1572                             PackagePreferences.put("visibility",
1573                                     Notification.visibilityToString(r.visibility));
1574                         }
1575                         if (r.showBadge != DEFAULT_SHOW_BADGE) {
1576                             PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1577                         }
1578                         JSONArray channels = new JSONArray();
1579                         for (NotificationChannel channel : r.channels.values()) {
1580                             channels.put(channel.toJson());
1581                         }
1582                         PackagePreferences.put("channels", channels);
1583                         JSONArray groups = new JSONArray();
1584                         for (NotificationChannelGroup group : r.groups.values()) {
1585                             groups.put(group.toJson());
1586                         }
1587                         PackagePreferences.put("groups", groups);
1588                     } catch (JSONException e) {
1589                         // pass
1590                     }
1591                     PackagePreferencess.put(PackagePreferences);
1592                 }
1593             }
1594         }
1595         try {
1596             ranking.put("PackagePreferencess", PackagePreferencess);
1597         } catch (JSONException e) {
1598             // pass
1599         }
1600         return ranking;
1601     }
1602 
1603     /**
1604      * Dump only the ban information as structured JSON for the stats collector.
1605      *
1606      * This is intentionally redundant with {#link dumpJson} because the old
1607      * scraper will expect this format.
1608      *
1609      * @param filter
1610      * @return
1611      */
dumpBansJson(NotificationManagerService.DumpFilter filter)1612     public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1613         JSONArray bans = new JSONArray();
1614         Map<Integer, String> packageBans = getPackageBans();
1615         for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1616             final int userId = UserHandle.getUserId(ban.getKey());
1617             final String packageName = ban.getValue();
1618             if (filter == null || filter.matches(packageName)) {
1619                 JSONObject banJson = new JSONObject();
1620                 try {
1621                     banJson.put("userId", userId);
1622                     banJson.put("packageName", packageName);
1623                 } catch (JSONException e) {
1624                     e.printStackTrace();
1625                 }
1626                 bans.put(banJson);
1627             }
1628         }
1629         return bans;
1630     }
1631 
getPackageBans()1632     public Map<Integer, String> getPackageBans() {
1633         synchronized (mPackagePreferences) {
1634             final int N = mPackagePreferences.size();
1635             ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1636             for (int i = 0; i < N; i++) {
1637                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1638                 if (r.importance == IMPORTANCE_NONE) {
1639                     packageBans.put(r.uid, r.pkg);
1640                 }
1641             }
1642 
1643             return packageBans;
1644         }
1645     }
1646 
1647     /**
1648      * Dump only the channel information as structured JSON for the stats collector.
1649      *
1650      * This is intentionally redundant with {#link dumpJson} because the old
1651      * scraper will expect this format.
1652      *
1653      * @param filter
1654      * @return
1655      */
dumpChannelsJson(NotificationManagerService.DumpFilter filter)1656     public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1657         JSONArray channels = new JSONArray();
1658         Map<String, Integer> packageChannels = getPackageChannels();
1659         for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1660             final String packageName = channelCount.getKey();
1661             if (filter == null || filter.matches(packageName)) {
1662                 JSONObject channelCountJson = new JSONObject();
1663                 try {
1664                     channelCountJson.put("packageName", packageName);
1665                     channelCountJson.put("channelCount", channelCount.getValue());
1666                 } catch (JSONException e) {
1667                     e.printStackTrace();
1668                 }
1669                 channels.put(channelCountJson);
1670             }
1671         }
1672         return channels;
1673     }
1674 
getPackageChannels()1675     private Map<String, Integer> getPackageChannels() {
1676         ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
1677         synchronized (mPackagePreferences) {
1678             for (int i = 0; i < mPackagePreferences.size(); i++) {
1679                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1680                 int channelCount = 0;
1681                 for (int j = 0; j < r.channels.size(); j++) {
1682                     if (!r.channels.valueAt(j).isDeleted()) {
1683                         channelCount++;
1684                     }
1685                 }
1686                 packageChannels.put(r.pkg, channelCount);
1687             }
1688         }
1689         return packageChannels;
1690     }
1691 
1692     /**
1693      * Called when user switches
1694      */
onUserSwitched(int userId)1695     public void onUserSwitched(int userId) {
1696         syncChannelsBypassingDnd(userId);
1697     }
1698 
1699     /**
1700      * Called when user is unlocked
1701      */
onUserUnlocked(int userId)1702     public void onUserUnlocked(int userId) {
1703         syncChannelsBypassingDnd(userId);
1704     }
1705 
onUserRemoved(int userId)1706     public void onUserRemoved(int userId) {
1707         synchronized (mPackagePreferences) {
1708             int N = mPackagePreferences.size();
1709             for (int i = N - 1; i >= 0; i--) {
1710                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
1711                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1712                     mPackagePreferences.removeAt(i);
1713                 }
1714             }
1715         }
1716     }
1717 
onLocaleChanged(Context context, int userId)1718     protected void onLocaleChanged(Context context, int userId) {
1719         synchronized (mPackagePreferences) {
1720             int N = mPackagePreferences.size();
1721             for (int i = 0; i < N; i++) {
1722                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
1723                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1724                     if (PackagePreferences.channels.containsKey(
1725                             NotificationChannel.DEFAULT_CHANNEL_ID)) {
1726                         PackagePreferences.channels.get(
1727                                 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1728                                 context.getResources().getString(
1729                                         R.string.default_notification_channel_label));
1730                     }
1731                 }
1732             }
1733         }
1734     }
1735 
onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)1736     public boolean onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1737             int[] uidList) {
1738         if (pkgList == null || pkgList.length == 0) {
1739             return false; // nothing to do
1740         }
1741         boolean updated = false;
1742         if (removingPackage) {
1743             // Remove notification settings for uninstalled package
1744             int size = Math.min(pkgList.length, uidList.length);
1745             for (int i = 0; i < size; i++) {
1746                 final String pkg = pkgList[i];
1747                 final int uid = uidList[i];
1748                 synchronized (mPackagePreferences) {
1749                     mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
1750                 }
1751                 mRestoredWithoutUids.remove(pkg);
1752                 updated = true;
1753             }
1754         } else {
1755             for (String pkg : pkgList) {
1756                 // Package install
1757                 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1758                 if (r != null) {
1759                     try {
1760                         r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1761                         mRestoredWithoutUids.remove(pkg);
1762                         synchronized (mPackagePreferences) {
1763                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
1764                         }
1765                         updated = true;
1766                     } catch (PackageManager.NameNotFoundException e) {
1767                         // noop
1768                     }
1769                 }
1770                 // Package upgrade
1771                 try {
1772                     synchronized (mPackagePreferences) {
1773                         PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg,
1774                                 mPm.getPackageUidAsUser(pkg, changeUserId));
1775                         if (fullPackagePreferences != null) {
1776                             updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences);
1777                             updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences);
1778                         }
1779                     }
1780                 } catch (PackageManager.NameNotFoundException e) {
1781                 }
1782             }
1783         }
1784 
1785         if (updated) {
1786             updateConfig();
1787         }
1788         return updated;
1789     }
1790 
clearData(String pkg, int uid)1791     public void clearData(String pkg, int uid) {
1792         synchronized (mPackagePreferences) {
1793             PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
1794             if (p != null) {
1795                 p.channels = new ArrayMap<>();
1796                 p.groups = new ArrayMap<>();
1797                 p.delegate = null;
1798                 p.lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1799                 p.allowBubble = DEFAULT_ALLOW_BUBBLE;
1800                 p.importance = DEFAULT_IMPORTANCE;
1801                 p.priority = DEFAULT_PRIORITY;
1802                 p.visibility = DEFAULT_VISIBILITY;
1803                 p.showBadge = DEFAULT_SHOW_BADGE;
1804             }
1805         }
1806     }
1807 
getChannelLog(NotificationChannel channel, String pkg)1808     private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1809         return new LogMaker(
1810                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1811                         .ACTION_NOTIFICATION_CHANNEL)
1812                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1813                 .setPackageName(pkg)
1814                 .addTaggedData(
1815                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
1816                                 .FIELD_NOTIFICATION_CHANNEL_ID,
1817                         channel.getId())
1818                 .addTaggedData(
1819                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
1820                                 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1821                         channel.getImportance());
1822     }
1823 
getChannelGroupLog(String groupId, String pkg)1824     private LogMaker getChannelGroupLog(String groupId, String pkg) {
1825         return new LogMaker(
1826                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1827                         .ACTION_NOTIFICATION_CHANNEL_GROUP)
1828                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1829                 .addTaggedData(
1830                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
1831                                 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1832                         groupId)
1833                 .setPackageName(pkg);
1834     }
1835 
updateBubblesEnabled()1836     public void updateBubblesEnabled() {
1837         final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
1838                 Settings.Global.NOTIFICATION_BUBBLES,
1839                 DEFAULT_ALLOW_BUBBLE ? 1 : 0) == 1;
1840         if (newValue != mBubblesEnabled) {
1841             mBubblesEnabled = newValue;
1842             updateConfig();
1843         }
1844     }
1845 
bubblesEnabled()1846     public boolean bubblesEnabled() {
1847         return mBubblesEnabled;
1848     }
1849 
updateBadgingEnabled()1850     public void updateBadgingEnabled() {
1851         if (mBadgingEnabled == null) {
1852             mBadgingEnabled = new SparseBooleanArray();
1853         }
1854         boolean changed = false;
1855         // update the cached values
1856         for (int index = 0; index < mBadgingEnabled.size(); index++) {
1857             int userId = mBadgingEnabled.keyAt(index);
1858             final boolean oldValue = mBadgingEnabled.get(userId);
1859             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1860                     Settings.Secure.NOTIFICATION_BADGING,
1861                     DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1862             mBadgingEnabled.put(userId, newValue);
1863             changed |= oldValue != newValue;
1864         }
1865         if (changed) {
1866             updateConfig();
1867         }
1868     }
1869 
badgingEnabled(UserHandle userHandle)1870     public boolean badgingEnabled(UserHandle userHandle) {
1871         int userId = userHandle.getIdentifier();
1872         if (userId == UserHandle.USER_ALL) {
1873             return false;
1874         }
1875         if (mBadgingEnabled.indexOfKey(userId) < 0) {
1876             mBadgingEnabled.put(userId,
1877                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
1878                             Settings.Secure.NOTIFICATION_BADGING,
1879                             DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1880         }
1881         return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1882     }
1883 
updateConfig()1884     private void updateConfig() {
1885         mRankingHandler.requestSort();
1886     }
1887 
packagePreferencesKey(String pkg, int uid)1888     private static String packagePreferencesKey(String pkg, int uid) {
1889         return pkg + "|" + uid;
1890     }
1891 
1892     private static class PackagePreferences {
1893         String pkg;
1894         int uid = UNKNOWN_UID;
1895         int importance = DEFAULT_IMPORTANCE;
1896         int priority = DEFAULT_PRIORITY;
1897         int visibility = DEFAULT_VISIBILITY;
1898         boolean showBadge = DEFAULT_SHOW_BADGE;
1899         boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
1900         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1901         // these fields are loaded on boot from a different source of truth and so are not
1902         // written to notification policy xml
1903         boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1904         List<String> futureOemLockedChannels = new ArrayList<>();
1905         boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
1906 
1907         Delegate delegate = null;
1908         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1909         Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
1910 
isValidDelegate(String pkg, int uid)1911         public boolean isValidDelegate(String pkg, int uid) {
1912             return delegate != null && delegate.isAllowed(pkg, uid);
1913         }
1914     }
1915 
1916     private static class Delegate {
1917         static final boolean DEFAULT_ENABLED = true;
1918         static final boolean DEFAULT_USER_ALLOWED = true;
1919         String mPkg;
1920         int mUid = UNKNOWN_UID;
1921         boolean mEnabled = DEFAULT_ENABLED;
1922         boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1923 
Delegate(String pkg, int uid, boolean enabled, boolean userAllowed)1924         Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1925             mPkg = pkg;
1926             mUid = uid;
1927             mEnabled = enabled;
1928             mUserAllowed = userAllowed;
1929         }
1930 
isAllowed(String pkg, int uid)1931         public boolean isAllowed(String pkg, int uid) {
1932             if (pkg == null || uid == UNKNOWN_UID) {
1933                 return false;
1934             }
1935             return pkg.equals(mPkg)
1936                     && uid == mUid
1937                     && (mUserAllowed && mEnabled);
1938         }
1939     }
1940 }
1941