blob: 8fce5e3acdc22cfcf523c2db257d4ab4c8d8355c [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
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400518 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400519 if (fromTargetApp) {
520 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400521 group.setAllowAppOverlay(oldGroup.canOverlayApps());
522 group.unlockFields(group.getUserLockedFields());
523 group.lockFields(oldGroup.getUserLockedFields());
524 } else {
525 // but the system can
526 if (group.isBlocked() != oldGroup.isBlocked()) {
527 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
528 }
529 if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
530 group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
531 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400532 }
533 }
534 r.groups.put(group.getId(), group);
535 }
536
537 @Override
538 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
539 boolean fromTargetApp, boolean hasDndAccess) {
540 Preconditions.checkNotNull(pkg);
541 Preconditions.checkNotNull(channel);
542 Preconditions.checkNotNull(channel.getId());
543 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
544 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
545 if (r == null) {
546 throw new IllegalArgumentException("Invalid package");
547 }
548 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
549 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
550 }
551 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
552 throw new IllegalArgumentException("Reserved id");
553 }
554 NotificationChannel existing = r.channels.get(channel.getId());
555 // Keep most of the existing settings
556 if (existing != null && fromTargetApp) {
557 if (existing.isDeleted()) {
558 existing.setDeleted(false);
559
560 // log a resurrected channel as if it's new again
561 MetricsLogger.action(getChannelLog(channel, pkg).setType(
562 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
563 }
564
565 existing.setName(channel.getName().toString());
566 existing.setDescription(channel.getDescription());
567 existing.setBlockableSystem(channel.isBlockableSystem());
568 if (existing.getGroup() == null) {
569 existing.setGroup(channel.getGroup());
570 }
571
572 // Apps are allowed to downgrade channel importance if the user has not changed any
573 // fields on this channel yet.
574 if (existing.getUserLockedFields() == 0 &&
575 channel.getImportance() < existing.getImportance()) {
576 existing.setImportance(channel.getImportance());
577 }
578
579 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
580 // fields on the channel yet
581 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
582 boolean bypassDnd = channel.canBypassDnd();
583 existing.setBypassDnd(bypassDnd);
584
585 if (bypassDnd != mAreChannelsBypassingDnd) {
586 updateChannelsBypassingDnd();
587 }
588 }
589
590 updateConfig();
591 return;
592 }
593 if (channel.getImportance() < IMPORTANCE_NONE
594 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
595 throw new IllegalArgumentException("Invalid importance level");
596 }
597
598 // Reset fields that apps aren't allowed to set.
599 if (fromTargetApp && !hasDndAccess) {
600 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
601 }
602 if (fromTargetApp) {
603 channel.setLockscreenVisibility(r.visibility);
604 }
605 clearLockedFields(channel);
606 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
607 channel.setLockscreenVisibility(
608 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
609 }
610 if (!r.showBadge) {
611 channel.setShowBadge(false);
612 }
613
614 r.channels.put(channel.getId(), channel);
615 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
616 updateChannelsBypassingDnd();
617 }
618 MetricsLogger.action(getChannelLog(channel, pkg).setType(
619 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
620 }
621
622 void clearLockedFields(NotificationChannel channel) {
623 channel.unlockFields(channel.getUserLockedFields());
624 }
625
626 @Override
627 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
628 boolean fromUser) {
629 Preconditions.checkNotNull(updatedChannel);
630 Preconditions.checkNotNull(updatedChannel.getId());
631 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
632 if (r == null) {
633 throw new IllegalArgumentException("Invalid package");
634 }
635 NotificationChannel channel = r.channels.get(updatedChannel.getId());
636 if (channel == null || channel.isDeleted()) {
637 throw new IllegalArgumentException("Channel does not exist");
638 }
639 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
640 updatedChannel.setLockscreenVisibility(
641 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
642 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400643 if (fromUser) {
644 updatedChannel.lockFields(channel.getUserLockedFields());
645 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400646 } else {
647 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400648 }
649 r.channels.put(updatedChannel.getId(), updatedChannel);
650
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400651 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400652 // copy settings to app level so they are inherited by new channels
653 // when the app migrates
654 r.importance = updatedChannel.getImportance();
655 r.priority = updatedChannel.canBypassDnd()
656 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
657 r.visibility = updatedChannel.getLockscreenVisibility();
658 r.showBadge = updatedChannel.canShowBadge();
659 }
660
661 if (!channel.equals(updatedChannel)) {
662 // only log if there are real changes
663 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
664 }
665
666 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
667 updateChannelsBypassingDnd();
668 }
669 updateConfig();
670 }
671
672 @Override
673 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
674 boolean includeDeleted) {
675 Preconditions.checkNotNull(pkg);
676 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
677 if (r == null) {
678 return null;
679 }
680 if (channelId == null) {
681 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
682 }
683 final NotificationChannel nc = r.channels.get(channelId);
684 if (nc != null && (includeDeleted || !nc.isDeleted())) {
685 return nc;
686 }
687 return null;
688 }
689
690 @Override
691 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
692 PackagePreferences r = getPackagePreferences(pkg, uid);
693 if (r == null) {
694 return;
695 }
696 NotificationChannel channel = r.channels.get(channelId);
697 if (channel != null) {
698 channel.setDeleted(true);
699 LogMaker lm = getChannelLog(channel, pkg);
700 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
701 MetricsLogger.action(lm);
702
703 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
704 updateChannelsBypassingDnd();
705 }
706 }
707 }
708
709 @Override
710 @VisibleForTesting
711 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
712 Preconditions.checkNotNull(pkg);
713 Preconditions.checkNotNull(channelId);
714 PackagePreferences r = getPackagePreferences(pkg, uid);
715 if (r == null) {
716 return;
717 }
718 r.channels.remove(channelId);
719 }
720
721 @Override
722 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
723 Preconditions.checkNotNull(pkg);
724 PackagePreferences r = getPackagePreferences(pkg, uid);
725 if (r == null) {
726 return;
727 }
728 int N = r.channels.size() - 1;
729 for (int i = N; i >= 0; i--) {
730 String key = r.channels.keyAt(i);
731 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
732 r.channels.remove(key);
733 }
734 }
735 }
736
737 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
738 int uid, String groupId, boolean includeDeleted) {
739 Preconditions.checkNotNull(pkg);
740 PackagePreferences r = getPackagePreferences(pkg, uid);
741 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
742 return null;
743 }
744 NotificationChannelGroup group = r.groups.get(groupId).clone();
745 group.setChannels(new ArrayList<>());
746 int N = r.channels.size();
747 for (int i = 0; i < N; i++) {
748 final NotificationChannel nc = r.channels.valueAt(i);
749 if (includeDeleted || !nc.isDeleted()) {
750 if (groupId.equals(nc.getGroup())) {
751 group.addChannel(nc);
752 }
753 }
754 }
755 return group;
756 }
757
758 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
759 int uid) {
760 Preconditions.checkNotNull(pkg);
761 PackagePreferences r = getPackagePreferences(pkg, uid);
762 if (r == null) {
763 return null;
764 }
765 return r.groups.get(groupId);
766 }
767
768 @Override
769 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400770 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400771 Preconditions.checkNotNull(pkg);
772 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
773 PackagePreferences r = getPackagePreferences(pkg, uid);
774 if (r == null) {
775 return ParceledListSlice.emptyList();
776 }
777 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
778 int N = r.channels.size();
779 for (int i = 0; i < N; i++) {
780 final NotificationChannel nc = r.channels.valueAt(i);
781 if (includeDeleted || !nc.isDeleted()) {
782 if (nc.getGroup() != null) {
783 if (r.groups.get(nc.getGroup()) != null) {
784 NotificationChannelGroup ncg = groups.get(nc.getGroup());
785 if (ncg == null) {
786 ncg = r.groups.get(nc.getGroup()).clone();
787 ncg.setChannels(new ArrayList<>());
788 groups.put(nc.getGroup(), ncg);
789
790 }
791 ncg.addChannel(nc);
792 }
793 } else {
794 nonGrouped.addChannel(nc);
795 }
796 }
797 }
798 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
799 groups.put(null, nonGrouped);
800 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400801 if (includeEmpty) {
802 for (NotificationChannelGroup group : r.groups.values()) {
803 if (!groups.containsKey(group.getId())) {
804 groups.put(group.getId(), group);
805 }
806 }
807 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400808 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
809 }
810
811 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
812 String groupId) {
813 List<NotificationChannel> deletedChannels = new ArrayList<>();
814 PackagePreferences r = getPackagePreferences(pkg, uid);
815 if (r == null || TextUtils.isEmpty(groupId)) {
816 return deletedChannels;
817 }
818
819 r.groups.remove(groupId);
820
821 int N = r.channels.size();
822 for (int i = 0; i < N; i++) {
823 final NotificationChannel nc = r.channels.valueAt(i);
824 if (groupId.equals(nc.getGroup())) {
825 nc.setDeleted(true);
826 deletedChannels.add(nc);
827 }
828 }
829 return deletedChannels;
830 }
831
832 @Override
833 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
834 int uid) {
835 PackagePreferences r = getPackagePreferences(pkg, uid);
836 if (r == null) {
837 return new ArrayList<>();
838 }
839 return r.groups.values();
840 }
841
842 @Override
843 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
844 boolean includeDeleted) {
845 Preconditions.checkNotNull(pkg);
846 List<NotificationChannel> channels = new ArrayList<>();
847 PackagePreferences r = getPackagePreferences(pkg, uid);
848 if (r == null) {
849 return ParceledListSlice.emptyList();
850 }
851 int N = r.channels.size();
852 for (int i = 0; i < N; i++) {
853 final NotificationChannel nc = r.channels.valueAt(i);
854 if (includeDeleted || !nc.isDeleted()) {
855 channels.add(nc);
856 }
857 }
858 return new ParceledListSlice<>(channels);
859 }
860
861 /**
862 * True for pre-O apps that only have the default channel, or pre O apps that have no
863 * channels yet. This method will create the default channel for pre-O apps that don't have it.
864 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
865 * upgrades.
866 */
867 public boolean onlyHasDefaultChannel(String pkg, int uid) {
868 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
869 if (r.channels.size() == 1
870 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
871 return true;
872 }
873 return false;
874 }
875
876 public int getDeletedChannelCount(String pkg, int uid) {
877 Preconditions.checkNotNull(pkg);
878 int deletedCount = 0;
879 PackagePreferences r = getPackagePreferences(pkg, uid);
880 if (r == null) {
881 return deletedCount;
882 }
883 int N = r.channels.size();
884 for (int i = 0; i < N; i++) {
885 final NotificationChannel nc = r.channels.valueAt(i);
886 if (nc.isDeleted()) {
887 deletedCount++;
888 }
889 }
890 return deletedCount;
891 }
892
893 public int getBlockedChannelCount(String pkg, int uid) {
894 Preconditions.checkNotNull(pkg);
895 int blockedCount = 0;
896 PackagePreferences r = getPackagePreferences(pkg, uid);
897 if (r == null) {
898 return blockedCount;
899 }
900 int N = r.channels.size();
901 for (int i = 0; i < N; i++) {
902 final NotificationChannel nc = r.channels.valueAt(i);
903 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
904 blockedCount++;
905 }
906 }
907 return blockedCount;
908 }
909
910 public int getBlockedAppCount(int userId) {
911 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400912 synchronized (mPackagePreferences) {
913 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400914 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400915 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400916 if (userId == UserHandle.getUserId(r.uid)
917 && r.importance == IMPORTANCE_NONE) {
918 count++;
919 }
920 }
921 }
922 return count;
923 }
924
925 public void updateChannelsBypassingDnd() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400926 synchronized (mPackagePreferences) {
927 final int numPackagePreferencess = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400928 for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
929 PackagePreferencesIndex++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400930 final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400931 final int numChannels = r.channels.size();
932
933 for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
934 NotificationChannel channel = r.channels.valueAt(channelIndex);
935 if (!channel.isDeleted() && channel.canBypassDnd()) {
936 // If any channel bypasses DND, synchronize state and return early.
937 if (!mAreChannelsBypassingDnd) {
938 mAreChannelsBypassingDnd = true;
939 updateZenPolicy(true);
940 }
941 return;
942 }
943 }
944 }
945 }
946
947 // If no channels bypass DND, update the zen policy once to disable DND bypass.
948 if (mAreChannelsBypassingDnd) {
949 mAreChannelsBypassingDnd = false;
950 updateZenPolicy(false);
951 }
952 }
953
954 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
955 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
956 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
957 policy.priorityCategories, policy.priorityCallSenders,
958 policy.priorityMessageSenders, policy.suppressedVisualEffects,
959 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
960 : 0)));
961 }
962
963 public boolean areChannelsBypassingDnd() {
964 return mAreChannelsBypassingDnd;
965 }
966
967 /**
968 * Sets importance.
969 */
970 @Override
971 public void setImportance(String pkgName, int uid, int importance) {
972 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
973 updateConfig();
974 }
975
976 public void setEnabled(String packageName, int uid, boolean enabled) {
977 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
978 if (wasEnabled == enabled) {
979 return;
980 }
981 setImportance(packageName, uid,
982 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
983 }
984
985 /**
986 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
987 * {@code uid}, have their importance locked by the user. Locked notifications don't get
988 * considered for sentiment adjustments (and thus never show a blocking helper).
989 */
990 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400991 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
992 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400993 return;
994 }
995
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400996 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400997 updateConfig();
998 }
999
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001000 /**
1001 * Returns the delegate for a given package, if it's allowed by the package and the user.
1002 */
1003 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1004 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1005
1006 if (prefs == null || prefs.delegate == null) {
1007 return null;
1008 }
1009 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1010 return null;
1011 }
1012 return prefs.delegate.mPkg;
1013 }
1014
1015 /**
1016 * Used by an app to delegate notification posting privileges to another apps.
1017 */
1018 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1019 String delegatePkg, int delegateUid) {
1020 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1021
1022 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1023 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1024 prefs.delegate = delegate;
1025 updateConfig();
1026 }
1027
1028 /**
1029 * Used by an app to turn off its notification delegate.
1030 */
1031 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1032 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1033 if (prefs != null && prefs.delegate != null) {
1034 prefs.delegate.mEnabled = false;
1035 updateConfig();
1036 }
1037 }
1038
1039 /**
1040 * Toggles whether an app can have a notification delegate on behalf of a user.
1041 */
1042 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1043 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1044 if (prefs != null && prefs.delegate != null) {
1045 prefs.delegate.mUserAllowed = userAllowed;
1046 updateConfig();
1047 }
1048 }
1049
1050 /**
1051 * Returns whether the given app is allowed on post notifications on behalf of the other given
1052 * app.
1053 */
1054 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1055 String potentialDelegatePkg, int potentialDelegateUid) {
1056 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1057
1058 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1059 }
1060
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001061 @VisibleForTesting
1062 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1063 if (original.canBypassDnd() != update.canBypassDnd()) {
1064 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1065 }
1066 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1067 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1068 }
1069 if (original.getImportance() != update.getImportance()) {
1070 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1071 }
1072 if (original.shouldShowLights() != update.shouldShowLights()
1073 || original.getLightColor() != update.getLightColor()) {
1074 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1075 }
1076 if (!Objects.equals(original.getSound(), update.getSound())) {
1077 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1078 }
1079 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1080 || original.shouldVibrate() != update.shouldVibrate()) {
1081 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1082 }
1083 if (original.canShowBadge() != update.canShowBadge()) {
1084 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1085 }
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001086 if (original.isAppOverlayAllowed() != update.isAppOverlayAllowed()) {
1087 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY);
1088 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001089 }
1090
1091 public void dump(PrintWriter pw, String prefix,
1092 @NonNull NotificationManagerService.DumpFilter filter) {
1093 pw.print(prefix);
1094 pw.println("per-package config:");
1095
1096 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001097 synchronized (mPackagePreferences) {
1098 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001099 }
1100 pw.println("Restored without uid:");
1101 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1102 }
1103
1104 public void dump(ProtoOutputStream proto,
1105 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001106 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001107 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001108 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001109 }
1110 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1111 mRestoredWithoutUids);
1112 }
1113
1114 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1115 @NonNull NotificationManagerService.DumpFilter filter,
1116 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1117 final int N = PackagePreferencess.size();
1118 for (int i = 0; i < N; i++) {
1119 final PackagePreferences r = PackagePreferencess.valueAt(i);
1120 if (filter.matches(r.pkg)) {
1121 pw.print(prefix);
1122 pw.print(" AppSettings: ");
1123 pw.print(r.pkg);
1124 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001125 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001126 pw.print(')');
1127 if (r.importance != DEFAULT_IMPORTANCE) {
1128 pw.print(" importance=");
1129 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1130 }
1131 if (r.priority != DEFAULT_PRIORITY) {
1132 pw.print(" priority=");
1133 pw.print(Notification.priorityToString(r.priority));
1134 }
1135 if (r.visibility != DEFAULT_VISIBILITY) {
1136 pw.print(" visibility=");
1137 pw.print(Notification.visibilityToString(r.visibility));
1138 }
1139 pw.print(" showBadge=");
1140 pw.print(Boolean.toString(r.showBadge));
1141 pw.println();
1142 for (NotificationChannel channel : r.channels.values()) {
1143 pw.print(prefix);
1144 channel.dump(pw, " ", filter.redact);
1145 }
1146 for (NotificationChannelGroup group : r.groups.values()) {
1147 pw.print(prefix);
1148 pw.print(" ");
1149 pw.print(" ");
1150 pw.println(group);
1151 }
1152 }
1153 }
1154 }
1155
1156 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1157 @NonNull NotificationManagerService.DumpFilter filter,
1158 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1159 final int N = PackagePreferencess.size();
1160 long fToken;
1161 for (int i = 0; i < N; i++) {
1162 final PackagePreferences r = PackagePreferencess.valueAt(i);
1163 if (filter.matches(r.pkg)) {
1164 fToken = proto.start(fieldId);
1165
1166 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1167 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1168 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1169 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1170 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1171 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1172
1173 for (NotificationChannel channel : r.channels.values()) {
1174 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1175 }
1176 for (NotificationChannelGroup group : r.groups.values()) {
1177 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1178 }
1179
1180 proto.end(fToken);
1181 }
1182 }
1183 }
1184
1185 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1186 JSONObject ranking = new JSONObject();
1187 JSONArray PackagePreferencess = new JSONArray();
1188 try {
1189 ranking.put("noUid", mRestoredWithoutUids.size());
1190 } catch (JSONException e) {
1191 // pass
1192 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001193 synchronized (mPackagePreferences) {
1194 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001195 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001196 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001197 if (filter == null || filter.matches(r.pkg)) {
1198 JSONObject PackagePreferences = new JSONObject();
1199 try {
1200 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1201 PackagePreferences.put("packageName", r.pkg);
1202 if (r.importance != DEFAULT_IMPORTANCE) {
1203 PackagePreferences.put("importance",
1204 NotificationListenerService.Ranking.importanceToString(
1205 r.importance));
1206 }
1207 if (r.priority != DEFAULT_PRIORITY) {
1208 PackagePreferences.put("priority",
1209 Notification.priorityToString(r.priority));
1210 }
1211 if (r.visibility != DEFAULT_VISIBILITY) {
1212 PackagePreferences.put("visibility",
1213 Notification.visibilityToString(r.visibility));
1214 }
1215 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1216 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1217 }
1218 JSONArray channels = new JSONArray();
1219 for (NotificationChannel channel : r.channels.values()) {
1220 channels.put(channel.toJson());
1221 }
1222 PackagePreferences.put("channels", channels);
1223 JSONArray groups = new JSONArray();
1224 for (NotificationChannelGroup group : r.groups.values()) {
1225 groups.put(group.toJson());
1226 }
1227 PackagePreferences.put("groups", groups);
1228 } catch (JSONException e) {
1229 // pass
1230 }
1231 PackagePreferencess.put(PackagePreferences);
1232 }
1233 }
1234 }
1235 try {
1236 ranking.put("PackagePreferencess", PackagePreferencess);
1237 } catch (JSONException e) {
1238 // pass
1239 }
1240 return ranking;
1241 }
1242
1243 /**
1244 * Dump only the ban information as structured JSON for the stats collector.
1245 *
1246 * This is intentionally redundant with {#link dumpJson} because the old
1247 * scraper will expect this format.
1248 *
1249 * @param filter
1250 * @return
1251 */
1252 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1253 JSONArray bans = new JSONArray();
1254 Map<Integer, String> packageBans = getPackageBans();
1255 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1256 final int userId = UserHandle.getUserId(ban.getKey());
1257 final String packageName = ban.getValue();
1258 if (filter == null || filter.matches(packageName)) {
1259 JSONObject banJson = new JSONObject();
1260 try {
1261 banJson.put("userId", userId);
1262 banJson.put("packageName", packageName);
1263 } catch (JSONException e) {
1264 e.printStackTrace();
1265 }
1266 bans.put(banJson);
1267 }
1268 }
1269 return bans;
1270 }
1271
1272 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001273 synchronized (mPackagePreferences) {
1274 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001275 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1276 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001277 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001278 if (r.importance == IMPORTANCE_NONE) {
1279 packageBans.put(r.uid, r.pkg);
1280 }
1281 }
1282
1283 return packageBans;
1284 }
1285 }
1286
1287 /**
1288 * Dump only the channel information as structured JSON for the stats collector.
1289 *
1290 * This is intentionally redundant with {#link dumpJson} because the old
1291 * scraper will expect this format.
1292 *
1293 * @param filter
1294 * @return
1295 */
1296 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1297 JSONArray channels = new JSONArray();
1298 Map<String, Integer> packageChannels = getPackageChannels();
1299 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1300 final String packageName = channelCount.getKey();
1301 if (filter == null || filter.matches(packageName)) {
1302 JSONObject channelCountJson = new JSONObject();
1303 try {
1304 channelCountJson.put("packageName", packageName);
1305 channelCountJson.put("channelCount", channelCount.getValue());
1306 } catch (JSONException e) {
1307 e.printStackTrace();
1308 }
1309 channels.put(channelCountJson);
1310 }
1311 }
1312 return channels;
1313 }
1314
1315 private Map<String, Integer> getPackageChannels() {
1316 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001317 synchronized (mPackagePreferences) {
1318 for (int i = 0; i < mPackagePreferences.size(); i++) {
1319 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001320 int channelCount = 0;
1321 for (int j = 0; j < r.channels.size(); j++) {
1322 if (!r.channels.valueAt(j).isDeleted()) {
1323 channelCount++;
1324 }
1325 }
1326 packageChannels.put(r.pkg, channelCount);
1327 }
1328 }
1329 return packageChannels;
1330 }
1331
1332 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001333 synchronized (mPackagePreferences) {
1334 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001335 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001336 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001337 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001338 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001339 }
1340 }
1341 }
1342 }
1343
1344 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001345 synchronized (mPackagePreferences) {
1346 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001347 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001348 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001349 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1350 if (PackagePreferences.channels.containsKey(
1351 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1352 PackagePreferences.channels.get(
1353 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1354 context.getResources().getString(
1355 R.string.default_notification_channel_label));
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1363 int[] uidList) {
1364 if (pkgList == null || pkgList.length == 0) {
1365 return; // nothing to do
1366 }
1367 boolean updated = false;
1368 if (removingPackage) {
1369 // Remove notification settings for uninstalled package
1370 int size = Math.min(pkgList.length, uidList.length);
1371 for (int i = 0; i < size; i++) {
1372 final String pkg = pkgList[i];
1373 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001374 synchronized (mPackagePreferences) {
1375 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001376 }
1377 mRestoredWithoutUids.remove(pkg);
1378 updated = true;
1379 }
1380 } else {
1381 for (String pkg : pkgList) {
1382 // Package install
1383 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1384 if (r != null) {
1385 try {
1386 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1387 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001388 synchronized (mPackagePreferences) {
1389 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001390 }
1391 updated = true;
1392 } catch (PackageManager.NameNotFoundException e) {
1393 // noop
1394 }
1395 }
1396 // Package upgrade
1397 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001398 synchronized (mPackagePreferences) {
1399 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1400 mPm.getPackageUidAsUser(pkg, changeUserId));
1401 if (fullPackagePreferences != null) {
1402 createDefaultChannelIfNeeded(fullPackagePreferences);
1403 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1404 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001405 }
1406 } catch (PackageManager.NameNotFoundException e) {
1407 }
1408 }
1409 }
1410
1411 if (updated) {
1412 updateConfig();
1413 }
1414 }
1415
1416 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1417 return new LogMaker(
1418 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1419 .ACTION_NOTIFICATION_CHANNEL)
1420 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1421 .setPackageName(pkg)
1422 .addTaggedData(
1423 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1424 .FIELD_NOTIFICATION_CHANNEL_ID,
1425 channel.getId())
1426 .addTaggedData(
1427 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1428 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1429 channel.getImportance());
1430 }
1431
1432 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1433 return new LogMaker(
1434 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1435 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1436 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1437 .addTaggedData(
1438 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1439 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1440 groupId)
1441 .setPackageName(pkg);
1442 }
1443
1444
1445 public void updateBadgingEnabled() {
1446 if (mBadgingEnabled == null) {
1447 mBadgingEnabled = new SparseBooleanArray();
1448 }
1449 boolean changed = false;
1450 // update the cached values
1451 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1452 int userId = mBadgingEnabled.keyAt(index);
1453 final boolean oldValue = mBadgingEnabled.get(userId);
1454 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1455 Settings.Secure.NOTIFICATION_BADGING,
1456 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1457 mBadgingEnabled.put(userId, newValue);
1458 changed |= oldValue != newValue;
1459 }
1460 if (changed) {
1461 updateConfig();
1462 }
1463 }
1464
1465 public boolean badgingEnabled(UserHandle userHandle) {
1466 int userId = userHandle.getIdentifier();
1467 if (userId == UserHandle.USER_ALL) {
1468 return false;
1469 }
1470 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1471 mBadgingEnabled.put(userId,
1472 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1473 Settings.Secure.NOTIFICATION_BADGING,
1474 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1475 }
1476 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1477 }
1478
1479 private void updateConfig() {
1480 mRankingHandler.requestSort();
1481 }
1482
1483 private static String packagePreferencesKey(String pkg, int uid) {
1484 return pkg + "|" + uid;
1485 }
1486
1487 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001488 String pkg;
1489 int uid = UNKNOWN_UID;
1490 int importance = DEFAULT_IMPORTANCE;
1491 int priority = DEFAULT_PRIORITY;
1492 int visibility = DEFAULT_VISIBILITY;
1493 boolean showBadge = DEFAULT_SHOW_BADGE;
1494 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1495
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001496 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001497 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1498 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001499
1500 public boolean isValidDelegate(String pkg, int uid) {
1501 return delegate != null && delegate.isAllowed(pkg, uid);
1502 }
1503 }
1504
1505 private static class Delegate {
1506 static final boolean DEFAULT_ENABLED = true;
1507 static final boolean DEFAULT_USER_ALLOWED = true;
1508 String mPkg;
1509 int mUid = UNKNOWN_UID;
1510 boolean mEnabled = DEFAULT_ENABLED;
1511 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1512
1513 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1514 mPkg = pkg;
1515 mUid = uid;
1516 mEnabled = enabled;
1517 mUserAllowed = userAllowed;
1518 }
1519
1520 public boolean isAllowed(String pkg, int uid) {
1521 if (pkg == null || uid == UNKNOWN_UID) {
1522 return false;
1523 }
1524 return pkg.equals(mPkg)
1525 && uid == mUid
1526 && (mUserAllowed && mEnabled);
1527 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001528 }
1529}