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