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