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