blob: 13ff6e8e551f9c92ef3a2aa518d394aedacc7099 [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,
758 int uid, boolean includeDeleted, boolean includeNonGrouped) {
759 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 }
789 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
790 }
791
792 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
793 String groupId) {
794 List<NotificationChannel> deletedChannels = new ArrayList<>();
795 PackagePreferences r = getPackagePreferences(pkg, uid);
796 if (r == null || TextUtils.isEmpty(groupId)) {
797 return deletedChannels;
798 }
799
800 r.groups.remove(groupId);
801
802 int N = r.channels.size();
803 for (int i = 0; i < N; i++) {
804 final NotificationChannel nc = r.channels.valueAt(i);
805 if (groupId.equals(nc.getGroup())) {
806 nc.setDeleted(true);
807 deletedChannels.add(nc);
808 }
809 }
810 return deletedChannels;
811 }
812
813 @Override
814 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
815 int uid) {
816 PackagePreferences r = getPackagePreferences(pkg, uid);
817 if (r == null) {
818 return new ArrayList<>();
819 }
820 return r.groups.values();
821 }
822
823 @Override
824 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
825 boolean includeDeleted) {
826 Preconditions.checkNotNull(pkg);
827 List<NotificationChannel> channels = new ArrayList<>();
828 PackagePreferences r = getPackagePreferences(pkg, uid);
829 if (r == null) {
830 return ParceledListSlice.emptyList();
831 }
832 int N = r.channels.size();
833 for (int i = 0; i < N; i++) {
834 final NotificationChannel nc = r.channels.valueAt(i);
835 if (includeDeleted || !nc.isDeleted()) {
836 channels.add(nc);
837 }
838 }
839 return new ParceledListSlice<>(channels);
840 }
841
842 /**
843 * True for pre-O apps that only have the default channel, or pre O apps that have no
844 * channels yet. This method will create the default channel for pre-O apps that don't have it.
845 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
846 * upgrades.
847 */
848 public boolean onlyHasDefaultChannel(String pkg, int uid) {
849 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
850 if (r.channels.size() == 1
851 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
852 return true;
853 }
854 return false;
855 }
856
857 public int getDeletedChannelCount(String pkg, int uid) {
858 Preconditions.checkNotNull(pkg);
859 int deletedCount = 0;
860 PackagePreferences r = getPackagePreferences(pkg, uid);
861 if (r == null) {
862 return deletedCount;
863 }
864 int N = r.channels.size();
865 for (int i = 0; i < N; i++) {
866 final NotificationChannel nc = r.channels.valueAt(i);
867 if (nc.isDeleted()) {
868 deletedCount++;
869 }
870 }
871 return deletedCount;
872 }
873
874 public int getBlockedChannelCount(String pkg, int uid) {
875 Preconditions.checkNotNull(pkg);
876 int blockedCount = 0;
877 PackagePreferences r = getPackagePreferences(pkg, uid);
878 if (r == null) {
879 return blockedCount;
880 }
881 int N = r.channels.size();
882 for (int i = 0; i < N; i++) {
883 final NotificationChannel nc = r.channels.valueAt(i);
884 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
885 blockedCount++;
886 }
887 }
888 return blockedCount;
889 }
890
891 public int getBlockedAppCount(int userId) {
892 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400893 synchronized (mPackagePreferences) {
894 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400895 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400896 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400897 if (userId == UserHandle.getUserId(r.uid)
898 && r.importance == IMPORTANCE_NONE) {
899 count++;
900 }
901 }
902 }
903 return count;
904 }
905
906 public void updateChannelsBypassingDnd() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400907 synchronized (mPackagePreferences) {
908 final int numPackagePreferencess = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400909 for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
910 PackagePreferencesIndex++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400911 final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400912 final int numChannels = r.channels.size();
913
914 for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
915 NotificationChannel channel = r.channels.valueAt(channelIndex);
916 if (!channel.isDeleted() && channel.canBypassDnd()) {
917 // If any channel bypasses DND, synchronize state and return early.
918 if (!mAreChannelsBypassingDnd) {
919 mAreChannelsBypassingDnd = true;
920 updateZenPolicy(true);
921 }
922 return;
923 }
924 }
925 }
926 }
927
928 // If no channels bypass DND, update the zen policy once to disable DND bypass.
929 if (mAreChannelsBypassingDnd) {
930 mAreChannelsBypassingDnd = false;
931 updateZenPolicy(false);
932 }
933 }
934
935 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
936 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
937 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
938 policy.priorityCategories, policy.priorityCallSenders,
939 policy.priorityMessageSenders, policy.suppressedVisualEffects,
940 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
941 : 0)));
942 }
943
944 public boolean areChannelsBypassingDnd() {
945 return mAreChannelsBypassingDnd;
946 }
947
948 /**
949 * Sets importance.
950 */
951 @Override
952 public void setImportance(String pkgName, int uid, int importance) {
953 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
954 updateConfig();
955 }
956
957 public void setEnabled(String packageName, int uid, boolean enabled) {
958 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
959 if (wasEnabled == enabled) {
960 return;
961 }
962 setImportance(packageName, uid,
963 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
964 }
965
966 /**
967 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
968 * {@code uid}, have their importance locked by the user. Locked notifications don't get
969 * considered for sentiment adjustments (and thus never show a blocking helper).
970 */
971 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400972 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
973 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400974 return;
975 }
976
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400977 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400978 updateConfig();
979 }
980
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400981 /**
982 * Returns the delegate for a given package, if it's allowed by the package and the user.
983 */
984 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
985 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
986
987 if (prefs == null || prefs.delegate == null) {
988 return null;
989 }
990 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
991 return null;
992 }
993 return prefs.delegate.mPkg;
994 }
995
996 /**
997 * Used by an app to delegate notification posting privileges to another apps.
998 */
999 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1000 String delegatePkg, int delegateUid) {
1001 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1002
1003 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1004 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1005 prefs.delegate = delegate;
1006 updateConfig();
1007 }
1008
1009 /**
1010 * Used by an app to turn off its notification delegate.
1011 */
1012 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1013 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1014 if (prefs != null && prefs.delegate != null) {
1015 prefs.delegate.mEnabled = false;
1016 updateConfig();
1017 }
1018 }
1019
1020 /**
1021 * Toggles whether an app can have a notification delegate on behalf of a user.
1022 */
1023 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1024 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1025 if (prefs != null && prefs.delegate != null) {
1026 prefs.delegate.mUserAllowed = userAllowed;
1027 updateConfig();
1028 }
1029 }
1030
1031 /**
1032 * Returns whether the given app is allowed on post notifications on behalf of the other given
1033 * app.
1034 */
1035 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1036 String potentialDelegatePkg, int potentialDelegateUid) {
1037 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1038
1039 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1040 }
1041
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001042 @VisibleForTesting
1043 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1044 if (original.canBypassDnd() != update.canBypassDnd()) {
1045 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1046 }
1047 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1048 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1049 }
1050 if (original.getImportance() != update.getImportance()) {
1051 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1052 }
1053 if (original.shouldShowLights() != update.shouldShowLights()
1054 || original.getLightColor() != update.getLightColor()) {
1055 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1056 }
1057 if (!Objects.equals(original.getSound(), update.getSound())) {
1058 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1059 }
1060 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1061 || original.shouldVibrate() != update.shouldVibrate()) {
1062 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1063 }
1064 if (original.canShowBadge() != update.canShowBadge()) {
1065 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1066 }
1067 }
1068
1069 public void dump(PrintWriter pw, String prefix,
1070 @NonNull NotificationManagerService.DumpFilter filter) {
1071 pw.print(prefix);
1072 pw.println("per-package config:");
1073
1074 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001075 synchronized (mPackagePreferences) {
1076 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001077 }
1078 pw.println("Restored without uid:");
1079 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1080 }
1081
1082 public void dump(ProtoOutputStream proto,
1083 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001084 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001085 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001086 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001087 }
1088 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1089 mRestoredWithoutUids);
1090 }
1091
1092 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1093 @NonNull NotificationManagerService.DumpFilter filter,
1094 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1095 final int N = PackagePreferencess.size();
1096 for (int i = 0; i < N; i++) {
1097 final PackagePreferences r = PackagePreferencess.valueAt(i);
1098 if (filter.matches(r.pkg)) {
1099 pw.print(prefix);
1100 pw.print(" AppSettings: ");
1101 pw.print(r.pkg);
1102 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001103 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001104 pw.print(')');
1105 if (r.importance != DEFAULT_IMPORTANCE) {
1106 pw.print(" importance=");
1107 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1108 }
1109 if (r.priority != DEFAULT_PRIORITY) {
1110 pw.print(" priority=");
1111 pw.print(Notification.priorityToString(r.priority));
1112 }
1113 if (r.visibility != DEFAULT_VISIBILITY) {
1114 pw.print(" visibility=");
1115 pw.print(Notification.visibilityToString(r.visibility));
1116 }
1117 pw.print(" showBadge=");
1118 pw.print(Boolean.toString(r.showBadge));
1119 pw.println();
1120 for (NotificationChannel channel : r.channels.values()) {
1121 pw.print(prefix);
1122 channel.dump(pw, " ", filter.redact);
1123 }
1124 for (NotificationChannelGroup group : r.groups.values()) {
1125 pw.print(prefix);
1126 pw.print(" ");
1127 pw.print(" ");
1128 pw.println(group);
1129 }
1130 }
1131 }
1132 }
1133
1134 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1135 @NonNull NotificationManagerService.DumpFilter filter,
1136 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1137 final int N = PackagePreferencess.size();
1138 long fToken;
1139 for (int i = 0; i < N; i++) {
1140 final PackagePreferences r = PackagePreferencess.valueAt(i);
1141 if (filter.matches(r.pkg)) {
1142 fToken = proto.start(fieldId);
1143
1144 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1145 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1146 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1147 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1148 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1149 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1150
1151 for (NotificationChannel channel : r.channels.values()) {
1152 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1153 }
1154 for (NotificationChannelGroup group : r.groups.values()) {
1155 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1156 }
1157
1158 proto.end(fToken);
1159 }
1160 }
1161 }
1162
1163 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1164 JSONObject ranking = new JSONObject();
1165 JSONArray PackagePreferencess = new JSONArray();
1166 try {
1167 ranking.put("noUid", mRestoredWithoutUids.size());
1168 } catch (JSONException e) {
1169 // pass
1170 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001171 synchronized (mPackagePreferences) {
1172 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001173 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001174 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001175 if (filter == null || filter.matches(r.pkg)) {
1176 JSONObject PackagePreferences = new JSONObject();
1177 try {
1178 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1179 PackagePreferences.put("packageName", r.pkg);
1180 if (r.importance != DEFAULT_IMPORTANCE) {
1181 PackagePreferences.put("importance",
1182 NotificationListenerService.Ranking.importanceToString(
1183 r.importance));
1184 }
1185 if (r.priority != DEFAULT_PRIORITY) {
1186 PackagePreferences.put("priority",
1187 Notification.priorityToString(r.priority));
1188 }
1189 if (r.visibility != DEFAULT_VISIBILITY) {
1190 PackagePreferences.put("visibility",
1191 Notification.visibilityToString(r.visibility));
1192 }
1193 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1194 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1195 }
1196 JSONArray channels = new JSONArray();
1197 for (NotificationChannel channel : r.channels.values()) {
1198 channels.put(channel.toJson());
1199 }
1200 PackagePreferences.put("channels", channels);
1201 JSONArray groups = new JSONArray();
1202 for (NotificationChannelGroup group : r.groups.values()) {
1203 groups.put(group.toJson());
1204 }
1205 PackagePreferences.put("groups", groups);
1206 } catch (JSONException e) {
1207 // pass
1208 }
1209 PackagePreferencess.put(PackagePreferences);
1210 }
1211 }
1212 }
1213 try {
1214 ranking.put("PackagePreferencess", PackagePreferencess);
1215 } catch (JSONException e) {
1216 // pass
1217 }
1218 return ranking;
1219 }
1220
1221 /**
1222 * Dump only the ban information as structured JSON for the stats collector.
1223 *
1224 * This is intentionally redundant with {#link dumpJson} because the old
1225 * scraper will expect this format.
1226 *
1227 * @param filter
1228 * @return
1229 */
1230 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1231 JSONArray bans = new JSONArray();
1232 Map<Integer, String> packageBans = getPackageBans();
1233 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1234 final int userId = UserHandle.getUserId(ban.getKey());
1235 final String packageName = ban.getValue();
1236 if (filter == null || filter.matches(packageName)) {
1237 JSONObject banJson = new JSONObject();
1238 try {
1239 banJson.put("userId", userId);
1240 banJson.put("packageName", packageName);
1241 } catch (JSONException e) {
1242 e.printStackTrace();
1243 }
1244 bans.put(banJson);
1245 }
1246 }
1247 return bans;
1248 }
1249
1250 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001251 synchronized (mPackagePreferences) {
1252 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001253 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1254 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001255 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001256 if (r.importance == IMPORTANCE_NONE) {
1257 packageBans.put(r.uid, r.pkg);
1258 }
1259 }
1260
1261 return packageBans;
1262 }
1263 }
1264
1265 /**
1266 * Dump only the channel information as structured JSON for the stats collector.
1267 *
1268 * This is intentionally redundant with {#link dumpJson} because the old
1269 * scraper will expect this format.
1270 *
1271 * @param filter
1272 * @return
1273 */
1274 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1275 JSONArray channels = new JSONArray();
1276 Map<String, Integer> packageChannels = getPackageChannels();
1277 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1278 final String packageName = channelCount.getKey();
1279 if (filter == null || filter.matches(packageName)) {
1280 JSONObject channelCountJson = new JSONObject();
1281 try {
1282 channelCountJson.put("packageName", packageName);
1283 channelCountJson.put("channelCount", channelCount.getValue());
1284 } catch (JSONException e) {
1285 e.printStackTrace();
1286 }
1287 channels.put(channelCountJson);
1288 }
1289 }
1290 return channels;
1291 }
1292
1293 private Map<String, Integer> getPackageChannels() {
1294 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001295 synchronized (mPackagePreferences) {
1296 for (int i = 0; i < mPackagePreferences.size(); i++) {
1297 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001298 int channelCount = 0;
1299 for (int j = 0; j < r.channels.size(); j++) {
1300 if (!r.channels.valueAt(j).isDeleted()) {
1301 channelCount++;
1302 }
1303 }
1304 packageChannels.put(r.pkg, channelCount);
1305 }
1306 }
1307 return packageChannels;
1308 }
1309
1310 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001311 synchronized (mPackagePreferences) {
1312 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001313 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001314 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001315 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001316 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001317 }
1318 }
1319 }
1320 }
1321
1322 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001323 synchronized (mPackagePreferences) {
1324 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001325 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001326 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001327 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1328 if (PackagePreferences.channels.containsKey(
1329 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1330 PackagePreferences.channels.get(
1331 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1332 context.getResources().getString(
1333 R.string.default_notification_channel_label));
1334 }
1335 }
1336 }
1337 }
1338 }
1339
1340 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1341 int[] uidList) {
1342 if (pkgList == null || pkgList.length == 0) {
1343 return; // nothing to do
1344 }
1345 boolean updated = false;
1346 if (removingPackage) {
1347 // Remove notification settings for uninstalled package
1348 int size = Math.min(pkgList.length, uidList.length);
1349 for (int i = 0; i < size; i++) {
1350 final String pkg = pkgList[i];
1351 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001352 synchronized (mPackagePreferences) {
1353 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001354 }
1355 mRestoredWithoutUids.remove(pkg);
1356 updated = true;
1357 }
1358 } else {
1359 for (String pkg : pkgList) {
1360 // Package install
1361 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1362 if (r != null) {
1363 try {
1364 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1365 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001366 synchronized (mPackagePreferences) {
1367 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001368 }
1369 updated = true;
1370 } catch (PackageManager.NameNotFoundException e) {
1371 // noop
1372 }
1373 }
1374 // Package upgrade
1375 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001376 synchronized (mPackagePreferences) {
1377 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1378 mPm.getPackageUidAsUser(pkg, changeUserId));
1379 if (fullPackagePreferences != null) {
1380 createDefaultChannelIfNeeded(fullPackagePreferences);
1381 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1382 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001383 }
1384 } catch (PackageManager.NameNotFoundException e) {
1385 }
1386 }
1387 }
1388
1389 if (updated) {
1390 updateConfig();
1391 }
1392 }
1393
1394 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1395 return new LogMaker(
1396 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1397 .ACTION_NOTIFICATION_CHANNEL)
1398 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1399 .setPackageName(pkg)
1400 .addTaggedData(
1401 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1402 .FIELD_NOTIFICATION_CHANNEL_ID,
1403 channel.getId())
1404 .addTaggedData(
1405 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1406 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1407 channel.getImportance());
1408 }
1409
1410 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1411 return new LogMaker(
1412 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1413 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1414 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1415 .addTaggedData(
1416 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1417 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1418 groupId)
1419 .setPackageName(pkg);
1420 }
1421
1422
1423 public void updateBadgingEnabled() {
1424 if (mBadgingEnabled == null) {
1425 mBadgingEnabled = new SparseBooleanArray();
1426 }
1427 boolean changed = false;
1428 // update the cached values
1429 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1430 int userId = mBadgingEnabled.keyAt(index);
1431 final boolean oldValue = mBadgingEnabled.get(userId);
1432 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1433 Settings.Secure.NOTIFICATION_BADGING,
1434 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1435 mBadgingEnabled.put(userId, newValue);
1436 changed |= oldValue != newValue;
1437 }
1438 if (changed) {
1439 updateConfig();
1440 }
1441 }
1442
1443 public boolean badgingEnabled(UserHandle userHandle) {
1444 int userId = userHandle.getIdentifier();
1445 if (userId == UserHandle.USER_ALL) {
1446 return false;
1447 }
1448 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1449 mBadgingEnabled.put(userId,
1450 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1451 Settings.Secure.NOTIFICATION_BADGING,
1452 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1453 }
1454 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1455 }
1456
1457 private void updateConfig() {
1458 mRankingHandler.requestSort();
1459 }
1460
1461 private static String packagePreferencesKey(String pkg, int uid) {
1462 return pkg + "|" + uid;
1463 }
1464
1465 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001466 String pkg;
1467 int uid = UNKNOWN_UID;
1468 int importance = DEFAULT_IMPORTANCE;
1469 int priority = DEFAULT_PRIORITY;
1470 int visibility = DEFAULT_VISIBILITY;
1471 boolean showBadge = DEFAULT_SHOW_BADGE;
1472 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1473
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001474 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001475 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1476 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001477
1478 public boolean isValidDelegate(String pkg, int uid) {
1479 return delegate != null && delegate.isAllowed(pkg, uid);
1480 }
1481 }
1482
1483 private static class Delegate {
1484 static final boolean DEFAULT_ENABLED = true;
1485 static final boolean DEFAULT_USER_ALLOWED = true;
1486 String mPkg;
1487 int mUid = UNKNOWN_UID;
1488 boolean mEnabled = DEFAULT_ENABLED;
1489 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1490
1491 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1492 mPkg = pkg;
1493 mUid = uid;
1494 mEnabled = enabled;
1495 mUserAllowed = userAllowed;
1496 }
1497
1498 public boolean isAllowed(String pkg, int uid) {
1499 if (pkg == null || uid == UNKNOWN_UID) {
1500 return false;
1501 }
1502 return pkg.equals(mPkg)
1503 && uid == mUid
1504 && (mUserAllowed && mEnabled);
1505 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001506 }
1507}