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