1 /**
2  * Copyright (c) 2014, 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 package com.android.server.notification;
17 
18 import static android.app.NotificationManager.IMPORTANCE_NONE;
19 
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.app.Notification;
23 import android.app.NotificationChannel;
24 import android.app.NotificationChannelGroup;
25 import android.app.NotificationManager;
26 import android.content.Context;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.pm.ParceledListSlice;
31 import android.metrics.LogMaker;
32 import android.os.Build;
33 import android.os.UserHandle;
34 import android.provider.Settings.Secure;
35 import android.service.notification.NotificationListenerService.Ranking;
36 import android.service.notification.RankingHelperProto;
37 import android.service.notification.RankingHelperProto.RecordProto;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.Slog;
41 import android.util.SparseBooleanArray;
42 import android.util.proto.ProtoOutputStream;
43 
44 import com.android.internal.R;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.logging.MetricsLogger;
47 import com.android.internal.logging.nano.MetricsProto;
48 import com.android.internal.util.Preconditions;
49 import com.android.internal.util.XmlUtils;
50 
51 import org.json.JSONArray;
52 import org.json.JSONException;
53 import org.json.JSONObject;
54 import org.xmlpull.v1.XmlPullParser;
55 import org.xmlpull.v1.XmlPullParserException;
56 import org.xmlpull.v1.XmlSerializer;
57 
58 import java.io.IOException;
59 import java.io.PrintWriter;
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Collection;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Map.Entry;
67 import java.util.Objects;
68 import java.util.concurrent.ConcurrentHashMap;
69 
70 public class RankingHelper implements RankingConfig {
71     private static final String TAG = "RankingHelper";
72 
73     private static final int XML_VERSION = 1;
74 
75     static final String TAG_RANKING = "ranking";
76     private static final String TAG_PACKAGE = "package";
77     private static final String TAG_CHANNEL = "channel";
78     private static final String TAG_GROUP = "channelGroup";
79 
80     private static final String ATT_VERSION = "version";
81     private static final String ATT_NAME = "name";
82     private static final String ATT_UID = "uid";
83     private static final String ATT_ID = "id";
84     private static final String ATT_PRIORITY = "priority";
85     private static final String ATT_VISIBILITY = "visibility";
86     private static final String ATT_IMPORTANCE = "importance";
87     private static final String ATT_SHOW_BADGE = "show_badge";
88     private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
89 
90     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
91     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
92     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
93     private static final boolean DEFAULT_SHOW_BADGE = true;
94     /**
95      * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
96      * fields.
97      */
98     private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
99 
100     /**
101      * All user-lockable fields for a given application.
102      */
103     @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
104     public @interface LockableAppFields {
105         int USER_LOCKED_IMPORTANCE = 0x00000001;
106     }
107 
108     private final NotificationSignalExtractor[] mSignalExtractors;
109     private final NotificationComparator mPreliminaryComparator;
110     private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
111 
112     private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
113     private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
114     private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
115 
116     private final Context mContext;
117     private final RankingHandler mRankingHandler;
118     private final PackageManager mPm;
119     private SparseBooleanArray mBadgingEnabled;
120 
121     private boolean mAreChannelsBypassingDnd;
122     private ZenModeHelper mZenModeHelper;
123 
RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames)124     public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
125             ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
126         mContext = context;
127         mRankingHandler = rankingHandler;
128         mPm = pm;
129         mZenModeHelper= zenHelper;
130 
131         mPreliminaryComparator = new NotificationComparator(mContext);
132 
133         updateBadgingEnabled();
134 
135         final int N = extractorNames.length;
136         mSignalExtractors = new NotificationSignalExtractor[N];
137         for (int i = 0; i < N; i++) {
138             try {
139                 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
140                 NotificationSignalExtractor extractor =
141                         (NotificationSignalExtractor) extractorClass.newInstance();
142                 extractor.initialize(mContext, usageStats);
143                 extractor.setConfig(this);
144                 extractor.setZenHelper(zenHelper);
145                 mSignalExtractors[i] = extractor;
146             } catch (ClassNotFoundException e) {
147                 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
148             } catch (InstantiationException e) {
149                 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
150             } catch (IllegalAccessException e) {
151                 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
152             }
153         }
154 
155         mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
156                 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
157         updateChannelsBypassingDnd();
158     }
159 
160     @SuppressWarnings("unchecked")
findExtractor(Class<T> extractorClass)161     public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
162         final int N = mSignalExtractors.length;
163         for (int i = 0; i < N; i++) {
164             final NotificationSignalExtractor extractor = mSignalExtractors[i];
165             if (extractorClass.equals(extractor.getClass())) {
166                 return (T) extractor;
167             }
168         }
169         return null;
170     }
171 
extractSignals(NotificationRecord r)172     public void extractSignals(NotificationRecord r) {
173         final int N = mSignalExtractors.length;
174         for (int i = 0; i < N; i++) {
175             NotificationSignalExtractor extractor = mSignalExtractors[i];
176             try {
177                 RankingReconsideration recon = extractor.process(r);
178                 if (recon != null) {
179                     mRankingHandler.requestReconsideration(recon);
180                 }
181             } catch (Throwable t) {
182                 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
183             }
184         }
185     }
186 
readXml(XmlPullParser parser, boolean forRestore)187     public void readXml(XmlPullParser parser, boolean forRestore)
188             throws XmlPullParserException, IOException {
189         int type = parser.getEventType();
190         if (type != XmlPullParser.START_TAG) return;
191         String tag = parser.getName();
192         if (!TAG_RANKING.equals(tag)) return;
193         // Clobber groups and channels with the xml, but don't delete other data that wasn't present
194         // at the time of serialization.
195         mRestoredWithoutUids.clear();
196         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
197             tag = parser.getName();
198             if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
199                 return;
200             }
201             if (type == XmlPullParser.START_TAG) {
202                 if (TAG_PACKAGE.equals(tag)) {
203                     int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
204                     String name = parser.getAttributeValue(null, ATT_NAME);
205                     if (!TextUtils.isEmpty(name)) {
206                         if (forRestore) {
207                             try {
208                                 //TODO: http://b/22388012
209                                 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
210                             } catch (NameNotFoundException e) {
211                                 // noop
212                             }
213                         }
214 
215                         Record r = getOrCreateRecord(name, uid,
216                                 XmlUtils.readIntAttribute(
217                                         parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
218                                 XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
219                                 XmlUtils.readIntAttribute(
220                                         parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
221                                 XmlUtils.readBooleanAttribute(
222                                         parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
223                         r.importance = XmlUtils.readIntAttribute(
224                                 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
225                         r.priority = XmlUtils.readIntAttribute(
226                                 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
227                         r.visibility = XmlUtils.readIntAttribute(
228                                 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
229                         r.showBadge = XmlUtils.readBooleanAttribute(
230                                 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
231                         r.lockedAppFields = XmlUtils.readIntAttribute(parser,
232                                 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
233 
234                         final int innerDepth = parser.getDepth();
235                         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
236                                 && (type != XmlPullParser.END_TAG
237                                 || parser.getDepth() > innerDepth)) {
238                             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
239                                 continue;
240                             }
241 
242                             String tagName = parser.getName();
243                             // Channel groups
244                             if (TAG_GROUP.equals(tagName)) {
245                                 String id = parser.getAttributeValue(null, ATT_ID);
246                                 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
247                                 if (!TextUtils.isEmpty(id)) {
248                                     NotificationChannelGroup group
249                                             = new NotificationChannelGroup(id, groupName);
250                                     group.populateFromXml(parser);
251                                     r.groups.put(id, group);
252                                 }
253                             }
254                             // Channels
255                             if (TAG_CHANNEL.equals(tagName)) {
256                                 String id = parser.getAttributeValue(null, ATT_ID);
257                                 String channelName = parser.getAttributeValue(null, ATT_NAME);
258                                 int channelImportance = XmlUtils.readIntAttribute(
259                                         parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
260                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
261                                     NotificationChannel channel = new NotificationChannel(id,
262                                             channelName, channelImportance);
263                                     if (forRestore) {
264                                         channel.populateFromXmlForRestore(parser, mContext);
265                                     } else {
266                                         channel.populateFromXml(parser);
267                                     }
268                                     r.channels.put(id, channel);
269                                 }
270                             }
271                         }
272 
273                         try {
274                             deleteDefaultChannelIfNeeded(r);
275                         } catch (NameNotFoundException e) {
276                             Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
277                         }
278                     }
279                 }
280             }
281         }
282         throw new IllegalStateException("Failed to reach END_DOCUMENT");
283     }
284 
recordKey(String pkg, int uid)285     private static String recordKey(String pkg, int uid) {
286         return pkg + "|" + uid;
287     }
288 
getRecord(String pkg, int uid)289     private Record getRecord(String pkg, int uid) {
290         final String key = recordKey(pkg, uid);
291         synchronized (mRecords) {
292             return mRecords.get(key);
293         }
294     }
295 
getOrCreateRecord(String pkg, int uid)296     private Record getOrCreateRecord(String pkg, int uid) {
297         return getOrCreateRecord(pkg, uid,
298                 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
299     }
300 
getOrCreateRecord(String pkg, int uid, int importance, int priority, int visibility, boolean showBadge)301     private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
302             int visibility, boolean showBadge) {
303         final String key = recordKey(pkg, uid);
304         synchronized (mRecords) {
305             Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
306                     key);
307             if (r == null) {
308                 r = new Record();
309                 r.pkg = pkg;
310                 r.uid = uid;
311                 r.importance = importance;
312                 r.priority = priority;
313                 r.visibility = visibility;
314                 r.showBadge = showBadge;
315 
316                 try {
317                     createDefaultChannelIfNeeded(r);
318                 } catch (NameNotFoundException e) {
319                     Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
320                 }
321 
322                 if (r.uid == Record.UNKNOWN_UID) {
323                     mRestoredWithoutUids.put(pkg, r);
324                 } else {
325                     mRecords.put(key, r);
326                 }
327             }
328             return r;
329         }
330     }
331 
shouldHaveDefaultChannel(Record r)332     private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
333         final int userId = UserHandle.getUserId(r.uid);
334         final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
335         if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
336             // O apps should not have the default channel.
337             return false;
338         }
339 
340         // Otherwise, this app should have the default channel.
341         return true;
342     }
343 
deleteDefaultChannelIfNeeded(Record r)344     private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
345         if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
346             // Not present
347             return;
348         }
349 
350         if (shouldHaveDefaultChannel(r)) {
351             // Keep the default channel until upgraded.
352             return;
353         }
354 
355         // Remove Default Channel.
356         r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
357     }
358 
createDefaultChannelIfNeeded(Record r)359     private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
360         if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
361             r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
362                     mContext.getString(R.string.default_notification_channel_label));
363             return;
364         }
365 
366         if (!shouldHaveDefaultChannel(r)) {
367             // Keep the default channel until upgraded.
368             return;
369         }
370 
371         // Create Default Channel
372         NotificationChannel channel;
373         channel = new NotificationChannel(
374                 NotificationChannel.DEFAULT_CHANNEL_ID,
375                 mContext.getString(R.string.default_notification_channel_label),
376                 r.importance);
377         channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
378         channel.setLockscreenVisibility(r.visibility);
379         if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
380             channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
381         }
382         if (r.priority != DEFAULT_PRIORITY) {
383             channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
384         }
385         if (r.visibility != DEFAULT_VISIBILITY) {
386             channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
387         }
388         r.channels.put(channel.getId(), channel);
389     }
390 
writeXml(XmlSerializer out, boolean forBackup)391     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
392         out.startTag(null, TAG_RANKING);
393         out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
394 
395         synchronized (mRecords) {
396             final int N = mRecords.size();
397             for (int i = 0; i < N; i++) {
398                 final Record r = mRecords.valueAt(i);
399                 //TODO: http://b/22388012
400                 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
401                     continue;
402                 }
403                 final boolean hasNonDefaultSettings =
404                         r.importance != DEFAULT_IMPORTANCE
405                             || r.priority != DEFAULT_PRIORITY
406                             || r.visibility != DEFAULT_VISIBILITY
407                             || r.showBadge != DEFAULT_SHOW_BADGE
408                             || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
409                             || r.channels.size() > 0
410                             || r.groups.size() > 0;
411                 if (hasNonDefaultSettings) {
412                     out.startTag(null, TAG_PACKAGE);
413                     out.attribute(null, ATT_NAME, r.pkg);
414                     if (r.importance != DEFAULT_IMPORTANCE) {
415                         out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
416                     }
417                     if (r.priority != DEFAULT_PRIORITY) {
418                         out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
419                     }
420                     if (r.visibility != DEFAULT_VISIBILITY) {
421                         out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
422                     }
423                     out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
424                     out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
425                             Integer.toString(r.lockedAppFields));
426 
427                     if (!forBackup) {
428                         out.attribute(null, ATT_UID, Integer.toString(r.uid));
429                     }
430 
431                     for (NotificationChannelGroup group : r.groups.values()) {
432                         group.writeXml(out);
433                     }
434 
435                     for (NotificationChannel channel : r.channels.values()) {
436                         if (forBackup) {
437                             if (!channel.isDeleted()) {
438                                 channel.writeXmlForBackup(out, mContext);
439                             }
440                         } else {
441                             channel.writeXml(out);
442                         }
443                     }
444 
445                     out.endTag(null, TAG_PACKAGE);
446                 }
447             }
448         }
449         out.endTag(null, TAG_RANKING);
450     }
451 
updateConfig()452     private void updateConfig() {
453         final int N = mSignalExtractors.length;
454         for (int i = 0; i < N; i++) {
455             mSignalExtractors[i].setConfig(this);
456         }
457         mRankingHandler.requestSort();
458     }
459 
sort(ArrayList<NotificationRecord> notificationList)460     public void sort(ArrayList<NotificationRecord> notificationList) {
461         final int N = notificationList.size();
462         // clear global sort keys
463         for (int i = N - 1; i >= 0; i--) {
464             notificationList.get(i).setGlobalSortKey(null);
465         }
466 
467         // rank each record individually
468         Collections.sort(notificationList, mPreliminaryComparator);
469 
470         synchronized (mProxyByGroupTmp) {
471             // record individual ranking result and nominate proxies for each group
472             for (int i = N - 1; i >= 0; i--) {
473                 final NotificationRecord record = notificationList.get(i);
474                 record.setAuthoritativeRank(i);
475                 final String groupKey = record.getGroupKey();
476                 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
477                 if (existingProxy == null) {
478                     mProxyByGroupTmp.put(groupKey, record);
479                 }
480             }
481             // assign global sort key:
482             //   is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
483             for (int i = 0; i < N; i++) {
484                 final NotificationRecord record = notificationList.get(i);
485                 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
486                 String groupSortKey = record.getNotification().getSortKey();
487 
488                 // We need to make sure the developer provided group sort key (gsk) is handled
489                 // correctly:
490                 //   gsk="" < gsk=non-null-string < gsk=null
491                 //
492                 // We enforce this by using different prefixes for these three cases.
493                 String groupSortKeyPortion;
494                 if (groupSortKey == null) {
495                     groupSortKeyPortion = "nsk";
496                 } else if (groupSortKey.equals("")) {
497                     groupSortKeyPortion = "esk";
498                 } else {
499                     groupSortKeyPortion = "gsk=" + groupSortKey;
500                 }
501 
502                 boolean isGroupSummary = record.getNotification().isGroupSummary();
503                 record.setGlobalSortKey(
504                         String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
505                         record.isRecentlyIntrusive()
506                                 && record.getImportance() > NotificationManager.IMPORTANCE_MIN
507                                 ? '0' : '1',
508                         groupProxy.getAuthoritativeRank(),
509                         isGroupSummary ? '0' : '1',
510                         groupSortKeyPortion,
511                         record.getAuthoritativeRank()));
512             }
513             mProxyByGroupTmp.clear();
514         }
515 
516         // Do a second ranking pass, using group proxies
517         Collections.sort(notificationList, mFinalComparator);
518     }
519 
indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target)520     public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
521         return Collections.binarySearch(notificationList, target, mFinalComparator);
522     }
523 
524     /**
525      * Gets importance.
526      */
527     @Override
getImportance(String packageName, int uid)528     public int getImportance(String packageName, int uid) {
529         return getOrCreateRecord(packageName, uid).importance;
530     }
531 
532 
533     /**
534      * Returns whether the importance of the corresponding notification is user-locked and shouldn't
535      * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
536      * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
537      */
getIsAppImportanceLocked(String packageName, int uid)538     public boolean getIsAppImportanceLocked(String packageName, int uid) {
539         int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
540         return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
541     }
542 
543     @Override
canShowBadge(String packageName, int uid)544     public boolean canShowBadge(String packageName, int uid) {
545         return getOrCreateRecord(packageName, uid).showBadge;
546     }
547 
548     @Override
setShowBadge(String packageName, int uid, boolean showBadge)549     public void setShowBadge(String packageName, int uid, boolean showBadge) {
550         getOrCreateRecord(packageName, uid).showBadge = showBadge;
551         updateConfig();
552     }
553 
554     @Override
isGroupBlocked(String packageName, int uid, String groupId)555     public boolean isGroupBlocked(String packageName, int uid, String groupId) {
556         if (groupId == null) {
557             return false;
558         }
559         Record r = getOrCreateRecord(packageName, uid);
560         NotificationChannelGroup group = r.groups.get(groupId);
561         if (group == null) {
562             return false;
563         }
564         return group.isBlocked();
565     }
566 
getPackagePriority(String pkg, int uid)567     int getPackagePriority(String pkg, int uid) {
568         return getOrCreateRecord(pkg, uid).priority;
569     }
570 
getPackageVisibility(String pkg, int uid)571     int getPackageVisibility(String pkg, int uid) {
572         return getOrCreateRecord(pkg, uid).visibility;
573     }
574 
575     @Override
createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group, boolean fromTargetApp)576     public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
577             boolean fromTargetApp) {
578         Preconditions.checkNotNull(pkg);
579         Preconditions.checkNotNull(group);
580         Preconditions.checkNotNull(group.getId());
581         Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
582         Record r = getOrCreateRecord(pkg, uid);
583         if (r == null) {
584             throw new IllegalArgumentException("Invalid package");
585         }
586         final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
587         if (!group.equals(oldGroup)) {
588             // will log for new entries as well as name/description changes
589             MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
590         }
591         if (oldGroup != null) {
592             group.setChannels(oldGroup.getChannels());
593 
594             if (fromTargetApp) {
595                 group.setBlocked(oldGroup.isBlocked());
596             }
597         }
598         r.groups.put(group.getId(), group);
599     }
600 
601     @Override
createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp, boolean hasDndAccess)602     public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
603             boolean fromTargetApp, boolean hasDndAccess) {
604         Preconditions.checkNotNull(pkg);
605         Preconditions.checkNotNull(channel);
606         Preconditions.checkNotNull(channel.getId());
607         Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
608         Record r = getOrCreateRecord(pkg, uid);
609         if (r == null) {
610             throw new IllegalArgumentException("Invalid package");
611         }
612         if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
613             throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
614         }
615         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
616             throw new IllegalArgumentException("Reserved id");
617         }
618         NotificationChannel existing = r.channels.get(channel.getId());
619         // Keep most of the existing settings
620         if (existing != null && fromTargetApp) {
621             if (existing.isDeleted()) {
622                 existing.setDeleted(false);
623 
624                 // log a resurrected channel as if it's new again
625                 MetricsLogger.action(getChannelLog(channel, pkg).setType(
626                         MetricsProto.MetricsEvent.TYPE_OPEN));
627             }
628 
629             existing.setName(channel.getName().toString());
630             existing.setDescription(channel.getDescription());
631             existing.setBlockableSystem(channel.isBlockableSystem());
632             if (existing.getGroup() == null) {
633                 existing.setGroup(channel.getGroup());
634             }
635 
636             // Apps are allowed to downgrade channel importance if the user has not changed any
637             // fields on this channel yet.
638             if (existing.getUserLockedFields() == 0 &&
639                     channel.getImportance() < existing.getImportance()) {
640                 existing.setImportance(channel.getImportance());
641             }
642 
643             // system apps and dnd access apps can bypass dnd if the user hasn't changed any
644             // fields on the channel yet
645             if (existing.getUserLockedFields() == 0 && hasDndAccess) {
646                 boolean bypassDnd = channel.canBypassDnd();
647                 existing.setBypassDnd(bypassDnd);
648 
649                 if (bypassDnd != mAreChannelsBypassingDnd) {
650                     updateChannelsBypassingDnd();
651                 }
652             }
653 
654             updateConfig();
655             return;
656         }
657         if (channel.getImportance() < IMPORTANCE_NONE
658                 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
659             throw new IllegalArgumentException("Invalid importance level");
660         }
661 
662         // Reset fields that apps aren't allowed to set.
663         if (fromTargetApp && !hasDndAccess) {
664             channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
665         }
666         if (fromTargetApp) {
667             channel.setLockscreenVisibility(r.visibility);
668         }
669         clearLockedFields(channel);
670         if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
671             channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
672         }
673         if (!r.showBadge) {
674             channel.setShowBadge(false);
675         }
676 
677         r.channels.put(channel.getId(), channel);
678         if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
679             updateChannelsBypassingDnd();
680         }
681         MetricsLogger.action(getChannelLog(channel, pkg).setType(
682                 MetricsProto.MetricsEvent.TYPE_OPEN));
683     }
684 
clearLockedFields(NotificationChannel channel)685     void clearLockedFields(NotificationChannel channel) {
686         channel.unlockFields(channel.getUserLockedFields());
687     }
688 
689     @Override
updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel, boolean fromUser)690     public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
691             boolean fromUser) {
692         Preconditions.checkNotNull(updatedChannel);
693         Preconditions.checkNotNull(updatedChannel.getId());
694         Record r = getOrCreateRecord(pkg, uid);
695         if (r == null) {
696             throw new IllegalArgumentException("Invalid package");
697         }
698         NotificationChannel channel = r.channels.get(updatedChannel.getId());
699         if (channel == null || channel.isDeleted()) {
700             throw new IllegalArgumentException("Channel does not exist");
701         }
702         if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
703             updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
704         }
705         if (!fromUser) {
706             updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
707         }
708         if (fromUser) {
709             updatedChannel.lockFields(channel.getUserLockedFields());
710             lockFieldsForUpdate(channel, updatedChannel);
711         }
712         r.channels.put(updatedChannel.getId(), updatedChannel);
713 
714         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
715             // copy settings to app level so they are inherited by new channels
716             // when the app migrates
717             r.importance = updatedChannel.getImportance();
718             r.priority = updatedChannel.canBypassDnd()
719                     ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
720             r.visibility = updatedChannel.getLockscreenVisibility();
721             r.showBadge = updatedChannel.canShowBadge();
722         }
723 
724         if (!channel.equals(updatedChannel)) {
725             // only log if there are real changes
726             MetricsLogger.action(getChannelLog(updatedChannel, pkg));
727         }
728 
729         if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
730             updateChannelsBypassingDnd();
731         }
732         updateConfig();
733     }
734 
735     @Override
getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted)736     public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
737             boolean includeDeleted) {
738         Preconditions.checkNotNull(pkg);
739         Record r = getOrCreateRecord(pkg, uid);
740         if (r == null) {
741             return null;
742         }
743         if (channelId == null) {
744             channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
745         }
746         final NotificationChannel nc = r.channels.get(channelId);
747         if (nc != null && (includeDeleted || !nc.isDeleted())) {
748             return nc;
749         }
750         return null;
751     }
752 
753     @Override
deleteNotificationChannel(String pkg, int uid, String channelId)754     public void deleteNotificationChannel(String pkg, int uid, String channelId) {
755         Record r = getRecord(pkg, uid);
756         if (r == null) {
757             return;
758         }
759         NotificationChannel channel = r.channels.get(channelId);
760         if (channel != null) {
761             channel.setDeleted(true);
762             LogMaker lm = getChannelLog(channel, pkg);
763             lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
764             MetricsLogger.action(lm);
765 
766             if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
767                 updateChannelsBypassingDnd();
768             }
769         }
770     }
771 
772     @Override
773     @VisibleForTesting
permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId)774     public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
775         Preconditions.checkNotNull(pkg);
776         Preconditions.checkNotNull(channelId);
777         Record r = getRecord(pkg, uid);
778         if (r == null) {
779             return;
780         }
781         r.channels.remove(channelId);
782     }
783 
784     @Override
permanentlyDeleteNotificationChannels(String pkg, int uid)785     public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
786         Preconditions.checkNotNull(pkg);
787         Record r = getRecord(pkg, uid);
788         if (r == null) {
789             return;
790         }
791         int N = r.channels.size() - 1;
792         for (int i = N; i >= 0; i--) {
793             String key = r.channels.keyAt(i);
794             if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
795                 r.channels.remove(key);
796             }
797         }
798     }
799 
getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted)800     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
801             int uid, String groupId, boolean includeDeleted) {
802         Preconditions.checkNotNull(pkg);
803         Record r = getRecord(pkg, uid);
804         if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
805             return null;
806         }
807         NotificationChannelGroup group = r.groups.get(groupId).clone();
808         group.setChannels(new ArrayList<>());
809         int N = r.channels.size();
810         for (int i = 0; i < N; i++) {
811             final NotificationChannel nc = r.channels.valueAt(i);
812             if (includeDeleted || !nc.isDeleted()) {
813                 if (groupId.equals(nc.getGroup())) {
814                     group.addChannel(nc);
815                 }
816             }
817         }
818         return group;
819     }
820 
getNotificationChannelGroup(String groupId, String pkg, int uid)821     public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
822             int uid) {
823         Preconditions.checkNotNull(pkg);
824         Record r = getRecord(pkg, uid);
825         if (r == null) {
826             return null;
827         }
828         return r.groups.get(groupId);
829     }
830 
831     @Override
getNotificationChannelGroups(String pkg, int uid, boolean includeDeleted, boolean includeNonGrouped)832     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
833             int uid, boolean includeDeleted, boolean includeNonGrouped) {
834         Preconditions.checkNotNull(pkg);
835         Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
836         Record r = getRecord(pkg, uid);
837         if (r == null) {
838             return ParceledListSlice.emptyList();
839         }
840         NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
841         int N = r.channels.size();
842         for (int i = 0; i < N; i++) {
843             final NotificationChannel nc = r.channels.valueAt(i);
844             if (includeDeleted || !nc.isDeleted()) {
845                 if (nc.getGroup() != null) {
846                     if (r.groups.get(nc.getGroup()) != null) {
847                         NotificationChannelGroup ncg = groups.get(nc.getGroup());
848                         if (ncg == null) {
849                             ncg = r.groups.get(nc.getGroup()).clone();
850                             ncg.setChannels(new ArrayList<>());
851                             groups.put(nc.getGroup(), ncg);
852 
853                         }
854                         ncg.addChannel(nc);
855                     }
856                 } else {
857                     nonGrouped.addChannel(nc);
858                 }
859             }
860         }
861         if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
862             groups.put(null, nonGrouped);
863         }
864         return new ParceledListSlice<>(new ArrayList<>(groups.values()));
865     }
866 
deleteNotificationChannelGroup(String pkg, int uid, String groupId)867     public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
868             String groupId) {
869         List<NotificationChannel> deletedChannels = new ArrayList<>();
870         Record r = getRecord(pkg, uid);
871         if (r == null || TextUtils.isEmpty(groupId)) {
872             return deletedChannels;
873         }
874 
875         r.groups.remove(groupId);
876 
877         int N = r.channels.size();
878         for (int i = 0; i < N; i++) {
879             final NotificationChannel nc = r.channels.valueAt(i);
880             if (groupId.equals(nc.getGroup())) {
881                 nc.setDeleted(true);
882                 deletedChannels.add(nc);
883             }
884         }
885         return deletedChannels;
886     }
887 
888     @Override
getNotificationChannelGroups(String pkg, int uid)889     public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
890             int uid) {
891         Record r = getRecord(pkg, uid);
892         if (r == null) {
893             return new ArrayList<>();
894         }
895         return r.groups.values();
896     }
897 
898     @Override
getNotificationChannels(String pkg, int uid, boolean includeDeleted)899     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
900             boolean includeDeleted) {
901         Preconditions.checkNotNull(pkg);
902         List<NotificationChannel> channels = new ArrayList<>();
903         Record r = getRecord(pkg, uid);
904         if (r == null) {
905             return ParceledListSlice.emptyList();
906         }
907         int N = r.channels.size();
908         for (int i = 0; i < N; i++) {
909             final NotificationChannel nc = r.channels.valueAt(i);
910             if (includeDeleted || !nc.isDeleted()) {
911                 channels.add(nc);
912             }
913         }
914         return new ParceledListSlice<>(channels);
915     }
916 
917     /**
918      * True for pre-O apps that only have the default channel, or pre O apps that have no
919      * channels yet. This method will create the default channel for pre-O apps that don't have it.
920      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
921      * upgrades.
922      */
onlyHasDefaultChannel(String pkg, int uid)923     public boolean onlyHasDefaultChannel(String pkg, int uid) {
924         Record r = getOrCreateRecord(pkg, uid);
925         if (r.channels.size() == 1
926                 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
927             return true;
928         }
929         return false;
930     }
931 
getDeletedChannelCount(String pkg, int uid)932     public int getDeletedChannelCount(String pkg, int uid) {
933         Preconditions.checkNotNull(pkg);
934         int deletedCount = 0;
935         Record r = getRecord(pkg, uid);
936         if (r == null) {
937             return deletedCount;
938         }
939         int N = r.channels.size();
940         for (int i = 0; i < N; i++) {
941             final NotificationChannel nc = r.channels.valueAt(i);
942             if (nc.isDeleted()) {
943                 deletedCount++;
944             }
945         }
946         return deletedCount;
947     }
948 
getBlockedChannelCount(String pkg, int uid)949     public int getBlockedChannelCount(String pkg, int uid) {
950         Preconditions.checkNotNull(pkg);
951         int blockedCount = 0;
952         Record r = getRecord(pkg, uid);
953         if (r == null) {
954             return blockedCount;
955         }
956         int N = r.channels.size();
957         for (int i = 0; i < N; i++) {
958             final NotificationChannel nc = r.channels.valueAt(i);
959             if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
960                 blockedCount++;
961             }
962         }
963         return blockedCount;
964     }
965 
getBlockedAppCount(int userId)966     public int getBlockedAppCount(int userId) {
967         int count = 0;
968         synchronized (mRecords) {
969             final int N = mRecords.size();
970             for (int i = 0; i < N; i++) {
971                 final Record r = mRecords.valueAt(i);
972                 if (userId == UserHandle.getUserId(r.uid)
973                         && r.importance == IMPORTANCE_NONE) {
974                     count++;
975                 }
976             }
977         }
978         return count;
979     }
980 
updateChannelsBypassingDnd()981     public void updateChannelsBypassingDnd() {
982         synchronized (mRecords) {
983             final int numRecords = mRecords.size();
984             for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
985                 final Record r = mRecords.valueAt(recordIndex);
986                 final int numChannels = r.channels.size();
987 
988                 for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
989                     NotificationChannel channel = r.channels.valueAt(channelIndex);
990                     if (!channel.isDeleted() && channel.canBypassDnd()) {
991                         if (!mAreChannelsBypassingDnd) {
992                             mAreChannelsBypassingDnd = true;
993                             updateZenPolicy(true);
994                         }
995                         return;
996                     }
997                 }
998             }
999         }
1000 
1001         if (mAreChannelsBypassingDnd) {
1002             mAreChannelsBypassingDnd = false;
1003             updateZenPolicy(false);
1004         }
1005     }
1006 
updateZenPolicy(boolean areChannelsBypassingDnd)1007     public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1008         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1009         mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1010                 policy.priorityCategories, policy.priorityCallSenders,
1011                 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1012                 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1013                         : 0)));
1014     }
1015 
areChannelsBypassingDnd()1016     public boolean areChannelsBypassingDnd() {
1017         return mAreChannelsBypassingDnd;
1018     }
1019 
1020     /**
1021      * Sets importance.
1022      */
1023     @Override
setImportance(String pkgName, int uid, int importance)1024     public void setImportance(String pkgName, int uid, int importance) {
1025         getOrCreateRecord(pkgName, uid).importance = importance;
1026         updateConfig();
1027     }
1028 
setEnabled(String packageName, int uid, boolean enabled)1029     public void setEnabled(String packageName, int uid, boolean enabled) {
1030         boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1031         if (wasEnabled == enabled) {
1032             return;
1033         }
1034         setImportance(packageName, uid,
1035                 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1036     }
1037 
1038     /**
1039      * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1040      * {@code uid}, have their importance locked by the user. Locked notifications don't get
1041      * considered for sentiment adjustments (and thus never show a blocking helper).
1042      */
setAppImportanceLocked(String packageName, int uid)1043     public void setAppImportanceLocked(String packageName, int uid) {
1044         Record record = getOrCreateRecord(packageName, uid);
1045         if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1046             return;
1047         }
1048 
1049         record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1050         updateConfig();
1051     }
1052 
1053     @VisibleForTesting
lockFieldsForUpdate(NotificationChannel original, NotificationChannel update)1054     void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1055         if (original.canBypassDnd() != update.canBypassDnd()) {
1056             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1057         }
1058         if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1059             update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1060         }
1061         if (original.getImportance() != update.getImportance()) {
1062             update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1063         }
1064         if (original.shouldShowLights() != update.shouldShowLights()
1065                 || original.getLightColor() != update.getLightColor()) {
1066             update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1067         }
1068         if (!Objects.equals(original.getSound(), update.getSound())) {
1069             update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1070         }
1071         if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1072                 || original.shouldVibrate() != update.shouldVibrate()) {
1073             update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1074         }
1075         if (original.canShowBadge() != update.canShowBadge()) {
1076             update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1077         }
1078     }
1079 
dump(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter)1080     public void dump(PrintWriter pw, String prefix,
1081             @NonNull NotificationManagerService.DumpFilter filter) {
1082         final int N = mSignalExtractors.length;
1083         pw.print(prefix);
1084         pw.print("mSignalExtractors.length = ");
1085         pw.println(N);
1086         for (int i = 0; i < N; i++) {
1087             pw.print(prefix);
1088             pw.print("  ");
1089             pw.println(mSignalExtractors[i].getClass().getSimpleName());
1090         }
1091 
1092         pw.print(prefix);
1093         pw.println("per-package config:");
1094 
1095         pw.println("Records:");
1096         synchronized (mRecords) {
1097             dumpRecords(pw, prefix, filter, mRecords);
1098         }
1099         pw.println("Restored without uid:");
1100         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
1101     }
1102 
dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter)1103     public void dump(ProtoOutputStream proto,
1104             @NonNull NotificationManagerService.DumpFilter filter) {
1105         final int N = mSignalExtractors.length;
1106         for (int i = 0; i < N; i++) {
1107             proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
1108                 mSignalExtractors[i].getClass().getSimpleName());
1109         }
1110         synchronized (mRecords) {
1111             dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
1112         }
1113         dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1114             mRestoredWithoutUids);
1115     }
1116 
dumpRecords(ProtoOutputStream proto, long fieldId, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records)1117     private static void dumpRecords(ProtoOutputStream proto, long fieldId,
1118             @NonNull NotificationManagerService.DumpFilter filter,
1119             ArrayMap<String, Record> records) {
1120         final int N = records.size();
1121         long fToken;
1122         for (int i = 0; i < N; i++) {
1123             final Record r = records.valueAt(i);
1124             if (filter.matches(r.pkg)) {
1125                 fToken = proto.start(fieldId);
1126 
1127                 proto.write(RecordProto.PACKAGE, r.pkg);
1128                 proto.write(RecordProto.UID, r.uid);
1129                 proto.write(RecordProto.IMPORTANCE, r.importance);
1130                 proto.write(RecordProto.PRIORITY, r.priority);
1131                 proto.write(RecordProto.VISIBILITY, r.visibility);
1132                 proto.write(RecordProto.SHOW_BADGE, r.showBadge);
1133 
1134                 for (NotificationChannel channel : r.channels.values()) {
1135                     channel.writeToProto(proto, RecordProto.CHANNELS);
1136                 }
1137                 for (NotificationChannelGroup group : r.groups.values()) {
1138                     group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
1139                 }
1140 
1141                 proto.end(fToken);
1142             }
1143         }
1144     }
1145 
dumpRecords(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records)1146     private static void dumpRecords(PrintWriter pw, String prefix,
1147             @NonNull NotificationManagerService.DumpFilter filter,
1148             ArrayMap<String, Record> records) {
1149         final int N = records.size();
1150         for (int i = 0; i < N; i++) {
1151             final Record r = records.valueAt(i);
1152             if (filter.matches(r.pkg)) {
1153                 pw.print(prefix);
1154                 pw.print("  AppSettings: ");
1155                 pw.print(r.pkg);
1156                 pw.print(" (");
1157                 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
1158                 pw.print(')');
1159                 if (r.importance != DEFAULT_IMPORTANCE) {
1160                     pw.print(" importance=");
1161                     pw.print(Ranking.importanceToString(r.importance));
1162                 }
1163                 if (r.priority != DEFAULT_PRIORITY) {
1164                     pw.print(" priority=");
1165                     pw.print(Notification.priorityToString(r.priority));
1166                 }
1167                 if (r.visibility != DEFAULT_VISIBILITY) {
1168                     pw.print(" visibility=");
1169                     pw.print(Notification.visibilityToString(r.visibility));
1170                 }
1171                 pw.print(" showBadge=");
1172                 pw.print(Boolean.toString(r.showBadge));
1173                 pw.println();
1174                 for (NotificationChannel channel : r.channels.values()) {
1175                     pw.print(prefix);
1176                     pw.print("  ");
1177                     pw.print("  ");
1178                     pw.println(channel);
1179                 }
1180                 for (NotificationChannelGroup group : r.groups.values()) {
1181                     pw.print(prefix);
1182                     pw.print("  ");
1183                     pw.print("  ");
1184                     pw.println(group);
1185                 }
1186             }
1187         }
1188     }
1189 
dumpJson(NotificationManagerService.DumpFilter filter)1190     public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1191         JSONObject ranking = new JSONObject();
1192         JSONArray records = new JSONArray();
1193         try {
1194             ranking.put("noUid", mRestoredWithoutUids.size());
1195         } catch (JSONException e) {
1196            // pass
1197         }
1198         synchronized (mRecords) {
1199             final int N = mRecords.size();
1200             for (int i = 0; i < N; i++) {
1201                 final Record r = mRecords.valueAt(i);
1202                 if (filter == null || filter.matches(r.pkg)) {
1203                     JSONObject record = new JSONObject();
1204                     try {
1205                         record.put("userId", UserHandle.getUserId(r.uid));
1206                         record.put("packageName", r.pkg);
1207                         if (r.importance != DEFAULT_IMPORTANCE) {
1208                             record.put("importance", Ranking.importanceToString(r.importance));
1209                         }
1210                         if (r.priority != DEFAULT_PRIORITY) {
1211                             record.put("priority", Notification.priorityToString(r.priority));
1212                         }
1213                         if (r.visibility != DEFAULT_VISIBILITY) {
1214                             record.put("visibility", Notification.visibilityToString(r.visibility));
1215                         }
1216                         if (r.showBadge != DEFAULT_SHOW_BADGE) {
1217                             record.put("showBadge", Boolean.valueOf(r.showBadge));
1218                         }
1219                         JSONArray channels = new JSONArray();
1220                         for (NotificationChannel channel : r.channels.values()) {
1221                             channels.put(channel.toJson());
1222                         }
1223                         record.put("channels", channels);
1224                         JSONArray groups = new JSONArray();
1225                         for (NotificationChannelGroup group : r.groups.values()) {
1226                             groups.put(group.toJson());
1227                         }
1228                         record.put("groups", groups);
1229                     } catch (JSONException e) {
1230                         // pass
1231                     }
1232                     records.put(record);
1233                 }
1234             }
1235         }
1236         try {
1237             ranking.put("records", records);
1238         } catch (JSONException e) {
1239             // pass
1240         }
1241         return ranking;
1242     }
1243 
1244     /**
1245      * Dump only the ban information as structured JSON for the stats collector.
1246      *
1247      * This is intentionally redundant with {#link dumpJson} because the old
1248      * scraper will expect this format.
1249      *
1250      * @param filter
1251      * @return
1252      */
dumpBansJson(NotificationManagerService.DumpFilter filter)1253     public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1254         JSONArray bans = new JSONArray();
1255         Map<Integer, String> packageBans = getPackageBans();
1256         for(Entry<Integer, String> ban : packageBans.entrySet()) {
1257             final int userId = UserHandle.getUserId(ban.getKey());
1258             final String packageName = ban.getValue();
1259             if (filter == null || filter.matches(packageName)) {
1260                 JSONObject banJson = new JSONObject();
1261                 try {
1262                     banJson.put("userId", userId);
1263                     banJson.put("packageName", packageName);
1264                 } catch (JSONException e) {
1265                     e.printStackTrace();
1266                 }
1267                 bans.put(banJson);
1268             }
1269         }
1270         return bans;
1271     }
1272 
getPackageBans()1273     public Map<Integer, String> getPackageBans() {
1274         synchronized (mRecords) {
1275             final int N = mRecords.size();
1276             ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1277             for (int i = 0; i < N; i++) {
1278                 final Record r = mRecords.valueAt(i);
1279                 if (r.importance == IMPORTANCE_NONE) {
1280                     packageBans.put(r.uid, r.pkg);
1281                 }
1282             }
1283 
1284             return packageBans;
1285         }
1286     }
1287 
1288     /**
1289      * Dump only the channel information as structured JSON for the stats collector.
1290      *
1291      * This is intentionally redundant with {#link dumpJson} because the old
1292      * scraper will expect this format.
1293      *
1294      * @param filter
1295      * @return
1296      */
dumpChannelsJson(NotificationManagerService.DumpFilter filter)1297     public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1298         JSONArray channels = new JSONArray();
1299         Map<String, Integer> packageChannels = getPackageChannels();
1300         for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1301             final String packageName = channelCount.getKey();
1302             if (filter == null || filter.matches(packageName)) {
1303                 JSONObject channelCountJson = new JSONObject();
1304                 try {
1305                     channelCountJson.put("packageName", packageName);
1306                     channelCountJson.put("channelCount", channelCount.getValue());
1307                 } catch (JSONException e) {
1308                     e.printStackTrace();
1309                 }
1310                 channels.put(channelCountJson);
1311             }
1312         }
1313         return channels;
1314     }
1315 
getPackageChannels()1316     private Map<String, Integer> getPackageChannels() {
1317         ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
1318         synchronized (mRecords) {
1319             for (int i = 0; i < mRecords.size(); i++) {
1320                 final Record r = mRecords.valueAt(i);
1321                 int channelCount = 0;
1322                 for (int j = 0; j < r.channels.size(); j++) {
1323                     if (!r.channels.valueAt(j).isDeleted()) {
1324                         channelCount++;
1325                     }
1326                 }
1327                 packageChannels.put(r.pkg, channelCount);
1328             }
1329         }
1330         return packageChannels;
1331     }
1332 
onUserRemoved(int userId)1333     public void onUserRemoved(int userId) {
1334         synchronized (mRecords) {
1335             int N = mRecords.size();
1336             for (int i = N - 1; i >= 0 ; i--) {
1337                 Record record = mRecords.valueAt(i);
1338                 if (UserHandle.getUserId(record.uid) == userId) {
1339                     mRecords.removeAt(i);
1340                 }
1341             }
1342         }
1343     }
1344 
onLocaleChanged(Context context, int userId)1345     protected void onLocaleChanged(Context context, int userId) {
1346         synchronized (mRecords) {
1347             int N = mRecords.size();
1348             for (int i = 0; i < N; i++) {
1349                 Record record = mRecords.valueAt(i);
1350                 if (UserHandle.getUserId(record.uid) == userId) {
1351                     if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1352                         record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1353                                 context.getResources().getString(
1354                                         R.string.default_notification_channel_label));
1355                     }
1356                 }
1357             }
1358         }
1359     }
1360 
onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList, int[] uidList)1361     public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1362             int[] uidList) {
1363         if (pkgList == null || pkgList.length == 0) {
1364             return; // nothing to do
1365         }
1366         boolean updated = false;
1367         if (removingPackage) {
1368             // Remove notification settings for uninstalled package
1369             int size = Math.min(pkgList.length, uidList.length);
1370             for (int i = 0; i < size; i++) {
1371                 final String pkg = pkgList[i];
1372                 final int uid = uidList[i];
1373                 synchronized (mRecords) {
1374                     mRecords.remove(recordKey(pkg, uid));
1375                 }
1376                 mRestoredWithoutUids.remove(pkg);
1377                 updated = true;
1378             }
1379         } else {
1380             for (String pkg : pkgList) {
1381                 // Package install
1382                 final Record r = mRestoredWithoutUids.get(pkg);
1383                 if (r != null) {
1384                     try {
1385                         r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1386                         mRestoredWithoutUids.remove(pkg);
1387                         synchronized (mRecords) {
1388                             mRecords.put(recordKey(r.pkg, r.uid), r);
1389                         }
1390                         updated = true;
1391                     } catch (NameNotFoundException e) {
1392                         // noop
1393                     }
1394                 }
1395                 // Package upgrade
1396                 try {
1397                     Record fullRecord = getRecord(pkg,
1398                             mPm.getPackageUidAsUser(pkg, changeUserId));
1399                     if (fullRecord != null) {
1400                         createDefaultChannelIfNeeded(fullRecord);
1401                         deleteDefaultChannelIfNeeded(fullRecord);
1402                     }
1403                 } catch (NameNotFoundException e) {}
1404             }
1405         }
1406 
1407         if (updated) {
1408             updateConfig();
1409         }
1410     }
1411 
getChannelLog(NotificationChannel channel, String pkg)1412     private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1413         return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
1414                 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1415                 .setPackageName(pkg)
1416                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
1417                         channel.getId())
1418                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1419                         channel.getImportance());
1420     }
1421 
getChannelGroupLog(String groupId, String pkg)1422     private LogMaker getChannelGroupLog(String groupId, String pkg) {
1423         return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
1424                 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1425                 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1426                         groupId)
1427                 .setPackageName(pkg);
1428     }
1429 
updateBadgingEnabled()1430     public void updateBadgingEnabled() {
1431         if (mBadgingEnabled == null) {
1432             mBadgingEnabled = new SparseBooleanArray();
1433         }
1434         boolean changed = false;
1435         // update the cached values
1436         for (int index = 0; index < mBadgingEnabled.size(); index++) {
1437             int userId = mBadgingEnabled.keyAt(index);
1438             final boolean oldValue = mBadgingEnabled.get(userId);
1439             final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
1440                     Secure.NOTIFICATION_BADGING,
1441                     DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1442             mBadgingEnabled.put(userId, newValue);
1443             changed |= oldValue != newValue;
1444         }
1445         if (changed) {
1446             updateConfig();
1447         }
1448     }
1449 
badgingEnabled(UserHandle userHandle)1450     public boolean badgingEnabled(UserHandle userHandle) {
1451         int userId = userHandle.getIdentifier();
1452         if (userId == UserHandle.USER_ALL) {
1453             return false;
1454         }
1455         if (mBadgingEnabled.indexOfKey(userId) < 0) {
1456             mBadgingEnabled.put(userId,
1457                     Secure.getIntForUser(mContext.getContentResolver(),
1458                             Secure.NOTIFICATION_BADGING,
1459                             DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1460         }
1461         return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1462     }
1463 
1464 
1465     private static class Record {
1466         static int UNKNOWN_UID = UserHandle.USER_NULL;
1467 
1468         String pkg;
1469         int uid = UNKNOWN_UID;
1470         int importance = DEFAULT_IMPORTANCE;
1471         int priority = DEFAULT_PRIORITY;
1472         int visibility = DEFAULT_VISIBILITY;
1473         boolean showBadge = DEFAULT_SHOW_BADGE;
1474         int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1475 
1476         ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1477         Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
1478    }
1479 }
1480