RankingHelper.java revision 22f02b3e4acd7c6983f4d4d58b85069d5ec920ab
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 */
16package com.android.server.notification;
17
18import static android.app.NotificationManager.IMPORTANCE_NONE;
19
20import com.android.internal.R;
21
22import android.app.Notification;
23import android.app.NotificationChannel;
24import android.app.NotificationManager;
25import android.content.Context;
26import android.content.pm.ApplicationInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.ParceledListSlice;
30import android.os.Build;
31import android.os.Process;
32import android.os.UserHandle;
33import android.service.notification.NotificationListenerService.Ranking;
34import android.text.TextUtils;
35import android.util.ArrayMap;
36import android.util.Slog;
37
38import org.json.JSONArray;
39import org.json.JSONException;
40import org.json.JSONObject;
41import org.xmlpull.v1.XmlPullParser;
42import org.xmlpull.v1.XmlPullParserException;
43import org.xmlpull.v1.XmlSerializer;
44
45import java.io.IOException;
46import java.io.PrintWriter;
47import java.util.ArrayList;
48import java.util.Collections;
49import java.util.List;
50import java.util.Map;
51import java.util.Map.Entry;
52
53public class RankingHelper implements RankingConfig {
54    private static final String TAG = "RankingHelper";
55
56    private static final int XML_VERSION = 1;
57
58    private static final String TAG_RANKING = "ranking";
59    private static final String TAG_PACKAGE = "package";
60    private static final String TAG_CHANNEL = "channel";
61
62    private static final String ATT_VERSION = "version";
63    private static final String ATT_NAME = "name";
64    private static final String ATT_UID = "uid";
65    private static final String ATT_ID = "id";
66    private static final String ATT_PRIORITY = "priority";
67    private static final String ATT_VISIBILITY = "visibility";
68    private static final String ATT_IMPORTANCE = "importance";
69
70    private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
71    private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
72    private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
73
74    private final NotificationSignalExtractor[] mSignalExtractors;
75    private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
76    private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
77
78    private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
79    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
80    private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
81
82    private final Context mContext;
83    private final RankingHandler mRankingHandler;
84    private final PackageManager mPm;
85
86    public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
87            NotificationUsageStats usageStats, String[] extractorNames) {
88        mContext = context;
89        mRankingHandler = rankingHandler;
90        mPm = pm;
91
92        final int N = extractorNames.length;
93        mSignalExtractors = new NotificationSignalExtractor[N];
94        for (int i = 0; i < N; i++) {
95            try {
96                Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
97                NotificationSignalExtractor extractor =
98                        (NotificationSignalExtractor) extractorClass.newInstance();
99                extractor.initialize(mContext, usageStats);
100                extractor.setConfig(this);
101                mSignalExtractors[i] = extractor;
102            } catch (ClassNotFoundException e) {
103                Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
104            } catch (InstantiationException e) {
105                Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
106            } catch (IllegalAccessException e) {
107                Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
108            }
109        }
110    }
111
112    @SuppressWarnings("unchecked")
113    public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
114        final int N = mSignalExtractors.length;
115        for (int i = 0; i < N; i++) {
116            final NotificationSignalExtractor extractor = mSignalExtractors[i];
117            if (extractorClass.equals(extractor.getClass())) {
118                return (T) extractor;
119            }
120        }
121        return null;
122    }
123
124    public void extractSignals(NotificationRecord r) {
125        final int N = mSignalExtractors.length;
126        for (int i = 0; i < N; i++) {
127            NotificationSignalExtractor extractor = mSignalExtractors[i];
128            try {
129                RankingReconsideration recon = extractor.process(r);
130                if (recon != null) {
131                    mRankingHandler.requestReconsideration(recon);
132                }
133            } catch (Throwable t) {
134                Slog.w(TAG, "NotificationSignalExtractor failed.", t);
135            }
136        }
137    }
138
139    public void readXml(XmlPullParser parser, boolean forRestore)
140            throws XmlPullParserException, IOException {
141        int type = parser.getEventType();
142        if (type != XmlPullParser.START_TAG) return;
143        String tag = parser.getName();
144        if (!TAG_RANKING.equals(tag)) return;
145        mRecords.clear();
146        mRestoredWithoutUids.clear();
147        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
148            tag = parser.getName();
149            if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
150                return;
151            }
152            if (type == XmlPullParser.START_TAG) {
153                if (TAG_PACKAGE.equals(tag)) {
154                    int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
155                    String name = parser.getAttributeValue(null, ATT_NAME);
156                    if (!TextUtils.isEmpty(name)) {
157                        if (forRestore) {
158                            try {
159                                //TODO: http://b/22388012
160                                uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
161                            } catch (NameNotFoundException e) {
162                                // noop
163                            }
164                        }
165
166                        Record r = getOrCreateRecord(name, uid,
167                                safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
168                                safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
169                                safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
170
171                        // Channels
172                        final int innerDepth = parser.getDepth();
173                        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
174                                && (type != XmlPullParser.END_TAG
175                                || parser.getDepth() > innerDepth)) {
176                            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
177                                continue;
178                            }
179
180                            String tagName = parser.getName();
181                            if (TAG_CHANNEL.equals(tagName)) {
182                                String id = parser.getAttributeValue(null, ATT_ID);
183                                CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
184                                int channelImportance =
185                                        safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
186
187                                if (!TextUtils.isEmpty(id)) {
188                                    final NotificationChannel channel = new NotificationChannel(id,
189                                            channelName, channelImportance);
190                                    channel.populateFromXml(parser);
191                                    r.channels.put(id, channel);
192                                }
193                            }
194                        }
195
196                        clampDefaultChannel(r);
197                    }
198                }
199            }
200        }
201        throw new IllegalStateException("Failed to reach END_DOCUMENT");
202    }
203
204    private static String recordKey(String pkg, int uid) {
205        return pkg + "|" + uid;
206    }
207
208    private Record getRecord(String pkg, int uid) {
209        final String key = recordKey(pkg, uid);
210        return mRecords.get(key);
211    }
212
213    private Record getOrCreateRecord(String pkg, int uid) {
214        return getOrCreateRecord(pkg, uid,
215                DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY);
216    }
217
218    private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
219            int visibility) {
220        final String key = recordKey(pkg, uid);
221        Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(key);
222        if (r == null) {
223            r = new Record();
224            r.pkg = pkg;
225            r.uid = uid;
226            r.importance = importance;
227            r.priority = priority;
228            r.visibility = visibility;
229            createDefaultChannelIfMissing(r);
230            if (r.uid == Record.UNKNOWN_UID) {
231                mRestoredWithoutUids.put(pkg, r);
232            } else {
233                mRecords.put(key, r);
234            }
235            clampDefaultChannel(r);
236        }
237        return r;
238    }
239
240    // Clamp the importance level of the default channel for apps targeting the new SDK version,
241    // unless the user has already changed the importance.
242    private void clampDefaultChannel(Record r) {
243        try {
244            if (r.uid != Record.UNKNOWN_UID) {
245                int userId = UserHandle.getUserId(r.uid);
246                final ApplicationInfo applicationInfo =
247                        mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
248                if (applicationInfo.targetSdkVersion > Build.VERSION_CODES.N_MR1) {
249                    final NotificationChannel defaultChannel =
250                            r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
251                    if ((defaultChannel.getUserLockedFields()
252                            & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
253                        defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
254                        updateConfig();
255                    }
256                }
257            }
258        } catch (NameNotFoundException e) {
259            // oh well.
260        }
261    }
262
263    private void createDefaultChannelIfMissing(Record r) {
264        if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
265            NotificationChannel channel;
266            channel = new NotificationChannel(
267                    NotificationChannel.DEFAULT_CHANNEL_ID,
268                    mContext.getString(R.string.default_notification_channel_label),
269                    r.importance);
270            channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
271            channel.setLockscreenVisibility(r.visibility);
272            if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
273                channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
274            }
275            if (r.priority != DEFAULT_PRIORITY) {
276                channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
277            }
278            if (r.visibility != DEFAULT_VISIBILITY) {
279                channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
280            }
281            r.channels.put(channel.getId(), channel);
282        }
283    }
284
285    public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
286        out.startTag(null, TAG_RANKING);
287        out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
288
289        final int N = mRecords.size();
290        for (int i = 0; i < N; i++) {
291            final Record r = mRecords.valueAt(i);
292            //TODO: http://b/22388012
293            if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
294                continue;
295            }
296            final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
297                    || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
298                    || r.channels.size() > 0;
299            if (hasNonDefaultSettings) {
300                out.startTag(null, TAG_PACKAGE);
301                out.attribute(null, ATT_NAME, r.pkg);
302                if (r.importance != DEFAULT_IMPORTANCE) {
303                    out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
304                }
305                if (r.priority != DEFAULT_PRIORITY) {
306                    out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
307                }
308                if (r.visibility != DEFAULT_VISIBILITY) {
309                    out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
310                }
311
312                if (!forBackup) {
313                    out.attribute(null, ATT_UID, Integer.toString(r.uid));
314                }
315
316                for (NotificationChannel channel : r.channels.values()) {
317                    channel.writeXml(out);
318                }
319
320                out.endTag(null, TAG_PACKAGE);
321            }
322        }
323        out.endTag(null, TAG_RANKING);
324    }
325
326    private void updateConfig() {
327        final int N = mSignalExtractors.length;
328        for (int i = 0; i < N; i++) {
329            mSignalExtractors[i].setConfig(this);
330        }
331        mRankingHandler.requestSort(false);
332    }
333
334    public void sort(ArrayList<NotificationRecord> notificationList) {
335        final int N = notificationList.size();
336        // clear global sort keys
337        for (int i = N - 1; i >= 0; i--) {
338            notificationList.get(i).setGlobalSortKey(null);
339        }
340
341        // rank each record individually
342        Collections.sort(notificationList, mPreliminaryComparator);
343
344        synchronized (mProxyByGroupTmp) {
345            // record individual ranking result and nominate proxies for each group
346            for (int i = N - 1; i >= 0; i--) {
347                final NotificationRecord record = notificationList.get(i);
348                record.setAuthoritativeRank(i);
349                final String groupKey = record.getGroupKey();
350                boolean isGroupSummary = record.getNotification().isGroupSummary();
351                if (isGroupSummary || !mProxyByGroupTmp.containsKey(groupKey)) {
352                    mProxyByGroupTmp.put(groupKey, record);
353                }
354            }
355            // assign global sort key:
356            //   is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
357            for (int i = 0; i < N; i++) {
358                final NotificationRecord record = notificationList.get(i);
359                NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
360                String groupSortKey = record.getNotification().getSortKey();
361
362                // We need to make sure the developer provided group sort key (gsk) is handled
363                // correctly:
364                //   gsk="" < gsk=non-null-string < gsk=null
365                //
366                // We enforce this by using different prefixes for these three cases.
367                String groupSortKeyPortion;
368                if (groupSortKey == null) {
369                    groupSortKeyPortion = "nsk";
370                } else if (groupSortKey.equals("")) {
371                    groupSortKeyPortion = "esk";
372                } else {
373                    groupSortKeyPortion = "gsk=" + groupSortKey;
374                }
375
376                boolean isGroupSummary = record.getNotification().isGroupSummary();
377                record.setGlobalSortKey(
378                        String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
379                        record.isRecentlyIntrusive() ? '0' : '1',
380                        groupProxy.getAuthoritativeRank(),
381                        isGroupSummary ? '0' : '1',
382                        groupSortKeyPortion,
383                        record.getAuthoritativeRank()));
384            }
385            mProxyByGroupTmp.clear();
386        }
387
388        // Do a second ranking pass, using group proxies
389        Collections.sort(notificationList, mFinalComparator);
390    }
391
392    public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
393        return Collections.binarySearch(notificationList, target, mFinalComparator);
394    }
395
396    private static int safeInt(XmlPullParser parser, String att, int defValue) {
397        final String val = parser.getAttributeValue(null, att);
398        return tryParseInt(val, defValue);
399    }
400
401    private static int tryParseInt(String value, int defValue) {
402        if (TextUtils.isEmpty(value)) return defValue;
403        try {
404            return Integer.parseInt(value);
405        } catch (NumberFormatException e) {
406            return defValue;
407        }
408    }
409
410    /**
411     * Gets priority.
412     */
413    @Override
414    public int getPriority(String packageName, int uid) {
415        return getOrCreateRecord(packageName, uid).priority;
416    }
417
418    /**
419     * Sets priority.
420     */
421    @Override
422    public void setPriority(String packageName, int uid, int priority) {
423        getOrCreateRecord(packageName, uid).priority = priority;
424        updateConfig();
425    }
426
427    /**
428     * Gets visual override.
429     */
430    @Override
431    public int getVisibilityOverride(String packageName, int uid) {
432        return getOrCreateRecord(packageName, uid).visibility;
433    }
434
435    /**
436     * Sets visibility override.
437     */
438    @Override
439    public void setVisibilityOverride(String pkgName, int uid, int visibility) {
440        getOrCreateRecord(pkgName, uid).visibility = visibility;
441        updateConfig();
442    }
443
444    /**
445     * Gets importance.
446     */
447    @Override
448    public int getImportance(String packageName, int uid) {
449        return getOrCreateRecord(packageName, uid).importance;
450    }
451
452    @Override
453    public void createNotificationChannel(String pkg, int uid, NotificationChannel channel) {
454        Record r = getOrCreateRecord(pkg, uid);
455        if (IMPORTANCE_NONE == r.importance) {
456            throw new IllegalArgumentException("Package blocked");
457        }
458        if (r.channels.containsKey(channel.getId()) || channel.getName().equals(
459                mContext.getString(R.string.default_notification_channel_label))) {
460            throw new IllegalArgumentException("Channel already exists");
461        }
462        if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE
463                || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
464            throw new IllegalArgumentException("Invalid importance level");
465        }
466        if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
467            channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
468        }
469        r.channels.put(channel.getId(), channel);
470        updateConfig();
471    }
472
473    @Override
474    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
475        Record r = getOrCreateRecord(pkg, uid);
476        NotificationChannel channel = r.channels.get(updatedChannel.getId());
477        if (channel == null) {
478            throw new IllegalArgumentException("Channel does not exist");
479        }
480        if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
481            updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
482        }
483        r.channels.put(updatedChannel.getId(), updatedChannel);
484        updateConfig();
485    }
486
487    @Override
488    public void updateNotificationChannelFromRanker(String pkg, int uid,
489            NotificationChannel updatedChannel) {
490        Record r = getOrCreateRecord(pkg, uid);
491        NotificationChannel channel = r.channels.get(updatedChannel.getId());
492        if (channel == null) {
493            throw new IllegalArgumentException("Channel does not exist");
494        }
495
496        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0) {
497            channel.setImportance(updatedChannel.getImportance());
498        }
499        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_LIGHTS) == 0) {
500            channel.setLights(updatedChannel.shouldShowLights());
501        }
502        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_PRIORITY) == 0) {
503            channel.setBypassDnd(updatedChannel.canBypassDnd());
504        }
505        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) {
506            channel.setSound(updatedChannel.getSound());
507        }
508        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
509            channel.enableVibration(updatedChannel.shouldVibrate());
510            channel.setVibrationPattern(updatedChannel.getVibrationPattern());
511        }
512        if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VISIBILITY) == 0) {
513            if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
514                channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
515            } else {
516                channel.setLockscreenVisibility(updatedChannel.getLockscreenVisibility());
517            }
518        }
519
520        r.channels.put(channel.getId(), channel);
521        updateConfig();
522    }
523
524    @Override
525    public NotificationChannel getNotificationChannelWithFallback(String pkg, int uid,
526            String channelId) {
527        Record r = getOrCreateRecord(pkg, uid);
528        if (channelId == null) {
529            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
530        }
531        NotificationChannel channel = r.channels.get(channelId);
532        if (channel != null) {
533            return channel;
534        } else {
535            return r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID);
536        }
537    }
538
539    @Override
540    public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) {
541        Record r = getOrCreateRecord(pkg, uid);
542        if (channelId == null) {
543            channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
544        }
545        return r.channels.get(channelId);
546    }
547
548    @Override
549    public void deleteNotificationChannel(String pkg, int uid, String channelId) {
550        Record r = getRecord(pkg, uid);
551        if (r != null) {
552            r.channels.remove(channelId);
553        }
554    }
555
556    @Override
557    public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid) {
558        List<NotificationChannel> channels = new ArrayList<>();
559        Record r = getOrCreateRecord(pkg, uid);
560        int N = r.channels.size();
561        for (int i = 0; i < N; i++) {
562            channels.add(r.channels.valueAt(i));
563        }
564        return new ParceledListSlice<>(channels);
565    }
566
567    /**
568     * Sets importance.
569     */
570    @Override
571    public void setImportance(String pkgName, int uid, int importance) {
572        getOrCreateRecord(pkgName, uid).importance = importance;
573        updateConfig();
574    }
575
576    public void setEnabled(String packageName, int uid, boolean enabled) {
577        boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE;
578        if (wasEnabled == enabled) {
579            return;
580        }
581        setImportance(packageName, uid,
582                enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE);
583    }
584
585    public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
586        if (filter == null) {
587            final int N = mSignalExtractors.length;
588            pw.print(prefix);
589            pw.print("mSignalExtractors.length = ");
590            pw.println(N);
591            for (int i = 0; i < N; i++) {
592                pw.print(prefix);
593                pw.print("  ");
594                pw.println(mSignalExtractors[i]);
595            }
596        }
597        if (filter == null) {
598            pw.print(prefix);
599            pw.println("per-package config:");
600        }
601        pw.println("Records:");
602        dumpRecords(pw, prefix, filter, mRecords);
603        pw.println("Restored without uid:");
604        dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
605    }
606
607    private static void dumpRecords(PrintWriter pw, String prefix,
608            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
609        final int N = records.size();
610        for (int i = 0; i < N; i++) {
611            final Record r = records.valueAt(i);
612            if (filter == null || filter.matches(r.pkg)) {
613                pw.print(prefix);
614                pw.print("  ");
615                pw.print(r.pkg);
616                pw.print(" (");
617                pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
618                pw.print(')');
619                if (r.importance != DEFAULT_IMPORTANCE) {
620                    pw.print(" importance=");
621                    pw.print(Ranking.importanceToString(r.importance));
622                }
623                if (r.priority != DEFAULT_PRIORITY) {
624                    pw.print(" priority=");
625                    pw.print(Notification.priorityToString(r.priority));
626                }
627                if (r.visibility != DEFAULT_VISIBILITY) {
628                    pw.print(" visibility=");
629                    pw.print(Notification.visibilityToString(r.visibility));
630                }
631                pw.println();
632                for (NotificationChannel channel : r.channels.values()) {
633                    pw.print(prefix);
634                    pw.print("  ");
635                    pw.print("  ");
636                    pw.println(channel);
637                }
638            }
639        }
640    }
641
642    public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
643        JSONObject ranking = new JSONObject();
644        JSONArray records = new JSONArray();
645        try {
646            ranking.put("noUid", mRestoredWithoutUids.size());
647        } catch (JSONException e) {
648           // pass
649        }
650        final int N = mRecords.size();
651        for (int i = 0; i < N; i++) {
652            final Record r = mRecords.valueAt(i);
653            if (filter == null || filter.matches(r.pkg)) {
654                JSONObject record = new JSONObject();
655                try {
656                    record.put("userId", UserHandle.getUserId(r.uid));
657                    record.put("packageName", r.pkg);
658                    if (r.importance != DEFAULT_IMPORTANCE) {
659                        record.put("importance", Ranking.importanceToString(r.importance));
660                    }
661                    if (r.priority != DEFAULT_PRIORITY) {
662                        record.put("priority", Notification.priorityToString(r.priority));
663                    }
664                    if (r.visibility != DEFAULT_VISIBILITY) {
665                        record.put("visibility", Notification.visibilityToString(r.visibility));
666                    }
667                    for (NotificationChannel channel : r.channels.values()) {
668                        record.put("channel", channel.toJson());
669                    }
670                } catch (JSONException e) {
671                   // pass
672                }
673                records.put(record);
674            }
675        }
676        try {
677            ranking.put("records", records);
678        } catch (JSONException e) {
679            // pass
680        }
681        return ranking;
682    }
683
684    /**
685     * Dump only the ban information as structured JSON for the stats collector.
686     *
687     * This is intentionally redundant with {#link dumpJson} because the old
688     * scraper will expect this format.
689     *
690     * @param filter
691     * @return
692     */
693    public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
694        JSONArray bans = new JSONArray();
695        Map<Integer, String> packageBans = getPackageBans();
696        for(Entry<Integer, String> ban : packageBans.entrySet()) {
697            final int userId = UserHandle.getUserId(ban.getKey());
698            final String packageName = ban.getValue();
699            if (filter == null || filter.matches(packageName)) {
700                JSONObject banJson = new JSONObject();
701                try {
702                    banJson.put("userId", userId);
703                    banJson.put("packageName", packageName);
704                } catch (JSONException e) {
705                    e.printStackTrace();
706                }
707                bans.put(banJson);
708            }
709        }
710        return bans;
711    }
712
713    public Map<Integer, String> getPackageBans() {
714        final int N = mRecords.size();
715        ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
716        for (int i = 0; i < N; i++) {
717            final Record r = mRecords.valueAt(i);
718            if (r.importance == NotificationManager.IMPORTANCE_NONE) {
719                packageBans.put(r.uid, r.pkg);
720            }
721        }
722        return packageBans;
723    }
724
725    public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList) {
726        if (removingPackage || pkgList == null || pkgList.length == 0) {
727            return; // nothing to do
728        }
729        boolean updated = false;
730        for (String pkg : pkgList) {
731            final Record r = mRestoredWithoutUids.get(pkg);
732            if (r != null) {
733                try {
734                    r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
735                    mRestoredWithoutUids.remove(pkg);
736                    mRecords.put(recordKey(r.pkg, r.uid), r);
737                    updated = true;
738                } catch (NameNotFoundException e) {
739                    // noop
740                }
741            }
742            try {
743                Record fullRecord = getRecord(pkg,
744                        mPm.getPackageUidAsUser(pkg, changeUserId));
745                if (fullRecord != null) {
746                    clampDefaultChannel(fullRecord);
747                }
748            } catch (NameNotFoundException e) {}
749        }
750
751        if (updated) {
752            updateConfig();
753        }
754    }
755
756    private static boolean isUidSystem(int uid) {
757        final int appid = UserHandle.getAppId(uid);
758        return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
759    }
760
761    private static class Record {
762        static int UNKNOWN_UID = UserHandle.USER_NULL;
763
764        String pkg;
765        int uid = UNKNOWN_UID;
766        int importance = DEFAULT_IMPORTANCE;
767        int priority = DEFAULT_PRIORITY;
768        int visibility = DEFAULT_VISIBILITY;
769
770        ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
771   }
772}
773