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