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