1 /**
2  * Copyright (c) 2018, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.notification;
18 
19 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
20 import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
21 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
22 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
23 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
24 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
25 import static android.app.NotificationManager.IMPORTANCE_MAX;
26 import static android.app.NotificationManager.IMPORTANCE_NONE;
27 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
28 
29 import static android.os.UserHandle.USER_SYSTEM;
30 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
31 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
32 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
33 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
34 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
35 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
36 
37 import android.annotation.IntDef;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.annotation.UserIdInt;
41 import android.app.AppOpsManager;
42 import android.app.Notification;
43 import android.app.NotificationChannel;
44 import android.app.NotificationChannelGroup;
45 import android.app.NotificationManager;
46 import android.content.AttributionSource;
47 import android.content.Context;
48 import android.content.pm.ApplicationInfo;
49 import android.content.pm.PackageInfo;
50 import android.content.pm.PackageManager;
51 import android.content.pm.ParceledListSlice;
52 import android.content.pm.UserInfo;
53 import android.metrics.LogMaker;
54 import android.net.Uri;
55 import android.os.Build;
56 import android.os.Process;
57 import android.os.UserHandle;
58 import android.permission.PermissionManager;
59 import android.provider.Settings;
60 import android.service.notification.ConversationChannelWrapper;
61 import android.service.notification.NotificationListenerService;
62 import android.service.notification.RankingHelperProto;
63 import android.service.notification.ZenModeConfig;
64 import android.text.TextUtils;
65 import android.text.format.DateUtils;
66 import android.util.ArrayMap;
67 import android.util.ArraySet;
68 import android.util.IntArray;
69 import android.util.Log;
70 import android.util.Pair;
71 import android.util.Slog;
72 import android.util.SparseBooleanArray;
73 import android.util.StatsEvent;
74 import android.util.proto.ProtoOutputStream;
75 
76 import com.android.internal.R;
77 import com.android.internal.annotations.GuardedBy;
78 import com.android.internal.annotations.VisibleForTesting;
79 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
80 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
81 import com.android.internal.logging.MetricsLogger;
82 import com.android.internal.util.FrameworkStatsLog;
83 import com.android.internal.util.Preconditions;
84 import com.android.internal.util.XmlUtils;
85 import com.android.modules.utils.TypedXmlPullParser;
86 import com.android.modules.utils.TypedXmlSerializer;
87 import com.android.server.notification.PermissionHelper.PackagePermission;
88 
89 import org.json.JSONArray;
90 import org.json.JSONException;
91 import org.json.JSONObject;
92 import org.xmlpull.v1.XmlPullParser;
93 import org.xmlpull.v1.XmlPullParserException;
94 
95 import java.io.IOException;
96 import java.io.PrintWriter;
97 import java.time.Clock;
98 import java.time.Duration;
99 import java.util.ArrayList;
100 import java.util.Arrays;
101 import java.util.Collection;
102 import java.util.List;
103 import java.util.Map;
104 import java.util.Objects;
105 import java.util.Set;
106 import java.util.concurrent.ConcurrentHashMap;
107 
108 public class PreferencesHelper implements RankingConfig {
109     private static final String TAG = "NotificationPrefHelper";
110     private final int XML_VERSION;
111     /** What version to check to do the upgrade for bubbles. */
112     private static final int XML_VERSION_BUBBLES_UPGRADE = 1;
113     /** The first xml version with notification permissions enabled. */
114     private static final int XML_VERSION_NOTIF_PERMISSION = 3;
115     /** The first xml version that notifies users to review their notification permissions */
116     private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4;
117     @VisibleForTesting
118     static final int UNKNOWN_UID = UserHandle.USER_NULL;
119     // The amount of time pacakage preferences can exist without the app being installed.
120     private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis();
121 
122     @VisibleForTesting
123     static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000;
124     @VisibleForTesting
125     static final int NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT = 6000;
126 
127     private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
128     private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
129     private static final int NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT = 1000;
130     private static final int NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS = 30;
131 
132     @VisibleForTesting
133     static final String TAG_RANKING = "ranking";
134     private static final String TAG_PACKAGE = "package";
135     private static final String TAG_CHANNEL = "channel";
136     private static final String TAG_GROUP = "channelGroup";
137     private static final String TAG_DELEGATE = "delegate";
138     private static final String TAG_STATUS_ICONS = "silent_status_icons";
139 
140     private static final String ATT_VERSION = "version";
141     private static final String ATT_NAME = "name";
142     private static final String ATT_UID = "uid";
143 
144     private static final String ATT_USERID = "userid";
145     private static final String ATT_ID = "id";
146     private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
147     private static final String ATT_PRIORITY = "priority";
148     private static final String ATT_VISIBILITY = "visibility";
149     private static final String ATT_IMPORTANCE = "importance";
150     private static final String ATT_SHOW_BADGE = "show_badge";
151     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
152     private static final String ATT_ENABLED = "enabled";
153     private static final String ATT_HIDE_SILENT = "hide_gentle";
154     private static final String ATT_SENT_INVALID_MESSAGE = "sent_invalid_msg";
155     private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
156     private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
157     private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
158 
159     private static final String ATT_CREATION_TIME = "creation_time";
160 
161     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
162     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
163     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
164     @VisibleForTesting
165     static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
166     private static final boolean DEFAULT_SHOW_BADGE = true;
167 
168     private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE  = false;
169 
170     static final boolean DEFAULT_BUBBLES_ENABLED = true;
171     @VisibleForTesting
172     static final int DEFAULT_BUBBLE_PREFERENCE = BUBBLE_PREFERENCE_NONE;
173 
174     private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP = 0;
175     private static final int NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER = 1;
176 
177     /**
178      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
179      * fields.
180      */
181     private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
182 
183     /**
184      * All user-lockable fields for a given application.
185      */
186     @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
187     public @interface LockableAppFields {
188         int USER_LOCKED_IMPORTANCE = 0x00000001;
189         int USER_LOCKED_BUBBLE = 0x00000002;
190     }
191 
192     private final Object mLock = new Object();
193     // pkg|uid => PackagePreferences
194     @GuardedBy("mLock")
195     private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
196     // pkg|userId => PackagePreferences
197     @GuardedBy("mLock")
198     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
199 
200     private final Context mContext;
201     private final PackageManager mPm;
202     private final RankingHandler mRankingHandler;
203     private final ZenModeHelper mZenModeHelper;
204     private final PermissionHelper mPermissionHelper;
205     private final PermissionManager mPermissionManager;
206     private final NotificationChannelLogger mNotificationChannelLogger;
207     private final AppOpsManager mAppOps;
208     private final ManagedServices.UserProfiles mUserProfiles;
209 
210     private SparseBooleanArray mBadgingEnabled;
211     private SparseBooleanArray mBubblesEnabled;
212     private SparseBooleanArray mLockScreenShowNotifications;
213     private SparseBooleanArray mLockScreenPrivateNotifications;
214     private boolean mIsMediaNotificationFilteringEnabled;
215     // When modes_api flag is enabled, this value only tracks whether the current user has any
216     // channels marked as "priority channels", but not necessarily whether they are permitted
217     // to bypass DND by current zen policy.
218     // TODO: b/310620812 - Rename to be more accurate when modes_api flag is inlined.
219     private boolean mCurrentUserHasChannelsBypassingDnd;
220     private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
221     private final boolean mShowReviewPermissionsNotification;
222 
223     Clock mClock;
224 
PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, boolean showReviewPermissionsNotification, Clock clock)225     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
226             ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
227             NotificationChannelLogger notificationChannelLogger,
228             AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
229             boolean showReviewPermissionsNotification, Clock clock) {
230         mContext = context;
231         mZenModeHelper = zenHelper;
232         mRankingHandler = rankingHandler;
233         mPermissionHelper = permHelper;
234         mPermissionManager = permManager;
235         mPm = pm;
236         mNotificationChannelLogger = notificationChannelLogger;
237         mAppOps = appOpsManager;
238         mUserProfiles = userProfiles;
239         mShowReviewPermissionsNotification = showReviewPermissionsNotification;
240         mIsMediaNotificationFilteringEnabled = context.getResources()
241                 .getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
242         mClock = clock;
243         XML_VERSION = 4;
244 
245         updateBadgingEnabled();
246         updateBubblesEnabled();
247         updateMediaNotificationFilteringEnabled();
248     }
249 
readXml(TypedXmlPullParser parser, boolean forRestore, int userId)250     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
251             throws XmlPullParserException, IOException {
252         int type = parser.getEventType();
253         if (type != XmlPullParser.START_TAG) return;
254         String tag = parser.getName();
255         if (!TAG_RANKING.equals(tag)) return;
256 
257         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
258         boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
259         boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION);
260         if (mShowReviewPermissionsNotification
261                 && (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION)) {
262             // make a note that we should show the notification at some point.
263             // it shouldn't be possible for the user to already have seen it, as the XML version
264             // would be newer then.
265             Settings.Global.putInt(mContext.getContentResolver(),
266                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
267                     NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW);
268         }
269         synchronized (mLock) {
270             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
271                 tag = parser.getName();
272                 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
273                     break;
274                 }
275                 if (type == XmlPullParser.START_TAG) {
276                     if (TAG_STATUS_ICONS.equals(tag)) {
277                         if (forRestore && userId != USER_SYSTEM) {
278                             continue;
279                         }
280                         mHideSilentStatusBarIcons = parser.getAttributeBoolean(null,
281                                 ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
282                     } else if (TAG_PACKAGE.equals(tag)) {
283                         String name = parser.getAttributeValue(null, ATT_NAME);
284                         if (!TextUtils.isEmpty(name)) {
285                             restorePackage(parser, forRestore, userId, name, upgradeForBubbles,
286                                     migrateToPermission);
287                         }
288                     }
289                 }
290             }
291         }
292     }
293 
294     @GuardedBy("mPackagePreferences")
restorePackage(TypedXmlPullParser parser, boolean forRestore, @UserIdInt int userId, String name, boolean upgradeForBubbles, boolean migrateToPermission)295     private void restorePackage(TypedXmlPullParser parser, boolean forRestore,
296             @UserIdInt int userId, String name, boolean upgradeForBubbles,
297             boolean migrateToPermission) {
298         try {
299             int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
300             if (forRestore) {
301                 try {
302                     uid = mPm.getPackageUidAsUser(name, userId);
303                 } catch (PackageManager.NameNotFoundException e) {
304                     // noop
305                 }
306             }
307             boolean skipWarningLogged = false;
308             boolean skipGroupWarningLogged = false;
309             boolean hasSAWPermission = false;
310             if (upgradeForBubbles && uid != UNKNOWN_UID) {
311                 hasSAWPermission = mAppOps.noteOpNoThrow(
312                         OP_SYSTEM_ALERT_WINDOW, uid, name, null,
313                         "check-notif-bubble") == AppOpsManager.MODE_ALLOWED;
314             }
315             int bubblePref = hasSAWPermission
316                     ? BUBBLE_PREFERENCE_ALL
317                     : parser.getAttributeInt(null, ATT_ALLOW_BUBBLE, DEFAULT_BUBBLE_PREFERENCE);
318             int appImportance = parser.getAttributeInt(null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
319 
320             // when data is loaded from disk it's loaded as USER_ALL, but restored data that
321             // is pending app install needs the user id that the data was restored to
322             int fixedUserId = userId;
323             if (Flags.persistIncompleteRestoreData()) {
324                 if (!forRestore && uid == UNKNOWN_UID) {
325                     fixedUserId = parser.getAttributeInt(null, ATT_USERID, USER_SYSTEM);
326                 }
327             }
328             PackagePreferences r = getOrCreatePackagePreferencesLocked(
329                     name, fixedUserId, uid,
330                     appImportance,
331                     parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY),
332                     parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY),
333                     parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
334                     bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis()));
335             r.bubblePreference = bubblePref;
336             r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY);
337             r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY);
338             r.showBadge = parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
339             r.lockedAppFields = parser.getAttributeInt(null,
340                     ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
341             r.hasSentInvalidMessage = parser.getAttributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
342                     false);
343             r.hasSentValidMessage = parser.getAttributeBoolean(null, ATT_SENT_VALID_MESSAGE, false);
344             r.userDemotedMsgApp = parser.getAttributeBoolean(
345                     null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
346             r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false);
347 
348             final int innerDepth = parser.getDepth();
349             int type;
350             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
351                     && (type != XmlPullParser.END_TAG
352                     || parser.getDepth() > innerDepth)) {
353                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
354                     continue;
355                 }
356 
357                 String tagName = parser.getName();
358                 // Channel groups
359                 if (TAG_GROUP.equals(tagName)) {
360                     if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
361                         if (!skipGroupWarningLogged) {
362                             Slog.w(TAG, "Skipping further groups for " + r.pkg);
363                             skipGroupWarningLogged = true;
364                         }
365                         continue;
366                     }
367                     String id = parser.getAttributeValue(null, ATT_ID);
368                     CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
369                     if (!TextUtils.isEmpty(id)) {
370                         NotificationChannelGroup group =
371                                 new NotificationChannelGroup(id, groupName);
372                         group.populateFromXml(parser);
373                         r.groups.put(id, group);
374                     }
375                 }
376                 // Channels
377                 if (TAG_CHANNEL.equals(tagName)) {
378                     if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
379                         if (!skipWarningLogged) {
380                             Slog.w(TAG, "Skipping further channels for " + r.pkg);
381                             skipWarningLogged = true;
382                         }
383                         continue;
384                     }
385                     restoreChannel(parser, forRestore, r);
386                 }
387 
388                 // Delegate
389                 if (TAG_DELEGATE.equals(tagName)) {
390                     int delegateId = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID);
391                     String delegateName = XmlUtils.readStringAttribute(parser, ATT_NAME);
392                     boolean delegateEnabled = parser.getAttributeBoolean(
393                             null, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
394                     Delegate d = null;
395                     if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(delegateName)) {
396                         d = new Delegate(delegateName, delegateId, delegateEnabled);
397                     }
398                     r.delegate = d;
399                 }
400             }
401 
402             try {
403                 deleteDefaultChannelIfNeededLocked(r);
404             } catch (PackageManager.NameNotFoundException e) {
405                 Slog.e(TAG, "deleteDefaultChannelIfNeededLocked - Exception: " + e);
406             }
407 
408             if (migrateToPermission) {
409                 r.importance = appImportance;
410                 r.migrateToPm = true;
411             }
412         } catch (Exception e) {
413             Slog.w(TAG, "Failed to restore pkg", e);
414         }
415     }
416 
417     @GuardedBy("mPackagePreferences")
restoreChannel(TypedXmlPullParser parser, boolean forRestore, PackagePreferences r)418     private void restoreChannel(TypedXmlPullParser parser, boolean forRestore,
419             PackagePreferences r) {
420         try {
421             String id = parser.getAttributeValue(null, ATT_ID);
422             String channelName = parser.getAttributeValue(null, ATT_NAME);
423             int channelImportance = parser.getAttributeInt(
424                     null, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
425             if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
426                 NotificationChannel channel = new NotificationChannel(
427                         id, channelName, channelImportance);
428                 if (forRestore) {
429                     final boolean pkgInstalled = r.uid != UNKNOWN_UID;
430                     channel.populateFromXmlForRestore(parser, pkgInstalled, mContext);
431                 } else {
432                     channel.populateFromXml(parser);
433                 }
434                 channel.setImportanceLockedByCriticalDeviceFunction(
435                         r.defaultAppLockedImportance || r.fixedImportance);
436 
437                 if (isShortcutOk(channel) && isDeletionOk(channel)) {
438                     r.channels.put(id, channel);
439                 }
440             }
441         } catch (Exception e) {
442             Slog.w(TAG, "could not restore channel for " + r.pkg, e);
443         }
444     }
445 
446     @GuardedBy("mPackagePreferences")
hasUserConfiguredSettings(PackagePreferences p)447     private boolean hasUserConfiguredSettings(PackagePreferences p){
448         boolean hasChangedChannel = false;
449         for (NotificationChannel channel : p.channels.values()) {
450             if (channel.getUserLockedFields() != 0) {
451                 hasChangedChannel = true;
452                 break;
453             }
454         }
455         return hasChangedChannel || p.importance == IMPORTANCE_NONE;
456     }
457 
isShortcutOk(NotificationChannel channel)458     private boolean isShortcutOk(NotificationChannel channel) {
459         boolean isInvalidShortcutChannel =
460                 channel.getConversationId() != null &&
461                         channel.getConversationId().contains(
462                                 PLACEHOLDER_CONVERSATION_ID);
463         return !isInvalidShortcutChannel;
464     }
465 
isDeletionOk(NotificationChannel nc)466     private boolean isDeletionOk(NotificationChannel nc) {
467         if (!nc.isDeleted()) {
468             return true;
469         }
470         long boundary = System.currentTimeMillis() - (
471                 DateUtils.DAY_IN_MILLIS * NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS);
472         if (nc.getDeletedTimeMs() <= boundary) {
473             return false;
474         }
475         return true;
476     }
477 
getPackagePreferencesLocked(String pkg, int uid)478     private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) {
479         final String key = packagePreferencesKey(pkg, uid);
480         return mPackagePreferences.get(key);
481     }
482 
getOrCreatePackagePreferencesLocked(String pkg, int uid)483     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
484             int uid) {
485         // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE
486         return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid,
487                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
488                 DEFAULT_BUBBLE_PREFERENCE, mClock.millis());
489     }
490 
491     @GuardedBy("mLock")
getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference, long creationTime)492     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
493             @UserIdInt int userId, int uid, int importance, int priority, int visibility,
494             boolean showBadge, int bubblePreference, long creationTime) {
495         final String key = packagePreferencesKey(pkg, uid);
496         PackagePreferences
497                 r = (uid == UNKNOWN_UID)
498                 ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
499                 : mPackagePreferences.get(key);
500         if (r == null) {
501             r = new PackagePreferences();
502             r.pkg = pkg;
503             r.uid = uid;
504             r.importance = importance;
505             r.priority = priority;
506             r.visibility = visibility;
507             r.showBadge = showBadge;
508             r.bubblePreference = bubblePreference;
509             if (Flags.persistIncompleteRestoreData()) {
510                 if (r.uid == UNKNOWN_UID) {
511                     r.creationTime = creationTime;
512                 }
513             }
514 
515             try {
516                 createDefaultChannelIfNeededLocked(r);
517             } catch (PackageManager.NameNotFoundException e) {
518                 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
519             }
520 
521             if (r.uid == UNKNOWN_UID) {
522                 if (Flags.persistIncompleteRestoreData()) {
523                     r.userId = userId;
524                 }
525                 mRestoredWithoutUids.put(unrestoredPackageKey(pkg, userId), r);
526             } else {
527                 mPackagePreferences.put(key, r);
528             }
529         }
530         if (r.uid == UNKNOWN_UID) {
531             if (Flags.persistIncompleteRestoreData()
532                     && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) {
533                 mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
534             }
535         }
536         return r;
537     }
538 
shouldHaveDefaultChannel(PackagePreferences r)539     private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
540             PackageManager.NameNotFoundException {
541         final int userId = UserHandle.getUserId(r.uid);
542         final ApplicationInfo applicationInfo =
543                 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
544         if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
545             // O apps should not have the default channel.
546             return false;
547         }
548 
549         // Otherwise, this app should have the default channel.
550         return true;
551     }
552 
deleteDefaultChannelIfNeededLocked(PackagePreferences r)553     private boolean deleteDefaultChannelIfNeededLocked(PackagePreferences r) throws
554             PackageManager.NameNotFoundException {
555         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
556             // Not present
557             return false;
558         }
559 
560         if (shouldHaveDefaultChannel(r)) {
561             // Keep the default channel until upgraded.
562             return false;
563         }
564 
565         // Remove Default Channel.
566         r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
567 
568         return true;
569     }
570 
createDefaultChannelIfNeededLocked(PackagePreferences r)571     private boolean createDefaultChannelIfNeededLocked(PackagePreferences r) throws
572             PackageManager.NameNotFoundException {
573         if (r.uid == UNKNOWN_UID) {
574             return false;
575         }
576 
577         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
578             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
579                     com.android.internal.R.string.default_notification_channel_label));
580             return false;
581         }
582 
583         if (!shouldHaveDefaultChannel(r)) {
584             // Keep the default channel until upgraded.
585             return false;
586         }
587 
588         // Create Default Channel
589         NotificationChannel channel;
590         channel = new NotificationChannel(
591                 NotificationChannel.DEFAULT_CHANNEL_ID,
592                 mContext.getString(R.string.default_notification_channel_label),
593                 r.importance);
594         channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
595         channel.setLockscreenVisibility(r.visibility);
596         if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
597             channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
598         }
599         if (r.priority != DEFAULT_PRIORITY) {
600             channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
601         }
602         if (r.visibility != DEFAULT_VISIBILITY) {
603             channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
604         }
605         r.channels.put(channel.getId(), channel);
606 
607         return true;
608     }
609 
writeXml(TypedXmlSerializer out, boolean forBackup, int userId)610     public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
611         out.startTag(null, TAG_RANKING);
612         out.attributeInt(null, ATT_VERSION, XML_VERSION);
613         if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
614             out.startTag(null, TAG_STATUS_ICONS);
615             out.attributeBoolean(null, ATT_HIDE_SILENT, mHideSilentStatusBarIcons);
616             out.endTag(null, TAG_STATUS_ICONS);
617         }
618         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions = new ArrayMap<>();
619         if (forBackup) {
620             notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId);
621         }
622 
623         synchronized (mLock) {
624             final int N = mPackagePreferences.size();
625             for (int i = 0; i < N; i++) {
626                 final PackagePreferences r = mPackagePreferences.valueAt(i);
627                 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
628                     continue;
629                 }
630                 writePackageXml(r, out, notifPermissions, forBackup);
631             }
632 
633             if (Flags.persistIncompleteRestoreData() && !forBackup) {
634                 final int M = mRestoredWithoutUids.size();
635                 for (int i = 0; i < M; i++) {
636                     final PackagePreferences r = mRestoredWithoutUids.valueAt(i);
637                     writePackageXml(r, out, notifPermissions, false);
638                 }
639             }
640         }
641         // Some apps have permissions set but don't have expanded notification settings
642         if (!notifPermissions.isEmpty()) {
643             for (Pair<Integer, String> app : notifPermissions.keySet()) {
644                 out.startTag(null, TAG_PACKAGE);
645                 out.attribute(null, ATT_NAME, app.second);
646                 out.attributeInt(null, ATT_IMPORTANCE,
647                         notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
648                 out.endTag(null, TAG_PACKAGE);
649             }
650         }
651         out.endTag(null, TAG_RANKING);
652     }
653 
writePackageXml(PackagePreferences r, TypedXmlSerializer out, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions, boolean forBackup)654     public void writePackageXml(PackagePreferences r, TypedXmlSerializer out,
655             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions,
656             boolean forBackup) throws
657             IOException {
658         out.startTag(null, TAG_PACKAGE);
659         out.attribute(null, ATT_NAME, r.pkg);
660         if (!notifPermissions.isEmpty()) {
661             Pair<Integer, String> app = new Pair(r.uid, r.pkg);
662             final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
663             out.attributeInt(null, ATT_IMPORTANCE,
664                     permission != null && permission.first ? IMPORTANCE_DEFAULT
665                             : IMPORTANCE_NONE);
666             notifPermissions.remove(app);
667         } else {
668             if (r.importance != DEFAULT_IMPORTANCE) {
669                 out.attributeInt(null, ATT_IMPORTANCE, r.importance);
670             }
671         }
672         if (r.priority != DEFAULT_PRIORITY) {
673             out.attributeInt(null, ATT_PRIORITY, r.priority);
674         }
675         if (r.visibility != DEFAULT_VISIBILITY) {
676             out.attributeInt(null, ATT_VISIBILITY, r.visibility);
677         }
678         if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
679             out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
680         }
681         out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
682         out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
683                 r.lockedAppFields);
684         out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
685                 r.hasSentInvalidMessage);
686         out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
687                 r.hasSentValidMessage);
688         out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
689                 r.userDemotedMsgApp);
690         out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
691 
692         if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
693             out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
694             out.attributeInt(null, ATT_USERID, r.userId);
695         }
696 
697         if (!forBackup) {
698             out.attributeInt(null, ATT_UID, r.uid);
699         }
700 
701         if (r.delegate != null) {
702             out.startTag(null, TAG_DELEGATE);
703 
704             out.attribute(null, ATT_NAME, r.delegate.mPkg);
705             out.attributeInt(null, ATT_UID, r.delegate.mUid);
706             if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
707                 out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
708             }
709             out.endTag(null, TAG_DELEGATE);
710         }
711 
712         for (NotificationChannelGroup group : r.groups.values()) {
713             group.writeXml(out);
714         }
715 
716         for (NotificationChannel channel : r.channels.values()) {
717             if (forBackup) {
718                 if (!channel.isDeleted()) {
719                     channel.writeXmlForBackup(out, mContext);
720                 }
721             } else {
722                 channel.writeXml(out);
723             }
724         }
725 
726         out.endTag(null, TAG_PACKAGE);
727     }
728 
729     /**
730      * Sets whether bubbles are allowed.
731      *
732      * @param pkg the package to allow or not allow bubbles for.
733      * @param uid the uid to allow or not allow bubbles for.
734      * @param bubblePreference whether bubbles are allowed.
735      */
setBubblesAllowed(String pkg, int uid, int bubblePreference)736     public void setBubblesAllowed(String pkg, int uid, int bubblePreference) {
737         boolean changed;
738         synchronized (mLock) {
739             PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid);
740             changed = p.bubblePreference != bubblePreference;
741             p.bubblePreference = bubblePreference;
742             p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
743         }
744         if (changed) {
745             updateConfig();
746         }
747     }
748 
749     /**
750      * Whether bubbles are allowed.
751      *
752      * @param pkg the package to check if bubbles are allowed for
753      * @param uid the uid to check if bubbles are allowed for.
754      * @return whether bubbles are allowed.
755      */
756     @Override
getBubblePreference(String pkg, int uid)757     public int getBubblePreference(String pkg, int uid) {
758         synchronized (mLock) {
759             return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference;
760         }
761     }
762 
getAppLockedFields(String pkg, int uid)763     public int getAppLockedFields(String pkg, int uid) {
764         synchronized (mLock) {
765             return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields;
766         }
767     }
768 
769     @Override
canShowBadge(String packageName, int uid)770     public boolean canShowBadge(String packageName, int uid) {
771         synchronized (mLock) {
772             return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge;
773         }
774     }
775 
776     @Override
setShowBadge(String packageName, int uid, boolean showBadge)777     public void setShowBadge(String packageName, int uid, boolean showBadge) {
778         boolean changed = false;
779         synchronized (mLock) {
780             PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
781             if (pkgPrefs.showBadge != showBadge) {
782                 pkgPrefs.showBadge = showBadge;
783                 changed = true;
784             }
785         }
786         if (changed) {
787             updateConfig();
788         }
789     }
790 
isInInvalidMsgState(String packageName, int uid)791     public boolean isInInvalidMsgState(String packageName, int uid) {
792         synchronized (mLock) {
793             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
794             return r.hasSentInvalidMessage && !r.hasSentValidMessage;
795         }
796     }
797 
hasUserDemotedInvalidMsgApp(String packageName, int uid)798     public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) {
799         synchronized (mLock) {
800             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
801             return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false;
802         }
803     }
804 
setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted)805     public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) {
806         synchronized (mLock) {
807             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
808             r.userDemotedMsgApp = isDemoted;
809         }
810     }
811 
setInvalidMessageSent(String packageName, int uid)812     public boolean setInvalidMessageSent(String packageName, int uid) {
813         synchronized (mLock) {
814             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
815             boolean valueChanged = r.hasSentInvalidMessage == false;
816             r.hasSentInvalidMessage = true;
817 
818             return valueChanged;
819         }
820     }
821 
setValidMessageSent(String packageName, int uid)822     public boolean setValidMessageSent(String packageName, int uid) {
823         synchronized (mLock) {
824             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
825             boolean valueChanged = r.hasSentValidMessage == false;
826             r.hasSentValidMessage = true;
827 
828             return valueChanged;
829         }
830     }
831 
832     @VisibleForTesting
hasSentInvalidMsg(String packageName, int uid)833     boolean hasSentInvalidMsg(String packageName, int uid) {
834         synchronized (mLock) {
835             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
836             return r.hasSentInvalidMessage;
837         }
838     }
839 
840     @VisibleForTesting
hasSentValidMsg(String packageName, int uid)841     boolean hasSentValidMsg(String packageName, int uid) {
842         synchronized (mLock) {
843             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
844             return r.hasSentValidMessage;
845         }
846     }
847 
848     @VisibleForTesting
didUserEverDemoteInvalidMsgApp(String packageName, int uid)849     boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) {
850         synchronized (mLock) {
851             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
852             return r.userDemotedMsgApp;
853         }
854     }
855 
856     /** Sets whether this package has sent a notification with valid bubble metadata. */
setValidBubbleSent(String packageName, int uid)857     public boolean setValidBubbleSent(String packageName, int uid) {
858         synchronized (mLock) {
859             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
860             boolean valueChanged = !r.hasSentValidBubble;
861             r.hasSentValidBubble = true;
862             return valueChanged;
863         }
864     }
865 
hasSentValidBubble(String packageName, int uid)866     boolean hasSentValidBubble(String packageName, int uid) {
867         synchronized (mLock) {
868             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
869             return r.hasSentValidBubble;
870         }
871     }
872 
isImportanceLocked(String pkg, int uid)873     boolean isImportanceLocked(String pkg, int uid) {
874         synchronized (mLock) {
875             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
876             return r.fixedImportance || r.defaultAppLockedImportance;
877         }
878     }
879 
880     @Override
isGroupBlocked(String packageName, int uid, String groupId)881     public boolean isGroupBlocked(String packageName, int uid, String groupId) {
882         if (groupId == null) {
883             return false;
884         }
885         synchronized (mLock) {
886             PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
887             NotificationChannelGroup group = r.groups.get(groupId);
888             if (group == null) {
889                 return false;
890             }
891             return group.isBlocked();
892         }
893     }
894 
getPackagePriority(String pkg, int uid)895     int getPackagePriority(String pkg, int uid) {
896         synchronized (mLock) {
897             return getOrCreatePackagePreferencesLocked(pkg, uid).priority;
898         }
899     }
900 
getPackageVisibility(String pkg, int uid)901     int getPackageVisibility(String pkg, int uid) {
902         synchronized (mLock) {
903             return getOrCreatePackagePreferencesLocked(pkg, uid).visibility;
904         }
905     }
906 
907     @Override
createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp, int callingUid, boolean fromSystemOrSystemUi)908     public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
909             boolean fromTargetApp, int callingUid, boolean fromSystemOrSystemUi) {
910         Objects.requireNonNull(pkg);
911         Objects.requireNonNull(group);
912         Objects.requireNonNull(group.getId());
913         if (TextUtils.isEmpty(group.getName())) {
914             throw new IllegalArgumentException("group.getName() can't be empty");
915         }
916         boolean needsDndChange = false;
917         synchronized (mLock) {
918             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
919             if (r == null) {
920                 throw new IllegalArgumentException("Invalid package");
921             }
922             if (r.groups.size() >= NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT) {
923                 throw new IllegalStateException("Limit exceed; cannot create more groups");
924             }
925             if (fromTargetApp) {
926                 group.setBlocked(false);
927             }
928             final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
929             if (oldGroup != null) {
930                 group.setChannels(oldGroup.getChannels());
931 
932                 // apps can't update the blocked status or app overlay permission
933                 if (fromTargetApp) {
934                     group.setBlocked(oldGroup.isBlocked());
935                     group.unlockFields(group.getUserLockedFields());
936                     group.lockFields(oldGroup.getUserLockedFields());
937                 } else {
938                     // but the system can
939                     if (group.isBlocked() != oldGroup.isBlocked()) {
940                         group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
941                         needsDndChange = true;
942                     }
943                 }
944             }
945             if (!group.equals(oldGroup)) {
946                 // will log for new entries as well as name/description changes
947                 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
948                 mNotificationChannelLogger.logNotificationChannelGroup(group, uid, pkg,
949                         oldGroup == null,
950                         (oldGroup != null) && oldGroup.isBlocked());
951             }
952             r.groups.put(group.getId(), group);
953         }
954         if (needsDndChange) {
955             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
956         }
957     }
958 
959     @Override
createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess, int callingUid, boolean fromSystemOrSystemUi)960     public boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
961             boolean fromTargetApp, boolean hasDndAccess, int callingUid,
962             boolean fromSystemOrSystemUi) {
963         Objects.requireNonNull(pkg);
964         Objects.requireNonNull(channel);
965         Objects.requireNonNull(channel.getId());
966         Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
967         Preconditions.checkArgument(channel.getImportance() >= IMPORTANCE_NONE
968                 && channel.getImportance() <= IMPORTANCE_MAX, "Invalid importance level");
969 
970         boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false;
971         synchronized (mLock) {
972             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
973             if (r == null) {
974                 throw new IllegalArgumentException("Invalid package");
975             }
976             if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
977                 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
978             }
979             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
980                 throw new IllegalArgumentException("Reserved id");
981             }
982             NotificationChannel existing = r.channels.get(channel.getId());
983             if (existing != null && fromTargetApp) {
984                 // Actually modifying an existing channel - keep most of the existing settings
985                 if (existing.isDeleted()) {
986                     // The existing channel was deleted - undelete it.
987                     existing.setDeleted(false);
988                     existing.setDeletedTimeMs(-1);
989                     needsPolicyFileChange = true;
990                     wasUndeleted = true;
991 
992                     // log a resurrected channel as if it's new again
993                     MetricsLogger.action(getChannelLog(channel, pkg).setType(
994                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
995                     mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
996                 }
997 
998                 if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) {
999                     existing.setName(channel.getName().toString());
1000                     needsPolicyFileChange = true;
1001                 }
1002                 if (!Objects.equals(channel.getDescription(), existing.getDescription())) {
1003                     existing.setDescription(channel.getDescription());
1004                     needsPolicyFileChange = true;
1005                 }
1006                 if (channel.isBlockable() != existing.isBlockable()) {
1007                     existing.setBlockable(channel.isBlockable());
1008                     needsPolicyFileChange = true;
1009                 }
1010                 if (channel.getGroup() != null && existing.getGroup() == null) {
1011                     existing.setGroup(channel.getGroup());
1012                     needsPolicyFileChange = true;
1013                 }
1014 
1015                 // Apps are allowed to downgrade channel importance if the user has not changed any
1016                 // fields on this channel yet.
1017                 final int previousExistingImportance = existing.getImportance();
1018                 final int previousLoggingImportance =
1019                         NotificationChannelLogger.getLoggingImportance(existing);
1020                 if (existing.getUserLockedFields() == 0 &&
1021                         channel.getImportance() < existing.getImportance()) {
1022                     existing.setImportance(channel.getImportance());
1023                     needsPolicyFileChange = true;
1024                 }
1025 
1026                 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
1027                 // fields on the channel yet
1028                 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
1029                     boolean bypassDnd = channel.canBypassDnd();
1030                     if (bypassDnd != existing.canBypassDnd() || wasUndeleted) {
1031                         existing.setBypassDnd(bypassDnd);
1032                         needsPolicyFileChange = true;
1033 
1034                         if (bypassDnd != mCurrentUserHasChannelsBypassingDnd
1035                                 || previousExistingImportance != existing.getImportance()) {
1036                             needsDndChange = true;
1037                         }
1038                     }
1039                 }
1040 
1041                 if (existing.getOriginalImportance() == IMPORTANCE_UNSPECIFIED) {
1042                     existing.setOriginalImportance(channel.getImportance());
1043                     needsPolicyFileChange = true;
1044                 }
1045 
1046                 if (needsPolicyFileChange) {
1047                     updateConfig();
1048                 }
1049                 if (needsPolicyFileChange && !wasUndeleted) {
1050                     mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg,
1051                             previousLoggingImportance, false);
1052                 }
1053             } else {
1054                 if (r.channels.size() >= NOTIFICATION_CHANNEL_COUNT_LIMIT) {
1055                     throw new IllegalStateException("Limit exceed; cannot create more channels");
1056                 }
1057 
1058                 needsPolicyFileChange = true;
1059 
1060                 // Reset fields that apps aren't allowed to set.
1061                 if (fromTargetApp && !hasDndAccess) {
1062                     channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
1063                 }
1064                 if (fromTargetApp) {
1065                     channel.setLockscreenVisibility(r.visibility);
1066                     channel.setAllowBubbles(existing != null
1067                             ? existing.getAllowBubbles()
1068                             : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
1069                     channel.setImportantConversation(false);
1070                 }
1071                 clearLockedFieldsLocked(channel);
1072 
1073                 channel.setImportanceLockedByCriticalDeviceFunction(
1074                         r.defaultAppLockedImportance || r.fixedImportance);
1075 
1076                 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
1077                     channel.setLockscreenVisibility(
1078                             NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
1079                 }
1080                 if (!r.showBadge) {
1081                     channel.setShowBadge(false);
1082                 }
1083                 channel.setOriginalImportance(channel.getImportance());
1084 
1085                 // validate parent
1086                 if (channel.getParentChannelId() != null) {
1087                     Preconditions.checkArgument(
1088                             r.channels.containsKey(channel.getParentChannelId()),
1089                             "Tried to create a conversation channel without a preexisting parent");
1090                 }
1091 
1092                 r.channels.put(channel.getId(), channel);
1093                 if (channel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd) {
1094                     needsDndChange = true;
1095                 }
1096                 MetricsLogger.action(getChannelLog(channel, pkg).setType(
1097                         com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
1098                 mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg);
1099             }
1100         }
1101 
1102         if (needsDndChange) {
1103             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
1104         }
1105 
1106         return needsPolicyFileChange;
1107     }
1108 
clearLockedFieldsLocked(NotificationChannel channel)1109     void clearLockedFieldsLocked(NotificationChannel channel) {
1110         channel.unlockFields(channel.getUserLockedFields());
1111     }
1112 
unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId)1113     void unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId) {
1114         Objects.requireNonNull(updatedChannelId);
1115         synchronized (mLock) {
1116             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1117             if (r == null) {
1118                 throw new IllegalArgumentException("Invalid package");
1119             }
1120 
1121             NotificationChannel channel = r.channels.get(updatedChannelId);
1122             if (channel == null || channel.isDeleted()) {
1123                 throw new IllegalArgumentException("Channel does not exist");
1124             }
1125             channel.unlockFields(USER_LOCKED_IMPORTANCE);
1126         }
1127     }
1128 
1129 
1130     @Override
updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, boolean fromUser, int callingUid, boolean fromSystemOrSystemUi)1131     public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
1132             boolean fromUser, int callingUid, boolean fromSystemOrSystemUi) {
1133         Objects.requireNonNull(updatedChannel);
1134         Objects.requireNonNull(updatedChannel.getId());
1135         boolean changed = false;
1136         boolean needsDndChange = false;
1137         synchronized (mLock) {
1138             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1139             if (r == null) {
1140                 throw new IllegalArgumentException("Invalid package");
1141             }
1142             NotificationChannel channel = r.channels.get(updatedChannel.getId());
1143             if (channel == null || channel.isDeleted()) {
1144                 throw new IllegalArgumentException("Channel does not exist");
1145             }
1146             if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
1147                 updatedChannel.setLockscreenVisibility(
1148                         NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
1149             }
1150             if (fromUser) {
1151                 updatedChannel.lockFields(channel.getUserLockedFields());
1152                 lockFieldsForUpdateLocked(channel, updatedChannel);
1153             } else {
1154                 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
1155             }
1156 
1157             if (channel.isImportanceLockedByCriticalDeviceFunction()
1158                     && !(channel.isBlockable() || channel.getImportance() == IMPORTANCE_NONE)) {
1159                 updatedChannel.setImportance(channel.getImportance());
1160             }
1161 
1162             r.channels.put(updatedChannel.getId(), updatedChannel);
1163 
1164             if (onlyHasDefaultChannel(pkg, uid)) {
1165                 r.priority = updatedChannel.canBypassDnd()
1166                         ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
1167                 r.visibility = updatedChannel.getLockscreenVisibility();
1168                 r.showBadge = updatedChannel.canShowBadge();
1169                 changed = true;
1170             }
1171 
1172             if (!channel.equals(updatedChannel)) {
1173                 // only log if there are real changes
1174                 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
1175                         .setSubtype(fromUser ? NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER
1176                                 : NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_APP));
1177                 mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg,
1178                         NotificationChannelLogger.getLoggingImportance(channel), fromUser);
1179                 changed = true;
1180             }
1181 
1182             if (fromUser && SystemUiSystemPropertiesFlags.getResolver().isEnabled(
1183                     NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS)) {
1184                 updateChildrenConversationChannels(r, channel, updatedChannel);
1185                 // No need to update changed or needsDndChanged as the child channel(s) cannot be
1186                 // relevantly affected without the parent channel already having been.
1187             }
1188 
1189             if (updatedChannel.canBypassDnd() != mCurrentUserHasChannelsBypassingDnd
1190                     || channel.getImportance() != updatedChannel.getImportance()) {
1191                 needsDndChange = true;
1192                 changed = true;
1193             }
1194         }
1195         if (needsDndChange) {
1196             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
1197         }
1198         if (changed) {
1199             updateConfig();
1200         }
1201     }
1202 
1203     /**
1204      * Updates conversation channels after user changes to their parent channel. See
1205      * {@link #maybeUpdateChildConversationChannel}.
1206      */
1207     @GuardedBy("mPackagePreferences")
updateChildrenConversationChannels(@onNull PackagePreferences packagePreferences, @NonNull NotificationChannel oldParent, @NonNull NotificationChannel updatedParent)1208     private void updateChildrenConversationChannels(@NonNull PackagePreferences packagePreferences,
1209             @NonNull NotificationChannel oldParent, @NonNull NotificationChannel updatedParent) {
1210         if (oldParent.equals(updatedParent)) {
1211             return;
1212         }
1213         if (oldParent.isConversation()) {
1214             return; // Can't have children.
1215         }
1216         for (NotificationChannel channel : packagePreferences.channels.values()) {
1217             // Include deleted -- otherwise they will have old settings if later resurrected.
1218             // Include demoted -- still attached to their parents.
1219             if (channel.isConversation()
1220                     && oldParent.getId().equals(channel.getParentChannelId())) {
1221                 maybeUpdateChildConversationChannel(packagePreferences.pkg, packagePreferences.uid,
1222                         channel, oldParent, updatedParent);
1223             }
1224         }
1225     }
1226 
1227     /**
1228      * Apply the diff between {@code oldParent} and {@code updatedParent} to the child
1229      * {@code conversation} channel. Only fields that are not locked on the conversation channel
1230      * (see {@link NotificationChannel#LOCKABLE_FIELDS }) will be updated (so that we don't override
1231      * previous explicit user choices).
1232      *
1233      * <p>This will also log the change as if it was {@code fromUser=true}.
1234      */
1235     @GuardedBy("mPackagePreferences")
maybeUpdateChildConversationChannel(String pkg, int uid, @NonNull NotificationChannel conversation, @NonNull NotificationChannel oldParent, @NonNull NotificationChannel updatedParent)1236     private void maybeUpdateChildConversationChannel(String pkg, int uid,
1237             @NonNull NotificationChannel conversation, @NonNull NotificationChannel oldParent,
1238             @NonNull NotificationChannel updatedParent) {
1239         boolean changed = false;
1240         int oldLoggingImportance = NotificationChannelLogger.getLoggingImportance(conversation);
1241 
1242         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0
1243                 && oldParent.canBypassDnd() != updatedParent.canBypassDnd()) {
1244             conversation.setBypassDnd(updatedParent.canBypassDnd());
1245             changed = true;
1246         }
1247         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0
1248                 && oldParent.getLockscreenVisibility()
1249                 != updatedParent.getLockscreenVisibility()) {
1250             conversation.setLockscreenVisibility(updatedParent.getLockscreenVisibility());
1251             changed = true;
1252         }
1253         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
1254                 && oldParent.getImportance() != updatedParent.getImportance()) {
1255             conversation.setImportance(updatedParent.getImportance());
1256             changed = true;
1257         }
1258         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0
1259                 && (oldParent.shouldShowLights() != updatedParent.shouldShowLights()
1260                 || oldParent.getLightColor() != updatedParent.getLightColor())) {
1261             conversation.enableLights(updatedParent.shouldShowLights());
1262             conversation.setLightColor(updatedParent.getLightColor());
1263             changed = true;
1264         }
1265         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0
1266                 && !Objects.equals(oldParent.getSound(), updatedParent.getSound())) {
1267             conversation.setSound(updatedParent.getSound(), updatedParent.getAudioAttributes());
1268             changed = true;
1269         }
1270         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0
1271                 && (!Arrays.equals(oldParent.getVibrationPattern(),
1272                 updatedParent.getVibrationPattern())
1273                 || !Objects.equals(
1274                         oldParent.getVibrationEffect(), updatedParent.getVibrationEffect())
1275                 || oldParent.shouldVibrate() != updatedParent.shouldVibrate())) {
1276             // enableVibration must be 2nd because setVibrationPattern may toggle it.
1277             conversation.setVibrationPattern(updatedParent.getVibrationPattern());
1278             conversation.enableVibration(updatedParent.shouldVibrate());
1279             changed = true;
1280         }
1281         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_SHOW_BADGE) == 0
1282                 && oldParent.canShowBadge() != updatedParent.canShowBadge()) {
1283             conversation.setShowBadge(updatedParent.canShowBadge());
1284             changed = true;
1285         }
1286         if ((conversation.getUserLockedFields() & NotificationChannel.USER_LOCKED_ALLOW_BUBBLE) == 0
1287                 && oldParent.getAllowBubbles() != updatedParent.getAllowBubbles()) {
1288             conversation.setAllowBubbles(updatedParent.getAllowBubbles());
1289             changed = true;
1290         }
1291 
1292         if (changed) {
1293             MetricsLogger.action(getChannelLog(conversation, pkg).setSubtype(
1294                     NOTIFICATION_UPDATE_LOG_SUBTYPE_FROM_USER));
1295             mNotificationChannelLogger.logNotificationChannelModified(conversation, uid, pkg,
1296                     oldLoggingImportance, /* fromUser= */ true);
1297         }
1298     }
1299 
1300     @Override
getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)1301     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
1302             boolean includeDeleted) {
1303         Objects.requireNonNull(pkg);
1304         return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
1305     }
1306 
1307     @Override
getConversationNotificationChannel(String pkg, int uid, String channelId, String conversationId, boolean returnParentIfNoConversationChannel, boolean includeDeleted)1308     public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
1309             String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
1310             boolean includeDeleted) {
1311         Preconditions.checkNotNull(pkg);
1312         synchronized (mLock) {
1313             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1314             if (r == null) {
1315                 return null;
1316             }
1317             if (channelId == null) {
1318                 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
1319             }
1320             NotificationChannel channel = null;
1321             if (conversationId != null) {
1322                 // look for an automatically created conversation specific channel
1323                 channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
1324             }
1325             if (channel == null && returnParentIfNoConversationChannel) {
1326                 // look for it just based on its id
1327                 final NotificationChannel nc = r.channels.get(channelId);
1328                 if (nc != null && (includeDeleted || !nc.isDeleted())) {
1329                     return nc;
1330                 }
1331             }
1332             return channel;
1333         }
1334     }
1335 
findConversationChannel(PackagePreferences p, String parentId, String conversationId, boolean includeDeleted)1336     private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
1337             String conversationId, boolean includeDeleted) {
1338         for (NotificationChannel nc : p.channels.values()) {
1339             if (conversationId.equals(nc.getConversationId())
1340                     && parentId.equals(nc.getParentChannelId())
1341                     && (includeDeleted || !nc.isDeleted())) {
1342                 return nc;
1343             }
1344         }
1345         return null;
1346     }
1347 
getNotificationChannelsByConversationId(String pkg, int uid, String conversationId)1348     public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
1349             String conversationId) {
1350         Preconditions.checkNotNull(pkg);
1351         Preconditions.checkNotNull(conversationId);
1352         List<NotificationChannel> channels = new ArrayList<>();
1353         synchronized (mLock) {
1354             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1355             if (r == null) {
1356                 return channels;
1357             }
1358             for (NotificationChannel nc : r.channels.values()) {
1359                 if (conversationId.equals(nc.getConversationId())
1360                         && !nc.isDeleted()) {
1361                     channels.add(nc);
1362                 }
1363             }
1364             return channels;
1365         }
1366     }
1367 
1368     @Override
deleteNotificationChannel(String pkg, int uid, String channelId, int callingUid, boolean fromSystemOrSystemUi)1369     public boolean deleteNotificationChannel(String pkg, int uid, String channelId,
1370             int callingUid, boolean fromSystemOrSystemUi) {
1371         boolean deletedChannel = false;
1372         boolean channelBypassedDnd = false;
1373         synchronized (mLock) {
1374             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1375             if (r == null) {
1376                 return false;
1377             }
1378             NotificationChannel channel = r.channels.get(channelId);
1379             if (channel != null) {
1380                 channelBypassedDnd = channel.canBypassDnd();
1381                 deletedChannel = deleteNotificationChannelLocked(channel, pkg, uid);
1382             }
1383         }
1384         if (channelBypassedDnd) {
1385             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
1386         }
1387         return deletedChannel;
1388     }
1389 
deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid)1390     private boolean deleteNotificationChannelLocked(NotificationChannel channel, String pkg,
1391             int uid) {
1392         if (!channel.isDeleted()) {
1393             channel.setDeleted(true);
1394             channel.setDeletedTimeMs(System.currentTimeMillis());
1395             LogMaker lm = getChannelLog(channel, pkg);
1396             lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
1397             MetricsLogger.action(lm);
1398             mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg);
1399             return true;
1400         }
1401         return false;
1402     }
1403 
1404     @Override
1405     @VisibleForTesting
permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)1406     public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
1407         Objects.requireNonNull(pkg);
1408         Objects.requireNonNull(channelId);
1409         synchronized (mLock) {
1410             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1411             if (r == null) {
1412                 return;
1413             }
1414             r.channels.remove(channelId);
1415         }
1416     }
1417 
1418     @Override
permanentlyDeleteNotificationChannels(String pkg, int uid)1419     public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
1420         Objects.requireNonNull(pkg);
1421         synchronized (mLock) {
1422             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1423             if (r == null) {
1424                 return;
1425             }
1426             int N = r.channels.size() - 1;
1427             for (int i = N; i >= 0; i--) {
1428                 String key = r.channels.keyAt(i);
1429                 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
1430                     r.channels.remove(key);
1431                 }
1432             }
1433         }
1434     }
1435 
shouldHideSilentStatusIcons()1436     public boolean shouldHideSilentStatusIcons() {
1437         return mHideSilentStatusBarIcons;
1438     }
1439 
setHideSilentStatusIcons(boolean hide)1440     public void setHideSilentStatusIcons(boolean hide) {
1441         mHideSilentStatusBarIcons = hide;
1442     }
1443 
updateFixedImportance(List<UserInfo> users)1444     public void updateFixedImportance(List<UserInfo> users) {
1445         for (UserInfo user : users) {
1446             List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
1447                     0, user.getUserHandle().getIdentifier());
1448             for (PackageInfo pi : packages) {
1449                 boolean fixed = mPermissionHelper.isPermissionFixed(
1450                         pi.packageName, user.getUserHandle().getIdentifier());
1451                 if (fixed) {
1452                     synchronized (mLock) {
1453                         PackagePreferences p = getOrCreatePackagePreferencesLocked(
1454                                 pi.packageName, pi.applicationInfo.uid);
1455                         p.fixedImportance = true;
1456                         for (NotificationChannel channel : p.channels.values()) {
1457                             channel.setImportanceLockedByCriticalDeviceFunction(true);
1458                         }
1459                     }
1460                 }
1461             }
1462         }
1463     }
1464 
updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd)1465     public void updateDefaultApps(int userId, ArraySet<String> toRemove,
1466             ArraySet<Pair<String, Integer>> toAdd) {
1467         synchronized (mLock) {
1468             for (PackagePreferences p : mPackagePreferences.values()) {
1469                 if (userId == UserHandle.getUserId(p.uid)) {
1470                     if (toRemove != null && toRemove.contains(p.pkg)) {
1471                         p.defaultAppLockedImportance = false;
1472                         if (!p.fixedImportance) {
1473                             for (NotificationChannel channel : p.channels.values()) {
1474                                 channel.setImportanceLockedByCriticalDeviceFunction(false);
1475                             }
1476                         }
1477                     }
1478                 }
1479             }
1480             if (toAdd != null) {
1481                 for (Pair<String, Integer> approvedApp : toAdd) {
1482                     PackagePreferences p = getOrCreatePackagePreferencesLocked(
1483                             approvedApp.first,
1484                             approvedApp.second);
1485                     p.defaultAppLockedImportance = true;
1486                     for (NotificationChannel channel : p.channels.values()) {
1487                         channel.setImportanceLockedByCriticalDeviceFunction(true);
1488                     }
1489                 }
1490             }
1491         }
1492     }
1493 
getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted)1494     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
1495             int uid, String groupId, boolean includeDeleted) {
1496         Objects.requireNonNull(pkg);
1497         synchronized (mLock) {
1498             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1499             if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
1500                 return null;
1501             }
1502             NotificationChannelGroup group = r.groups.get(groupId).clone();
1503             group.setChannels(new ArrayList<>());
1504             int N = r.channels.size();
1505             for (int i = 0; i < N; i++) {
1506                 final NotificationChannel nc = r.channels.valueAt(i);
1507                 if (includeDeleted || !nc.isDeleted()) {
1508                     if (groupId.equals(nc.getGroup())) {
1509                         group.addChannel(nc);
1510                     }
1511                 }
1512             }
1513             return group;
1514         }
1515     }
1516 
getNotificationChannelGroup(String groupId, String pkg, int uid)1517     public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
1518             int uid) {
1519         Objects.requireNonNull(pkg);
1520         synchronized (mLock) {
1521             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1522             if (r == null) {
1523                 return null;
1524             }
1525             return r.groups.get(groupId);
1526         }
1527     }
1528 
getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty, boolean includeBlocked, Set<String> activeChannelFilter)1529     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
1530             int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty,
1531             boolean includeBlocked, Set<String> activeChannelFilter) {
1532         Objects.requireNonNull(pkg);
1533         Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
1534         synchronized (mLock) {
1535             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1536             if (r == null) {
1537                 return ParceledListSlice.emptyList();
1538             }
1539             NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
1540             int N = r.channels.size();
1541             for (int i = 0; i < N; i++) {
1542                 final NotificationChannel nc = r.channels.valueAt(i);
1543                 boolean includeChannel = (includeDeleted || !nc.isDeleted())
1544                         && (activeChannelFilter == null
1545                                 || (includeBlocked && nc.getImportance() == IMPORTANCE_NONE)
1546                                 || activeChannelFilter.contains(nc.getId()));
1547                 if (includeChannel) {
1548                     if (nc.getGroup() != null) {
1549                         if (r.groups.get(nc.getGroup()) != null) {
1550                             NotificationChannelGroup ncg = groups.get(nc.getGroup());
1551                             if (ncg == null) {
1552                                 ncg = r.groups.get(nc.getGroup()).clone();
1553                                 ncg.setChannels(new ArrayList<>());
1554                                 groups.put(nc.getGroup(), ncg);
1555                             }
1556                             ncg.addChannel(nc);
1557                         }
1558                     } else {
1559                         nonGrouped.addChannel(nc);
1560                     }
1561                 }
1562             }
1563             if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
1564                 groups.put(null, nonGrouped);
1565             }
1566             if (includeEmpty) {
1567                 for (NotificationChannelGroup group : r.groups.values()) {
1568                     if (!groups.containsKey(group.getId())) {
1569                         groups.put(group.getId(), group);
1570                     }
1571                 }
1572             }
1573             return new ParceledListSlice<>(new ArrayList<>(groups.values()));
1574         }
1575     }
1576 
deleteNotificationChannelGroup(String pkg, int uid, String groupId, int callingUid, boolean fromSystemOrSystemUi)1577     public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
1578             String groupId, int callingUid, boolean fromSystemOrSystemUi) {
1579         List<NotificationChannel> deletedChannels = new ArrayList<>();
1580         boolean groupBypassedDnd = false;
1581         synchronized (mLock) {
1582             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1583             if (r == null || TextUtils.isEmpty(groupId)) {
1584                 return deletedChannels;
1585             }
1586 
1587             NotificationChannelGroup channelGroup = r.groups.remove(groupId);
1588             if (channelGroup != null) {
1589                 mNotificationChannelLogger.logNotificationChannelGroupDeleted(channelGroup, uid,
1590                         pkg);
1591             }
1592 
1593             int N = r.channels.size();
1594             for (int i = 0; i < N; i++) {
1595                 final NotificationChannel nc = r.channels.valueAt(i);
1596                 if (groupId.equals(nc.getGroup())) {
1597                     groupBypassedDnd |= nc.canBypassDnd();
1598                     deleteNotificationChannelLocked(nc, pkg, uid);
1599                     deletedChannels.add(nc);
1600                 }
1601             }
1602         }
1603         if (groupBypassedDnd) {
1604             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
1605         }
1606         return deletedChannels;
1607     }
1608 
1609     @Override
getNotificationChannelGroups(String pkg, int uid)1610     public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
1611             int uid) {
1612         List<NotificationChannelGroup> groups = new ArrayList<>();
1613         synchronized (mLock) {
1614             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1615             if (r == null) {
1616                 return groups;
1617             }
1618             groups.addAll(r.groups.values());
1619         }
1620         return groups;
1621     }
1622 
getGroupForChannel(String pkg, int uid, String channelId)1623     public NotificationChannelGroup getGroupForChannel(String pkg, int uid, String channelId) {
1624         synchronized (mLock) {
1625             PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
1626             if (p != null) {
1627                 NotificationChannel nc = p.channels.get(channelId);
1628                 if (nc != null && !nc.isDeleted()) {
1629                     if (nc.getGroup() != null) {
1630                         NotificationChannelGroup group = p.groups.get(nc.getGroup());
1631                         if (group != null) {
1632                             return group;
1633                         }
1634                     }
1635                 }
1636             }
1637         }
1638         return null;
1639     }
1640 
getConversations(IntArray userIds, boolean onlyImportant)1641     public ArrayList<ConversationChannelWrapper> getConversations(IntArray userIds,
1642             boolean onlyImportant) {
1643         synchronized (mLock) {
1644             ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
1645             for (PackagePreferences p : mPackagePreferences.values()) {
1646                 if (userIds.binarySearch(UserHandle.getUserId(p.uid)) >= 0) {
1647                     int N = p.channels.size();
1648                     for (int i = 0; i < N; i++) {
1649                         final NotificationChannel nc = p.channels.valueAt(i);
1650                         if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()
1651                                 && !nc.isDemoted()
1652                                 && (nc.isImportantConversation() || !onlyImportant)) {
1653                             ConversationChannelWrapper conversation =
1654                                     new ConversationChannelWrapper();
1655                             conversation.setPkg(p.pkg);
1656                             conversation.setUid(p.uid);
1657                             conversation.setNotificationChannel(nc);
1658                             NotificationChannel parent = p.channels.get(nc.getParentChannelId());
1659                             conversation.setParentChannelLabel(parent == null
1660                                     ? null
1661                                     : parent.getName());
1662                             boolean blockedByGroup = false;
1663                             if (nc.getGroup() != null) {
1664                                 NotificationChannelGroup group = p.groups.get(nc.getGroup());
1665                                 if (group != null) {
1666                                     if (group.isBlocked()) {
1667                                         blockedByGroup = true;
1668                                     } else {
1669                                         conversation.setGroupLabel(group.getName());
1670                                     }
1671                                 }
1672                             }
1673                             if (!blockedByGroup) {
1674                                 conversations.add(conversation);
1675                             }
1676                         }
1677                     }
1678                 }
1679             }
1680 
1681             return conversations;
1682         }
1683     }
1684 
getConversations(String pkg, int uid)1685     public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) {
1686         Objects.requireNonNull(pkg);
1687         synchronized (mLock) {
1688             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1689             if (r == null) {
1690                 return new ArrayList<>();
1691             }
1692             ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
1693             int N = r.channels.size();
1694             for (int i = 0; i < N; i++) {
1695                 final NotificationChannel nc = r.channels.valueAt(i);
1696                 if (!TextUtils.isEmpty(nc.getConversationId())
1697                         && !nc.isDeleted()
1698                         && !nc.isDemoted()) {
1699                     ConversationChannelWrapper conversation = new ConversationChannelWrapper();
1700                     conversation.setPkg(r.pkg);
1701                     conversation.setUid(r.uid);
1702                     conversation.setNotificationChannel(nc);
1703                     conversation.setParentChannelLabel(
1704                             r.channels.get(nc.getParentChannelId()).getName());
1705                     boolean blockedByGroup = false;
1706                     if (nc.getGroup() != null) {
1707                         NotificationChannelGroup group = r.groups.get(nc.getGroup());
1708                         if (group != null) {
1709                             if (group.isBlocked()) {
1710                                 blockedByGroup = true;
1711                             } else {
1712                                 conversation.setGroupLabel(group.getName());
1713                             }
1714                         }
1715                     }
1716                     if (!blockedByGroup) {
1717                         conversations.add(conversation);
1718                     }
1719                 }
1720             }
1721 
1722             return conversations;
1723         }
1724     }
1725 
deleteConversations(String pkg, int uid, Set<String> conversationIds, int callingUid, boolean fromSystemOrSystemUi)1726     public @NonNull List<String> deleteConversations(String pkg, int uid,
1727             Set<String> conversationIds, int callingUid, boolean fromSystemOrSystemUi) {
1728         List<String> deletedChannelIds = new ArrayList<>();
1729         synchronized (mLock) {
1730             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1731             if (r == null) {
1732                 return deletedChannelIds;
1733             }
1734             int N = r.channels.size();
1735             for (int i = 0; i < N; i++) {
1736                 final NotificationChannel nc = r.channels.valueAt(i);
1737                 if (nc.getConversationId() != null
1738                         && conversationIds.contains(nc.getConversationId())) {
1739                     nc.setDeleted(true);
1740                     nc.setDeletedTimeMs(System.currentTimeMillis());
1741                     LogMaker lm = getChannelLog(nc, pkg);
1742                     lm.setType(
1743                             com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
1744                     MetricsLogger.action(lm);
1745                     mNotificationChannelLogger.logNotificationChannelDeleted(nc, uid, pkg);
1746 
1747                     deletedChannelIds.add(nc.getId());
1748                 }
1749             }
1750         }
1751         if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
1752             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
1753         }
1754         return deletedChannelIds;
1755     }
1756 
1757     @Override
getNotificationChannels(String pkg, int uid, boolean includeDeleted)1758     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
1759             boolean includeDeleted) {
1760         Objects.requireNonNull(pkg);
1761         List<NotificationChannel> channels = new ArrayList<>();
1762         synchronized (mLock) {
1763             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1764             if (r == null) {
1765                 return ParceledListSlice.emptyList();
1766             }
1767             int N = r.channels.size();
1768             for (int i = 0; i < N; i++) {
1769                 final NotificationChannel nc = r.channels.valueAt(i);
1770                 if (includeDeleted || !nc.isDeleted()) {
1771                     channels.add(nc);
1772                 }
1773             }
1774             return new ParceledListSlice<>(channels);
1775         }
1776     }
1777 
1778     /**
1779      * Gets all notification channels associated with the given pkg and uid that can bypass dnd
1780      */
getNotificationChannelsBypassingDnd(String pkg, int uid)1781     public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
1782             int uid) {
1783         List<NotificationChannel> channels = new ArrayList<>();
1784         synchronized (mLock) {
1785             final PackagePreferences r = mPackagePreferences.get(
1786                     packagePreferencesKey(pkg, uid));
1787             if (r != null) {
1788                 for (NotificationChannel channel : r.channels.values()) {
1789                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1790                         channels.add(channel);
1791                     }
1792                 }
1793             }
1794         }
1795         return new ParceledListSlice<>(channels);
1796     }
1797 
1798     /**
1799      * True for pre-O apps that only have the default channel, or pre O apps that have no
1800      * channels yet. This method will create the default channel for pre-O apps that don't have it.
1801      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
1802      * upgrades.
1803      */
onlyHasDefaultChannel(String pkg, int uid)1804     public boolean onlyHasDefaultChannel(String pkg, int uid) {
1805         synchronized (mLock) {
1806             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
1807             if (r.channels.size() == 1
1808                     && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1809                 return true;
1810             }
1811             return false;
1812         }
1813     }
1814 
getDeletedChannelCount(String pkg, int uid)1815     public int getDeletedChannelCount(String pkg, int uid) {
1816         Objects.requireNonNull(pkg);
1817         int deletedCount = 0;
1818         synchronized (mLock) {
1819             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1820             if (r == null) {
1821                 return deletedCount;
1822             }
1823             int N = r.channels.size();
1824             for (int i = 0; i < N; i++) {
1825                 final NotificationChannel nc = r.channels.valueAt(i);
1826                 if (nc.isDeleted()) {
1827                     deletedCount++;
1828                 }
1829             }
1830             return deletedCount;
1831         }
1832     }
1833 
getBlockedChannelCount(String pkg, int uid)1834     public int getBlockedChannelCount(String pkg, int uid) {
1835         Objects.requireNonNull(pkg);
1836         int blockedCount = 0;
1837         synchronized (mLock) {
1838             PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
1839             if (r == null) {
1840                 return blockedCount;
1841             }
1842             int N = r.channels.size();
1843             for (int i = 0; i < N; i++) {
1844                 final NotificationChannel nc = r.channels.valueAt(i);
1845                 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1846                     blockedCount++;
1847                 }
1848             }
1849             return blockedCount;
1850         }
1851     }
1852 
1853     /**
1854      * Syncs {@link #mCurrentUserHasChannelsBypassingDnd} with the current user's notification
1855      * policy before updating. Must be called:
1856      * <ul>
1857      *     <li>On system init, after channels and DND configurations are loaded.
1858      *     <li>When the current user is switched, after the corresponding DND config is loaded.
1859      *     <li>If users are removed (the removed user could've been a profile of the current one).
1860      * </ul>
1861      */
syncChannelsBypassingDnd()1862     void syncChannelsBypassingDnd() {
1863         mCurrentUserHasChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1864                 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) != 0;
1865 
1866         updateCurrentUserHasChannelsBypassingDnd(/* callingUid= */ Process.SYSTEM_UID,
1867                 /* fromSystemOrSystemUi= */ true);
1868     }
1869 
1870     /**
1871      * Updates the user's NotificationPolicy based on whether the current userId has channels
1872      * bypassing DND. It should be called whenever a channel is created, updated, or deleted, or
1873      * when the current user (or its profiles) change.
1874      */
updateCurrentUserHasChannelsBypassingDnd(int callingUid, boolean fromSystemOrSystemUi)1875     private void updateCurrentUserHasChannelsBypassingDnd(int callingUid,
1876             boolean fromSystemOrSystemUi) {
1877         ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>();
1878 
1879         final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
1880         synchronized (mLock) {
1881             final int numPackagePreferences = mPackagePreferences.size();
1882             for (int i = 0; i < numPackagePreferences; i++) {
1883                 final PackagePreferences r = mPackagePreferences.valueAt(i);
1884                 if (!currentUserIds.contains(UserHandle.getUserId(r.uid))) {
1885                     continue; // Package isn't associated with any profile of the current userId.
1886                 }
1887 
1888                 for (NotificationChannel channel : r.channels.values()) {
1889                     if (channelIsLiveLocked(r, channel) && channel.canBypassDnd()) {
1890                         candidatePkgs.add(new Pair<>(r.pkg, r.uid));
1891                         break;
1892                     }
1893                 }
1894             }
1895         }
1896         for (int i = candidatePkgs.size() - 1; i >= 0; i--) {
1897             Pair<String, Integer> app = candidatePkgs.valueAt(i);
1898             if (!mPermissionHelper.hasPermission(app.second)) {
1899                 candidatePkgs.removeAt(i);
1900             }
1901         }
1902         boolean haveBypassingApps = candidatePkgs.size() > 0;
1903         if (mCurrentUserHasChannelsBypassingDnd != haveBypassingApps) {
1904             mCurrentUserHasChannelsBypassingDnd = haveBypassingApps;
1905             updateZenPolicy(mCurrentUserHasChannelsBypassingDnd, callingUid, fromSystemOrSystemUi);
1906         }
1907     }
1908 
channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel)1909     private boolean channelIsLiveLocked(PackagePreferences pkgPref, NotificationChannel channel) {
1910         // Channel is in a group that's blocked
1911         if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1912             return false;
1913         }
1914 
1915         // Channel is deleted or is blocked
1916         if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1917             return false;
1918         }
1919 
1920         return true;
1921     }
1922 
updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid, boolean fromSystemOrSystemUi)1923     public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid,
1924             boolean fromSystemOrSystemUi) {
1925         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1926         mZenModeHelper.setNotificationPolicy(
1927                 new NotificationManager.Policy(
1928                         policy.priorityCategories, policy.priorityCallSenders,
1929                         policy.priorityMessageSenders, policy.suppressedVisualEffects,
1930                         (areChannelsBypassingDnd
1931                                 ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0),
1932                         policy.priorityConversationSenders),
1933                 fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
1934                         : ZenModeConfig.UPDATE_ORIGIN_APP,
1935                 callingUid);
1936     }
1937 
1938     // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined.
areChannelsBypassingDnd()1939     public boolean areChannelsBypassingDnd() {
1940         return mCurrentUserHasChannelsBypassingDnd;
1941     }
1942 
1943     /**
1944      * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1945      * {@code uid}, have their importance locked by the user. Locked notifications don't get
1946      * considered for sentiment adjustments (and thus never show a blocking helper).
1947      */
setAppImportanceLocked(String packageName, int uid)1948     public void setAppImportanceLocked(String packageName, int uid) {
1949         synchronized (mLock) {
1950             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid);
1951             if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1952                 return;
1953             }
1954 
1955             prefs.lockedAppFields =
1956                     prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1957         }
1958         updateConfig();
1959     }
1960 
1961     /**
1962      * Returns the delegate for a given package, if it's allowed by the package and the user.
1963      */
getNotificationDelegate(String sourcePkg, int sourceUid)1964     public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1965         synchronized (mLock) {
1966             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1967 
1968             if (prefs == null || prefs.delegate == null) {
1969                 return null;
1970             }
1971             if (!prefs.delegate.mEnabled) {
1972                 return null;
1973             }
1974             return prefs.delegate.mPkg;
1975         }
1976     }
1977 
1978     /**
1979      * Used by an app to delegate notification posting privileges to another apps.
1980      */
setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid)1981     public void setNotificationDelegate(String sourcePkg, int sourceUid,
1982             String delegatePkg, int delegateUid) {
1983         synchronized (mLock) {
1984             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
1985             prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
1986         }
1987     }
1988 
1989     /**
1990      * Used by an app to turn off its notification delegate.
1991      */
revokeNotificationDelegate(String sourcePkg, int sourceUid)1992     public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1993         synchronized (mLock) {
1994             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
1995             if (prefs != null && prefs.delegate != null) {
1996                 prefs.delegate.mEnabled = false;
1997             }
1998         }
1999     }
2000 
2001     /**
2002      * Returns whether the given app is allowed on post notifications on behalf of the other given
2003      * app.
2004      */
isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid)2005     public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
2006             String potentialDelegatePkg, int potentialDelegateUid) {
2007         synchronized (mLock) {
2008             PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid);
2009 
2010             return prefs != null && prefs.isValidDelegate(potentialDelegatePkg,
2011                     potentialDelegateUid);
2012         }
2013     }
2014 
lockFieldsForUpdateLocked(NotificationChannel original, NotificationChannel update)2015     private void lockFieldsForUpdateLocked(NotificationChannel original,
2016             NotificationChannel update) {
2017         if (original.canBypassDnd() != update.canBypassDnd()) {
2018             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
2019         }
2020         if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
2021             update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
2022         }
2023         if (original.getImportance() != update.getImportance()) {
2024             update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
2025         }
2026         if (original.shouldShowLights() != update.shouldShowLights()
2027                 || original.getLightColor() != update.getLightColor()) {
2028             update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
2029         }
2030         if (!Objects.equals(original.getSound(), update.getSound())) {
2031             update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
2032         }
2033         if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
2034                 || !Objects.equals(original.getVibrationEffect(), update.getVibrationEffect())
2035                 || original.shouldVibrate() != update.shouldVibrate()) {
2036             update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
2037         }
2038         if (original.canShowBadge() != update.canShowBadge()) {
2039             update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
2040         }
2041         if (original.getAllowBubbles() != update.getAllowBubbles()) {
2042             update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
2043         }
2044     }
2045 
dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2046     public void dump(PrintWriter pw, String prefix,
2047             @NonNull NotificationManagerService.DumpFilter filter,
2048             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2049         pw.print(prefix);
2050         pw.println("per-package config version: " + XML_VERSION);
2051 
2052         pw.println("PackagePreferences:");
2053         synchronized (mLock) {
2054             dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions);
2055             pw.println("Restored without uid:");
2056             dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null);
2057         }
2058     }
2059 
dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2060     public void dump(ProtoOutputStream proto,
2061             @NonNull NotificationManagerService.DumpFilter filter,
2062             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2063         synchronized (mLock) {
2064             dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter,
2065                     mPackagePreferences, pkgPermissions);
2066             dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID,
2067                     filter, mRestoredWithoutUids, null);
2068         }
2069     }
2070 
2071     @GuardedBy("mLock")
dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions)2072     private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix,
2073             @NonNull NotificationManagerService.DumpFilter filter,
2074             ArrayMap<String, PackagePreferences> packagePreferences,
2075             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
2076         // Used for tracking which package preferences we've seen already for notification
2077         // permission reasons; after handling packages with local preferences, we'll want to dump
2078         // the ones with notification permissions set but not local prefs.
2079         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
2080         if (packagePermissions != null) {
2081             pkgsWithPermissionsToHandle = packagePermissions.keySet();
2082         }
2083         final int N = packagePreferences.size();
2084         for (int i = 0; i < N; i++) {
2085             final PackagePreferences r = packagePreferences.valueAt(i);
2086             if (filter.matches(r.pkg)) {
2087                 pw.print(prefix);
2088                 pw.print("  AppSettings: ");
2089                 pw.print(r.pkg);
2090                 pw.print(" (");
2091                 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
2092                 pw.print(')');
2093                 Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
2094                 if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
2095                     pw.print(" importance=");
2096                     pw.print(NotificationListenerService.Ranking.importanceToString(
2097                             packagePermissions.get(key).first
2098                                     ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
2099                     pw.print(" userSet=");
2100                     pw.print(packagePermissions.get(key).second);
2101                     pkgsWithPermissionsToHandle.remove(key);
2102                 }
2103                 if (r.priority != DEFAULT_PRIORITY) {
2104                     pw.print(" priority=");
2105                     pw.print(Notification.priorityToString(r.priority));
2106                 }
2107                 if (r.visibility != DEFAULT_VISIBILITY) {
2108                     pw.print(" visibility=");
2109                     pw.print(Notification.visibilityToString(r.visibility));
2110                 }
2111                 if (r.showBadge != DEFAULT_SHOW_BADGE) {
2112                     pw.print(" showBadge=");
2113                     pw.print(r.showBadge);
2114                 }
2115                 if (r.defaultAppLockedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) {
2116                     pw.print(" defaultAppLocked=");
2117                     pw.print(r.defaultAppLockedImportance);
2118                 }
2119                 if (r.fixedImportance != DEFAULT_APP_LOCKED_IMPORTANCE) {
2120                     pw.print(" fixedImportance=");
2121                     pw.print(r.fixedImportance);
2122                 }
2123                 pw.println();
2124                 for (NotificationChannel channel : r.channels.values()) {
2125                     pw.print(prefix);
2126                     channel.dump(pw, "    ", filter.redact);
2127                 }
2128                 for (NotificationChannelGroup group : r.groups.values()) {
2129                     pw.print(prefix);
2130                     pw.print("  ");
2131                     pw.print("  ");
2132                     pw.println(group);
2133                 }
2134             }
2135         }
2136         // Handle any remaining packages with permissions
2137         if (pkgsWithPermissionsToHandle != null) {
2138             for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
2139                 // p.first is the uid of this package; p.second is the package name
2140                 if (filter.matches(p.second)) {
2141                     pw.print(prefix);
2142                     pw.print("  AppSettings: ");
2143                     pw.print(p.second);
2144                     pw.print(" (");
2145                     pw.print(p.first == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(p.first));
2146                     pw.print(')');
2147                     pw.print(" importance=");
2148                     pw.print(NotificationListenerService.Ranking.importanceToString(
2149                             packagePermissions.get(p).first
2150                                     ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
2151                     pw.print(" userSet=");
2152                     pw.print(packagePermissions.get(p).second);
2153                     pw.println();
2154                 }
2155             }
2156         }
2157     }
2158 
dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions)2159     private void dumpPackagePreferencesLocked(ProtoOutputStream proto, long fieldId,
2160             @NonNull NotificationManagerService.DumpFilter filter,
2161             ArrayMap<String, PackagePreferences> packagePreferences,
2162             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> packagePermissions) {
2163         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
2164         if (packagePermissions != null) {
2165             pkgsWithPermissionsToHandle = packagePermissions.keySet();
2166         }
2167 
2168         final int N = packagePreferences.size();
2169         long fToken;
2170         for (int i = 0; i < N; i++) {
2171             final PackagePreferences r = packagePreferences.valueAt(i);
2172             if (filter.matches(r.pkg)) {
2173                 fToken = proto.start(fieldId);
2174 
2175                 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
2176                 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
2177                 Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
2178                 if (packagePermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
2179                     proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
2180                             packagePermissions.get(key).first
2181                                     ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
2182                     pkgsWithPermissionsToHandle.remove(key);
2183                 }
2184                 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
2185                 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
2186                 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
2187 
2188                 for (NotificationChannel channel : r.channels.values()) {
2189                     channel.dumpDebug(proto, RankingHelperProto.RecordProto.CHANNELS);
2190                 }
2191                 for (NotificationChannelGroup group : r.groups.values()) {
2192                     group.dumpDebug(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
2193                 }
2194 
2195                 proto.end(fToken);
2196             }
2197         }
2198 
2199         if (pkgsWithPermissionsToHandle != null) {
2200             for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
2201                 if (filter.matches(p.second)) {
2202                     fToken = proto.start(fieldId);
2203                     proto.write(RankingHelperProto.RecordProto.PACKAGE, p.second);
2204                     proto.write(RankingHelperProto.RecordProto.UID, p.first);
2205                     proto.write(RankingHelperProto.RecordProto.IMPORTANCE,
2206                             packagePermissions.get(p).first
2207                                     ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE);
2208                     proto.end(fToken);
2209                 }
2210             }
2211         }
2212     }
2213 
2214     /**
2215      * @return State of the full screen intent permission for this package.
2216      */
2217     @VisibleForTesting
getFsiState(String pkg, int uid, boolean requestedFSIPermission)2218     int getFsiState(String pkg, int uid, boolean requestedFSIPermission) {
2219         if (!requestedFSIPermission) {
2220             return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
2221         }
2222         final AttributionSource attributionSource =
2223                 new AttributionSource.Builder(uid).setPackageName(pkg).build();
2224 
2225         final int result = mPermissionManager.checkPermissionForPreflight(
2226                 android.Manifest.permission.USE_FULL_SCREEN_INTENT, attributionSource);
2227 
2228         if (result == PermissionManager.PERMISSION_GRANTED) {
2229             return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
2230         }
2231         return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
2232     }
2233 
2234     /**
2235      * @return True if the current full screen intent permission state for this package was set by
2236      * the user.
2237      */
2238     @VisibleForTesting
isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags)2239     boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) {
2240         if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
2241             return false;
2242         }
2243         return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
2244     }
2245 
2246     /**
2247      * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
2248      */
pullPackagePreferencesStats(List<StatsEvent> events, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2249     public void pullPackagePreferencesStats(List<StatsEvent> events,
2250             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2251         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
2252         if (pkgPermissions != null) {
2253             pkgsWithPermissionsToHandle = pkgPermissions.keySet();
2254         }
2255         int pulledEvents = 0;
2256         synchronized (mLock) {
2257             for (int i = 0; i < mPackagePreferences.size(); i++) {
2258                 if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) {
2259                     break;
2260                 }
2261                 pulledEvents++;
2262                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2263 
2264                 // collect whether this package's importance info was user-set for later, if needed
2265                 // before the migration is enabled, this will simply default to false in all cases.
2266                 boolean importanceIsUserSet = false;
2267                 // Even if this package's data is not present, we need to write something;
2268                 // default to IMPORTANCE_UNSPECIFIED. If PM doesn't know about the package
2269                 // for some reason, notifications are not allowed, but in logged output we want
2270                 // to distinguish this case from the actually-banned packages.
2271                 int importance = IMPORTANCE_UNSPECIFIED;
2272                 Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
2273                 if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
2274                     Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key);
2275                     importance = permissionPair.first
2276                             ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
2277                     // cache the second value for writing later
2278                     importanceIsUserSet = permissionPair.second;
2279 
2280                     pkgsWithPermissionsToHandle.remove(key);
2281                 }
2282 
2283                 final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
2284                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
2285 
2286                 final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission);
2287 
2288                 final int currentPermissionFlags = mPm.getPermissionFlags(
2289                         android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
2290                         UserHandle.getUserHandleForUid(r.uid));
2291 
2292                 final boolean fsiIsUserSet =
2293                         isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
2294                                 currentPermissionFlags);
2295 
2296                 events.add(FrameworkStatsLog.buildStatsEvent(
2297                         PACKAGE_NOTIFICATION_PREFERENCES,
2298                         /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
2299                         /* optional int32 importance = 2 */ importance,
2300                         /* optional int32 visibility = 3 */ r.visibility,
2301                         /* optional int32 user_locked_fields = 4 */ r.lockedAppFields,
2302                         /* optional bool user_set_importance = 5 */ importanceIsUserSet,
2303                         /* optional FsiState fsi_state = 6 */ fsiState,
2304                         /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet));
2305             }
2306         }
2307 
2308         // handle remaining packages with PackageManager permissions but not local settings
2309         if (pkgPermissions != null) {
2310             for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
2311                 if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) {
2312                     break;
2313                 }
2314                 pulledEvents++;
2315                 // Because all fields are required in FrameworkStatsLog.buildStatsEvent, we have
2316                 // to fill in default values for all the unspecified fields.
2317                 events.add(FrameworkStatsLog.buildStatsEvent(
2318                         PACKAGE_NOTIFICATION_PREFERENCES,
2319                         /* optional int32 uid = 1 [(is_uid) = true] */ p.first,
2320                         /* optional int32 importance = 2 */ pkgPermissions.get(p).first
2321                                 ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE,
2322                         /* optional int32 visibility = 3 */ DEFAULT_VISIBILITY,
2323                         /* optional int32 user_locked_fields = 4 */ DEFAULT_LOCKED_APP_FIELDS,
2324                         /* optional bool user_set_importance = 5 */ pkgPermissions.get(p).second,
2325                         /* optional FsiState fsi_state = 6 */ 0,
2326                         /* optional bool is_fsi_permission_user_set = 7 */ false));
2327             }
2328         }
2329     }
2330 
2331     /**
2332      * Fills out {@link PackageNotificationChannelPreferences} proto and wraps it in a
2333      * {@link StatsEvent}.
2334      */
pullPackageChannelPreferencesStats(List<StatsEvent> events)2335     public void pullPackageChannelPreferencesStats(List<StatsEvent> events) {
2336         synchronized (mLock) {
2337             int totalChannelsPulled = 0;
2338             for (int i = 0; i < mPackagePreferences.size(); i++) {
2339                 if (totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
2340                     break;
2341                 }
2342                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2343                 for (NotificationChannel channel : r.channels.values()) {
2344                     if (++totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) {
2345                         break;
2346                     }
2347                     events.add(FrameworkStatsLog.buildStatsEvent(
2348                             PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES,
2349                             /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
2350                             /* optional string channel_id = 2 */ channel.getId(),
2351                             /* optional string channel_name = 3 */ channel.getName().toString(),
2352                             /* optional string description = 4 */ channel.getDescription(),
2353                             /* optional int32 importance = 5 */ channel.getImportance(),
2354                             /* optional int32 user_locked_fields = 6 */
2355                             channel.getUserLockedFields(),
2356                             /* optional bool is_deleted = 7 */ channel.isDeleted(),
2357                             /* optional bool is_conversation = 8 */
2358                             channel.getConversationId() != null,
2359                             /* optional bool is_demoted_conversation = 9 */ channel.isDemoted(),
2360                             /* optional bool is_important_conversation = 10 */
2361                             channel.isImportantConversation()));
2362                 }
2363             }
2364         }
2365     }
2366 
2367     /**
2368      * Fills out {@link PackageNotificationChannelGroupPreferences} proto and wraps it in a
2369      * {@link StatsEvent}.
2370      */
pullPackageChannelGroupPreferencesStats(List<StatsEvent> events)2371     public void pullPackageChannelGroupPreferencesStats(List<StatsEvent> events) {
2372         synchronized (mLock) {
2373             int totalGroupsPulled = 0;
2374             for (int i = 0; i < mPackagePreferences.size(); i++) {
2375                 if (totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
2376                     break;
2377                 }
2378                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2379                 for (NotificationChannelGroup groupChannel : r.groups.values()) {
2380                     if (++totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) {
2381                         break;
2382                     }
2383                     events.add(FrameworkStatsLog.buildStatsEvent(
2384                             PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES,
2385                             /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
2386                             /* optional string group_id = 2 */ groupChannel.getId(),
2387                             /* optional string group_name = 3 */ groupChannel.getName().toString(),
2388                             /* optional string description = 4 */ groupChannel.getDescription(),
2389                             /* optional bool is_blocked = 5 */ groupChannel.isBlocked(),
2390                             /* optional int32 user_locked_fields = 6 */
2391                             groupChannel.getUserLockedFields()));
2392                 }
2393             }
2394         }
2395     }
2396 
dumpJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2397     public JSONObject dumpJson(NotificationManagerService.DumpFilter filter,
2398             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2399         JSONObject ranking = new JSONObject();
2400         JSONArray PackagePreferencess = new JSONArray();
2401         synchronized (mLock) {
2402             try {
2403                 ranking.put("noUid", mRestoredWithoutUids.size());
2404             } catch (JSONException e) {
2405                 // pass
2406             }
2407         }
2408 
2409         // Track data that we've handled from the permissions-based list
2410         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
2411         if (pkgPermissions != null) {
2412             pkgsWithPermissionsToHandle = pkgPermissions.keySet();
2413         }
2414 
2415         synchronized (mLock) {
2416             final int N = mPackagePreferences.size();
2417             for (int i = 0; i < N; i++) {
2418                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2419                 if (filter == null || filter.matches(r.pkg)) {
2420                     JSONObject PackagePreferences = new JSONObject();
2421                     try {
2422                         PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
2423                         PackagePreferences.put("packageName", r.pkg);
2424                         Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
2425                         if (pkgPermissions != null
2426                                 && pkgsWithPermissionsToHandle.contains(key)) {
2427                             PackagePreferences.put("importance",
2428                                     NotificationListenerService.Ranking.importanceToString(
2429                                             pkgPermissions.get(key).first
2430                                                     ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
2431                             pkgsWithPermissionsToHandle.remove(key);
2432                         }
2433                         if (r.priority != DEFAULT_PRIORITY) {
2434                             PackagePreferences.put("priority",
2435                                     Notification.priorityToString(r.priority));
2436                         }
2437                         if (r.visibility != DEFAULT_VISIBILITY) {
2438                             PackagePreferences.put("visibility",
2439                                     Notification.visibilityToString(r.visibility));
2440                         }
2441                         if (r.showBadge != DEFAULT_SHOW_BADGE) {
2442                             PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
2443                         }
2444                         JSONArray channels = new JSONArray();
2445                         for (NotificationChannel channel : r.channels.values()) {
2446                             channels.put(channel.toJson());
2447                         }
2448                         PackagePreferences.put("channels", channels);
2449                         JSONArray groups = new JSONArray();
2450                         for (NotificationChannelGroup group : r.groups.values()) {
2451                             groups.put(group.toJson());
2452                         }
2453                         PackagePreferences.put("groups", groups);
2454                     } catch (JSONException e) {
2455                         // pass
2456                     }
2457                     PackagePreferencess.put(PackagePreferences);
2458                 }
2459             }
2460         }
2461 
2462         // handle packages for which there are permissions but no local settings
2463         if (pkgsWithPermissionsToHandle != null) {
2464             for (Pair<Integer, String> p : pkgsWithPermissionsToHandle) {
2465                 if (filter == null || filter.matches(p.second)) {
2466                     JSONObject PackagePreferences = new JSONObject();
2467                     try {
2468                         PackagePreferences.put("userId", UserHandle.getUserId(p.first));
2469                         PackagePreferences.put("packageName", p.second);
2470                         PackagePreferences.put("importance",
2471                                 NotificationListenerService.Ranking.importanceToString(
2472                                         pkgPermissions.get(p).first
2473                                                 ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE));
2474                     } catch (JSONException e) {
2475                         // pass
2476                     }
2477                     PackagePreferencess.put(PackagePreferences);
2478                 }
2479             }
2480         }
2481 
2482         try {
2483             ranking.put("PackagePreferencess", PackagePreferencess);
2484         } catch (JSONException e) {
2485             // pass
2486         }
2487         return ranking;
2488     }
2489 
2490     /**
2491      * Dump only the ban information as structured JSON for the stats collector.
2492      *
2493      * This is intentionally redundant with {#link dumpJson} because the old
2494      * scraper will expect this format.
2495      *
2496      * @param filter
2497      * @return
2498      */
dumpBansJson(NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2499     public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter,
2500             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2501         JSONArray bans = new JSONArray();
2502         Map<Integer, String> packageBans = getPermissionBasedPackageBans(pkgPermissions);
2503         for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
2504             final int userId = UserHandle.getUserId(ban.getKey());
2505             final String packageName = ban.getValue();
2506             if (filter == null || filter.matches(packageName)) {
2507                 JSONObject banJson = new JSONObject();
2508                 try {
2509                     banJson.put("userId", userId);
2510                     banJson.put("packageName", packageName);
2511                 } catch (JSONException e) {
2512                     e.printStackTrace();
2513                 }
2514                 bans.put(banJson);
2515             }
2516         }
2517         return bans;
2518     }
2519 
getPackageBans()2520     public Map<Integer, String> getPackageBans() {
2521         synchronized (mLock) {
2522             final int N = mPackagePreferences.size();
2523             ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
2524             for (int i = 0; i < N; i++) {
2525                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2526                 if (r.importance == IMPORTANCE_NONE) {
2527                     packageBans.put(r.uid, r.pkg);
2528                 }
2529             }
2530 
2531             return packageBans;
2532         }
2533     }
2534 
2535     // Same functionality as getPackageBans by extracting the set of packages from the provided
2536     // map that are disallowed from sending notifications.
getPermissionBasedPackageBans( ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions)2537     protected Map<Integer, String> getPermissionBasedPackageBans(
2538             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
2539         ArrayMap<Integer, String> packageBans = new ArrayMap<>();
2540         if (pkgPermissions != null) {
2541             for (Pair<Integer, String> p : pkgPermissions.keySet()) {
2542                 if (!pkgPermissions.get(p).first) {
2543                     packageBans.put(p.first, p.second);
2544                 }
2545             }
2546         }
2547         return packageBans;
2548     }
2549 
2550     /**
2551      * Dump only the channel information as structured JSON for the stats collector.
2552      *
2553      * This is intentionally redundant with {#link dumpJson} because the old
2554      * scraper will expect this format.
2555      *
2556      * @param filter
2557      * @return
2558      */
dumpChannelsJson(NotificationManagerService.DumpFilter filter)2559     public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
2560         JSONArray channels = new JSONArray();
2561         Map<String, Integer> packageChannels = getPackageChannels();
2562         for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
2563             final String packageName = channelCount.getKey();
2564             if (filter == null || filter.matches(packageName)) {
2565                 JSONObject channelCountJson = new JSONObject();
2566                 try {
2567                     channelCountJson.put("packageName", packageName);
2568                     channelCountJson.put("channelCount", channelCount.getValue());
2569                 } catch (JSONException e) {
2570                     e.printStackTrace();
2571                 }
2572                 channels.put(channelCountJson);
2573             }
2574         }
2575         return channels;
2576     }
2577 
getPackageChannels()2578     private Map<String, Integer> getPackageChannels() {
2579         ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
2580         synchronized (mLock) {
2581             for (int i = 0; i < mPackagePreferences.size(); i++) {
2582                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2583                 int channelCount = 0;
2584                 for (int j = 0; j < r.channels.size(); j++) {
2585                     if (!r.channels.valueAt(j).isDeleted()) {
2586                         channelCount++;
2587                     }
2588                 }
2589                 packageChannels.put(r.pkg, channelCount);
2590             }
2591         }
2592         return packageChannels;
2593     }
2594 
onUserRemoved(int userId)2595     public void onUserRemoved(int userId) {
2596         synchronized (mLock) {
2597             int N = mPackagePreferences.size();
2598             for (int i = N - 1; i >= 0; i--) {
2599                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
2600                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
2601                     mPackagePreferences.removeAt(i);
2602                 }
2603             }
2604         }
2605     }
2606 
onLocaleChanged(Context context, int userId)2607     protected void onLocaleChanged(Context context, int userId) {
2608         synchronized (mLock) {
2609             int N = mPackagePreferences.size();
2610             for (int i = 0; i < N; i++) {
2611                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
2612                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
2613                     if (PackagePreferences.channels.containsKey(
2614                             NotificationChannel.DEFAULT_CHANNEL_ID)) {
2615                         PackagePreferences.channels.get(
2616                                 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
2617                                 context.getResources().getString(
2618                                         R.string.default_notification_channel_label));
2619                     }
2620                 }
2621             }
2622         }
2623     }
2624 
onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)2625     public boolean onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
2626             int[] uidList) {
2627         if (pkgList == null || pkgList.length == 0) {
2628             return false; // nothing to do
2629         }
2630         boolean updated = false;
2631         if (removingPackage) {
2632             // Remove notification settings for uninstalled package
2633             int size = Math.min(pkgList.length, uidList.length);
2634             for (int i = 0; i < size; i++) {
2635                 final String pkg = pkgList[i];
2636                 final int uid = uidList[i];
2637                 synchronized (mLock) {
2638                     mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
2639                     mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
2640                 }
2641                 updated = true;
2642             }
2643         } else {
2644             for (String pkg : pkgList) {
2645                 try {
2646                     // Package install
2647                     int uid = mPm.getPackageUidAsUser(pkg, changeUserId);
2648                     PackagePermission p = null;
2649                     synchronized (mLock) {
2650                         final PackagePreferences r =
2651                                 mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId));
2652                         if (r != null) {
2653                             r.uid = uid;
2654                             mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId));
2655                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
2656 
2657                             // Try to restore any unrestored sound resources
2658                             for (NotificationChannel channel : r.channels.values()) {
2659                                 if (!channel.isSoundRestored()) {
2660                                     Uri uri = channel.getSound();
2661                                     Uri restoredUri =
2662                                             channel.restoreSoundUri(
2663                                                     mContext,
2664                                                     uri,
2665                                                     true,
2666                                                     channel.getAudioAttributes().getUsage());
2667                                     if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
2668                                             restoredUri)) {
2669                                         Log.w(TAG,
2670                                                 "Could not restore sound: " + uri + " for channel: "
2671                                                         + channel);
2672                                     }
2673                                     channel.setSound(restoredUri, channel.getAudioAttributes());
2674                                 }
2675                             }
2676 
2677                             if (r.migrateToPm) {
2678                                 p = new PackagePermission(
2679                                         r.pkg, UserHandle.getUserId(r.uid),
2680                                         r.importance != IMPORTANCE_NONE,
2681                                         hasUserConfiguredSettings(r));
2682                             }
2683                             updated = true;
2684                         }
2685                     }
2686                     if (p != null) {
2687                         mPermissionHelper.setNotificationPermission(p);
2688                     }
2689                 } catch (Exception e) {
2690                     Slog.e(TAG, "could not restore " + pkg, e);
2691                 }
2692                 // Package upgrade
2693                 try {
2694                     PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg,
2695                             mPm.getPackageUidAsUser(pkg, changeUserId));
2696                     if (fullPackagePreferences != null) {
2697                         updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences);
2698                         updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences);
2699                     }
2700                 } catch (PackageManager.NameNotFoundException e) {
2701                 }
2702             }
2703         }
2704 
2705         if (updated) {
2706             updateConfig();
2707         }
2708         return updated;
2709     }
2710 
clearData(String pkg, int uid)2711     public void clearData(String pkg, int uid) {
2712         synchronized (mLock) {
2713             PackagePreferences p = getPackagePreferencesLocked(pkg, uid);
2714             if (p != null) {
2715                 p.channels = new ArrayMap<>();
2716                 p.groups = new ArrayMap<>();
2717                 p.delegate = null;
2718                 p.lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
2719                 p.bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
2720                 p.importance = DEFAULT_IMPORTANCE;
2721                 p.priority = DEFAULT_PRIORITY;
2722                 p.visibility = DEFAULT_VISIBILITY;
2723                 p.showBadge = DEFAULT_SHOW_BADGE;
2724             }
2725         }
2726     }
2727 
getChannelLog(NotificationChannel channel, String pkg)2728     private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
2729         return new LogMaker(
2730                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
2731                         .ACTION_NOTIFICATION_CHANNEL)
2732                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
2733                 .setPackageName(pkg)
2734                 .addTaggedData(
2735                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2736                                 .FIELD_NOTIFICATION_CHANNEL_ID,
2737                         channel.getId())
2738                 .addTaggedData(
2739                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2740                                 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
2741                         channel.getImportance());
2742     }
2743 
getChannelGroupLog(String groupId, String pkg)2744     private LogMaker getChannelGroupLog(String groupId, String pkg) {
2745         return new LogMaker(
2746                 com.android.internal.logging.nano.MetricsProto.MetricsEvent
2747                         .ACTION_NOTIFICATION_CHANNEL_GROUP)
2748                 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
2749                 .addTaggedData(
2750                         com.android.internal.logging.nano.MetricsProto.MetricsEvent
2751                                 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
2752                         groupId)
2753                 .setPackageName(pkg);
2754     }
2755 
2756     /** Requests check of the feature setting for showing media notifications in quick settings. */
updateMediaNotificationFilteringEnabled()2757     public void updateMediaNotificationFilteringEnabled() {
2758         // TODO(b/192412820): Consolidate SHOW_MEDIA_ON_QUICK_SETTINGS into compile-time value.
2759         final boolean newValue = Settings.Global.getInt(mContext.getContentResolver(),
2760                 Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) > 0
2761                         && mContext.getResources().getBoolean(
2762                                 R.bool.config_quickSettingsShowMediaPlayer);
2763         if (newValue != mIsMediaNotificationFilteringEnabled) {
2764             mIsMediaNotificationFilteringEnabled = newValue;
2765             updateConfig();
2766         }
2767     }
2768 
2769     /** Returns true if the setting is enabled for showing media notifications in quick settings. */
isMediaNotificationFilteringEnabled()2770     public boolean isMediaNotificationFilteringEnabled() {
2771         return mIsMediaNotificationFilteringEnabled;
2772     }
2773 
updateBadgingEnabled()2774     public void updateBadgingEnabled() {
2775         if (mBadgingEnabled == null) {
2776             mBadgingEnabled = new SparseBooleanArray();
2777         }
2778         boolean changed = false;
2779         // update the cached values
2780         for (int index = 0; index < mBadgingEnabled.size(); index++) {
2781             int userId = mBadgingEnabled.keyAt(index);
2782             final boolean oldValue = mBadgingEnabled.get(userId);
2783             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2784                     Settings.Secure.NOTIFICATION_BADGING,
2785                     DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
2786             mBadgingEnabled.put(userId, newValue);
2787             changed |= oldValue != newValue;
2788         }
2789         if (changed) {
2790             updateConfig();
2791         }
2792     }
2793 
badgingEnabled(UserHandle userHandle)2794     public boolean badgingEnabled(UserHandle userHandle) {
2795         int userId = userHandle.getIdentifier();
2796         if (userId == UserHandle.USER_ALL) {
2797             return false;
2798         }
2799         if (mBadgingEnabled.indexOfKey(userId) < 0) {
2800             mBadgingEnabled.put(userId,
2801                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
2802                             Settings.Secure.NOTIFICATION_BADGING,
2803                             DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
2804         }
2805         return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
2806     }
2807 
2808     /** Updates whether bubbles are enabled for this user. */
updateBubblesEnabled()2809     public void updateBubblesEnabled() {
2810         if (mBubblesEnabled == null) {
2811             mBubblesEnabled = new SparseBooleanArray();
2812         }
2813         boolean changed = false;
2814         // update the cached values
2815         for (int index = 0; index < mBubblesEnabled.size(); index++) {
2816             int userId = mBubblesEnabled.keyAt(index);
2817             final boolean oldValue = mBubblesEnabled.get(userId);
2818             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2819                     Settings.Secure.NOTIFICATION_BUBBLES,
2820                     DEFAULT_BUBBLES_ENABLED ? 1 : 0, userId) != 0;
2821             mBubblesEnabled.put(userId, newValue);
2822             changed |= oldValue != newValue;
2823         }
2824         if (changed) {
2825             updateConfig();
2826         }
2827     }
2828 
2829     /** Returns true if bubbles are enabled for this user. */
bubblesEnabled(UserHandle userHandle)2830     public boolean bubblesEnabled(UserHandle userHandle) {
2831         int userId = userHandle.getIdentifier();
2832         if (userId == UserHandle.USER_ALL) {
2833             return false;
2834         }
2835         if (mBubblesEnabled.indexOfKey(userId) < 0) {
2836             mBubblesEnabled.put(userId,
2837                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
2838                             Settings.Secure.NOTIFICATION_BUBBLES,
2839                             DEFAULT_BUBBLES_ENABLED ? 1 : 0, userId) != 0);
2840         }
2841         return mBubblesEnabled.get(userId, DEFAULT_BUBBLES_ENABLED);
2842     }
2843 
updateLockScreenPrivateNotifications()2844     public void updateLockScreenPrivateNotifications() {
2845         if (mLockScreenPrivateNotifications == null) {
2846             mLockScreenPrivateNotifications = new SparseBooleanArray();
2847         }
2848         boolean changed = false;
2849         // update the cached values
2850         for (int index = 0; index < mLockScreenPrivateNotifications.size(); index++) {
2851             int userId = mLockScreenPrivateNotifications.keyAt(index);
2852             final boolean oldValue = mLockScreenPrivateNotifications.get(userId);
2853             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2854                     Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1, userId) != 0;
2855             mLockScreenPrivateNotifications.put(userId, newValue);
2856             changed |= oldValue != newValue;
2857         }
2858         if (changed) {
2859             updateConfig();
2860         }
2861     }
2862 
updateLockScreenShowNotifications()2863     public void updateLockScreenShowNotifications() {
2864         if (mLockScreenShowNotifications == null) {
2865             mLockScreenShowNotifications = new SparseBooleanArray();
2866         }
2867         boolean changed = false;
2868         // update the cached values
2869         for (int index = 0; index < mLockScreenShowNotifications.size(); index++) {
2870             int userId = mLockScreenShowNotifications.keyAt(index);
2871             final boolean oldValue = mLockScreenShowNotifications.get(userId);
2872             final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
2873                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1, userId) != 0;
2874             mLockScreenShowNotifications.put(userId, newValue);
2875             changed |= oldValue != newValue;
2876         }
2877         if (changed) {
2878             updateConfig();
2879         }
2880     }
2881 
2882     @Override
canShowNotificationsOnLockscreen(int userId)2883     public boolean canShowNotificationsOnLockscreen(int userId) {
2884         if (mLockScreenShowNotifications == null) {
2885             mLockScreenShowNotifications = new SparseBooleanArray();
2886         }
2887         return mLockScreenShowNotifications.get(userId, true);
2888     }
2889 
2890     @Override
canShowPrivateNotificationsOnLockScreen(int userId)2891     public boolean canShowPrivateNotificationsOnLockScreen(int userId) {
2892         if (mLockScreenPrivateNotifications == null) {
2893             mLockScreenPrivateNotifications = new SparseBooleanArray();
2894         }
2895         return mLockScreenPrivateNotifications.get(userId, true);
2896     }
2897 
unlockAllNotificationChannels()2898     public void unlockAllNotificationChannels() {
2899         synchronized (mLock) {
2900             final int numPackagePreferences = mPackagePreferences.size();
2901             for (int i = 0; i < numPackagePreferences; i++) {
2902                 final PackagePreferences r = mPackagePreferences.valueAt(i);
2903                 for (NotificationChannel channel : r.channels.values()) {
2904                     channel.unlockFields(USER_LOCKED_IMPORTANCE);
2905                 }
2906             }
2907         }
2908     }
2909 
migrateNotificationPermissions(List<UserInfo> users)2910     public void migrateNotificationPermissions(List<UserInfo> users) {
2911         for (UserInfo user : users) {
2912             List<PackageInfo> packages = mPm.getInstalledPackagesAsUser(
2913                     PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL),
2914                     user.getUserHandle().getIdentifier());
2915             for (PackageInfo pi : packages) {
2916                 synchronized (mLock) {
2917                     PackagePreferences p = getOrCreatePackagePreferencesLocked(
2918                             pi.packageName, pi.applicationInfo.uid);
2919                     if (p.migrateToPm && p.uid != UNKNOWN_UID) {
2920                         try {
2921                             PackagePermission pkgPerm = new PackagePermission(
2922                                     p.pkg, UserHandle.getUserId(p.uid),
2923                                     p.importance != IMPORTANCE_NONE,
2924                                     hasUserConfiguredSettings(p));
2925                             mPermissionHelper.setNotificationPermission(pkgPerm);
2926                         } catch (Exception e) {
2927                             Slog.e(TAG, "could not migrate setting for " + p.pkg, e);
2928                         }
2929                     }
2930                 }
2931             }
2932         }
2933     }
2934 
updateConfig()2935     private void updateConfig() {
2936         mRankingHandler.requestSort();
2937     }
2938 
packagePreferencesKey(String pkg, int uid)2939     private static String packagePreferencesKey(String pkg, int uid) {
2940         return pkg + "|" + uid;
2941     }
2942 
unrestoredPackageKey(String pkg, @UserIdInt int userId)2943     private static String unrestoredPackageKey(String pkg, @UserIdInt int userId) {
2944         return pkg + "|" + userId;
2945     }
2946 
2947     private static class PackagePreferences {
2948         String pkg;
2949         int uid = UNKNOWN_UID;
2950         int importance = DEFAULT_IMPORTANCE;
2951         int priority = DEFAULT_PRIORITY;
2952         int visibility = DEFAULT_VISIBILITY;
2953         boolean showBadge = DEFAULT_SHOW_BADGE;
2954         int bubblePreference = DEFAULT_BUBBLE_PREFERENCE;
2955         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
2956         // these fields are loaded on boot from a different source of truth and so are not
2957         // written to notification policy xml
2958         boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
2959         boolean fixedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
2960 
2961         boolean hasSentInvalidMessage = false;
2962         boolean hasSentValidMessage = false;
2963         // note: only valid while hasSentMessage is false and hasSentInvalidMessage is true
2964         boolean userDemotedMsgApp = false;
2965         boolean hasSentValidBubble = false;
2966 
2967         boolean migrateToPm = false;
2968         long creationTime;
2969 
2970         @UserIdInt int userId;
2971 
2972         Delegate delegate = null;
2973         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
2974         Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
2975 
isValidDelegate(String pkg, int uid)2976         public boolean isValidDelegate(String pkg, int uid) {
2977             return delegate != null && delegate.isAllowed(pkg, uid);
2978         }
2979     }
2980 
2981     private static class Delegate {
2982         static final boolean DEFAULT_ENABLED = true;
2983 
2984         final String mPkg;
2985         final int mUid;
2986         boolean mEnabled;
2987 
Delegate(String pkg, int uid, boolean enabled)2988         Delegate(String pkg, int uid, boolean enabled) {
2989             mPkg = pkg;
2990             mUid = uid;
2991             mEnabled = enabled;
2992         }
2993 
isAllowed(String pkg, int uid)2994         public boolean isAllowed(String pkg, int uid) {
2995             if (pkg == null || uid == UNKNOWN_UID) {
2996                 return false;
2997             }
2998             return pkg.equals(mPkg)
2999                     && uid == mUid
3000                     && mEnabled;
3001         }
3002     }
3003 }
3004