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