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