Home | History | Annotate | Download | only in notification
      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 
    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")
    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 
    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 
    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 
    252     private static String recordKey(String pkg, int uid) {
    253         return pkg + "|" + uid;
    254     }
    255 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    489     public int getImportance(String packageName, int uid) {
    490         return getOrCreateRecord(packageName, uid).importance;
    491     }
    492 
    493     @Override
    494     public boolean canShowBadge(String packageName, int uid) {
    495         return getOrCreateRecord(packageName, uid).showBadge;
    496     }
    497 
    498     @Override
    499     public void setShowBadge(String packageName, int uid, boolean showBadge) {
    500         getOrCreateRecord(packageName, uid).showBadge = showBadge;
    501         updateConfig();
    502     }
    503 
    504     int getPackagePriority(String pkg, int uid) {
    505         return getOrCreateRecord(pkg, uid).priority;
    506     }
    507 
    508     int getPackageVisibility(String pkg, int uid) {
    509         return getOrCreateRecord(pkg, uid).visibility;
    510     }
    511 
    512     @Override
    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
    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 
    595     void clearLockedFields(NotificationChannel channel) {
    596         channel.unlockFields(channel.getUserLockedFields());
    597     }
    598 
    599     @Override
    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
    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
    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
    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
    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 
    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
    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 
    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
    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
    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      */
    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 
    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
    828     public void setImportance(String pkgName, int uid, int importance) {
    829         getOrCreateRecord(pkgName, uid).importance = importance;
    830         updateConfig();
    831     }
    832 
    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
    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 
    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 
    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 
    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      */
    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 
   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      */
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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