blob: 28f6972636be3c9cd0a8e514087ee9529a2de688 [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;
Julia Reynolds413ba842019-01-11 10:38:08 -050071 private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040072
73 @VisibleForTesting
74 static final String TAG_RANKING = "ranking";
75 private static final String TAG_PACKAGE = "package";
76 private static final String TAG_CHANNEL = "channel";
77 private static final String TAG_GROUP = "channelGroup";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040078 private static final String TAG_DELEGATE = "delegate";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040079
80 private static final String ATT_VERSION = "version";
81 private static final String ATT_NAME = "name";
82 private static final String ATT_UID = "uid";
83 private static final String ATT_ID = "id";
Julia Reynolds33ab8a02018-12-17 16:19:52 -050084 private static final String ATT_APP_OVERLAY = "overlay";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040085 private static final String ATT_PRIORITY = "priority";
86 private static final String ATT_VISIBILITY = "visibility";
87 private static final String ATT_IMPORTANCE = "importance";
88 private static final String ATT_SHOW_BADGE = "show_badge";
89 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040090 private static final String ATT_ENABLED = "enabled";
91 private static final String ATT_USER_ALLOWED = "allowed";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040092
93 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
94 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
95 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
96 private static final boolean DEFAULT_SHOW_BADGE = true;
Julia Reynolds33ab8a02018-12-17 16:19:52 -050097 private static final boolean DEFAULT_ALLOW_APP_OVERLAY = true;
Julia Reynolds413ba842019-01-11 10:38:08 -050098 private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040099 /**
100 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
101 * fields.
102 */
103 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
104
105 /**
106 * All user-lockable fields for a given application.
107 */
108 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
109 public @interface LockableAppFields {
110 int USER_LOCKED_IMPORTANCE = 0x00000001;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500111 int USER_LOCKED_APP_OVERLAY = 0x00000002;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400112 }
113
114 // pkg|uid => PackagePreferences
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400115 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400116 // pkg => PackagePreferences
117 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
118
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400119 private final Context mContext;
120 private final PackageManager mPm;
121 private final RankingHandler mRankingHandler;
122 private final ZenModeHelper mZenModeHelper;
123
124 private SparseBooleanArray mBadgingEnabled;
125 private boolean mAreChannelsBypassingDnd;
126
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400127 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
128 ZenModeHelper zenHelper) {
129 mContext = context;
130 mZenModeHelper = zenHelper;
131 mRankingHandler = rankingHandler;
132 mPm = pm;
133
134 updateBadgingEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500135 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400136 }
137
138 public void readXml(XmlPullParser parser, boolean forRestore)
139 throws XmlPullParserException, IOException {
140 int type = parser.getEventType();
141 if (type != XmlPullParser.START_TAG) return;
142 String tag = parser.getName();
143 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400144 synchronized (mPackagePreferences) {
145 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400146
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400147 // at the time of serialization.
148 mRestoredWithoutUids.clear();
149 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
150 tag = parser.getName();
151 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
152 return;
153 }
154 if (type == XmlPullParser.START_TAG) {
155 if (TAG_PACKAGE.equals(tag)) {
156 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
157 String name = parser.getAttributeValue(null, ATT_NAME);
158 if (!TextUtils.isEmpty(name)) {
159 if (forRestore) {
160 try {
161 //TODO: http://b/22388012
162 uid = mPm.getPackageUidAsUser(name,
163 UserHandle.USER_SYSTEM);
164 } catch (PackageManager.NameNotFoundException e) {
165 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400166 }
167 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400168
169 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
170 XmlUtils.readIntAttribute(
171 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
172 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
173 DEFAULT_PRIORITY),
174 XmlUtils.readIntAttribute(
175 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
176 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500177 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
178 XmlUtils.readBooleanAttribute(
179 parser, ATT_APP_OVERLAY, DEFAULT_ALLOW_APP_OVERLAY));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400180 r.importance = XmlUtils.readIntAttribute(
181 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
182 r.priority = XmlUtils.readIntAttribute(
183 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
184 r.visibility = XmlUtils.readIntAttribute(
185 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
186 r.showBadge = XmlUtils.readBooleanAttribute(
187 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
188 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
189 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
190
191 final int innerDepth = parser.getDepth();
192 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
193 && (type != XmlPullParser.END_TAG
194 || parser.getDepth() > innerDepth)) {
195 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
196 continue;
197 }
198
199 String tagName = parser.getName();
200 // Channel groups
201 if (TAG_GROUP.equals(tagName)) {
202 String id = parser.getAttributeValue(null, ATT_ID);
203 CharSequence groupName = parser.getAttributeValue(null,
204 ATT_NAME);
205 if (!TextUtils.isEmpty(id)) {
206 NotificationChannelGroup group
207 = new NotificationChannelGroup(id, groupName);
208 group.populateFromXml(parser);
209 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400210 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400211 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400212 // Channels
213 if (TAG_CHANNEL.equals(tagName)) {
214 String id = parser.getAttributeValue(null, ATT_ID);
215 String channelName = parser.getAttributeValue(null, ATT_NAME);
216 int channelImportance = XmlUtils.readIntAttribute(
217 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
218 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
219 NotificationChannel channel = new NotificationChannel(id,
220 channelName, channelImportance);
221 if (forRestore) {
222 channel.populateFromXmlForRestore(parser, mContext);
223 } else {
224 channel.populateFromXml(parser);
225 }
226 r.channels.put(id, channel);
227 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400228 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400229 // Delegate
230 if (TAG_DELEGATE.equals(tagName)) {
231 int delegateId =
232 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
233 String delegateName =
234 XmlUtils.readStringAttribute(parser, ATT_NAME);
235 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
236 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
237 boolean userAllowed = XmlUtils.readBooleanAttribute(
238 parser, ATT_USER_ALLOWED,
239 Delegate.DEFAULT_USER_ALLOWED);
240 Delegate d = null;
241 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
242 delegateName)) {
243 d = new Delegate(
244 delegateName, delegateId, delegateEnabled,
245 userAllowed);
246 }
247 r.delegate = d;
248 }
249
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400250 }
251
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400252 try {
253 deleteDefaultChannelIfNeeded(r);
254 } catch (PackageManager.NameNotFoundException e) {
255 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
256 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400257 }
258 }
259 }
260 }
261 }
262 throw new IllegalStateException("Failed to reach END_DOCUMENT");
263 }
264
265 private PackagePreferences getPackagePreferences(String pkg, int uid) {
266 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400267 synchronized (mPackagePreferences) {
268 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400269 }
270 }
271
272 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
273 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500274 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
275 DEFAULT_ALLOW_APP_OVERLAY);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400276 }
277
278 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500279 int priority, int visibility, boolean showBadge, boolean allowAppOverlay) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400280 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400281 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400282 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400283 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400284 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400285 if (r == null) {
286 r = new PackagePreferences();
287 r.pkg = pkg;
288 r.uid = uid;
289 r.importance = importance;
290 r.priority = priority;
291 r.visibility = visibility;
292 r.showBadge = showBadge;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500293 r.appOverlay = allowAppOverlay;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400294
295 try {
296 createDefaultChannelIfNeeded(r);
297 } catch (PackageManager.NameNotFoundException e) {
298 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
299 }
300
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400301 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400302 mRestoredWithoutUids.put(pkg, r);
303 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400304 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400305 }
306 }
307 return r;
308 }
309 }
310
311 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
312 PackageManager.NameNotFoundException {
313 final int userId = UserHandle.getUserId(r.uid);
314 final ApplicationInfo applicationInfo =
315 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
316 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
317 // O apps should not have the default channel.
318 return false;
319 }
320
321 // Otherwise, this app should have the default channel.
322 return true;
323 }
324
325 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
326 PackageManager.NameNotFoundException {
327 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
328 // Not present
329 return;
330 }
331
332 if (shouldHaveDefaultChannel(r)) {
333 // Keep the default channel until upgraded.
334 return;
335 }
336
337 // Remove Default Channel.
338 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
339 }
340
341 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
342 PackageManager.NameNotFoundException {
343 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
344 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
345 com.android.internal.R.string.default_notification_channel_label));
346 return;
347 }
348
349 if (!shouldHaveDefaultChannel(r)) {
350 // Keep the default channel until upgraded.
351 return;
352 }
353
354 // Create Default Channel
355 NotificationChannel channel;
356 channel = new NotificationChannel(
357 NotificationChannel.DEFAULT_CHANNEL_ID,
358 mContext.getString(R.string.default_notification_channel_label),
359 r.importance);
360 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
361 channel.setLockscreenVisibility(r.visibility);
362 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
363 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
364 }
365 if (r.priority != DEFAULT_PRIORITY) {
366 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
367 }
368 if (r.visibility != DEFAULT_VISIBILITY) {
369 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
370 }
371 r.channels.put(channel.getId(), channel);
372 }
373
374 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
375 out.startTag(null, TAG_RANKING);
376 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
377
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400378 synchronized (mPackagePreferences) {
379 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400380 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400381 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400382 //TODO: http://b/22388012
383 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
384 continue;
385 }
386 final boolean hasNonDefaultSettings =
387 r.importance != DEFAULT_IMPORTANCE
388 || r.priority != DEFAULT_PRIORITY
389 || r.visibility != DEFAULT_VISIBILITY
390 || r.showBadge != DEFAULT_SHOW_BADGE
391 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
392 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400393 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500394 || r.delegate != null
395 || r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400396 if (hasNonDefaultSettings) {
397 out.startTag(null, TAG_PACKAGE);
398 out.attribute(null, ATT_NAME, r.pkg);
399 if (r.importance != DEFAULT_IMPORTANCE) {
400 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
401 }
402 if (r.priority != DEFAULT_PRIORITY) {
403 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
404 }
405 if (r.visibility != DEFAULT_VISIBILITY) {
406 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
407 }
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500408 if (r.appOverlay != DEFAULT_ALLOW_APP_OVERLAY) {
409 out.attribute(null, ATT_APP_OVERLAY, Boolean.toString(r.appOverlay));
410 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400411 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
412 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
413 Integer.toString(r.lockedAppFields));
414
415 if (!forBackup) {
416 out.attribute(null, ATT_UID, Integer.toString(r.uid));
417 }
418
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400419 if (r.delegate != null) {
420 out.startTag(null, TAG_DELEGATE);
421
422 out.attribute(null, ATT_NAME, r.delegate.mPkg);
423 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
424 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
425 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
426 }
427 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
428 out.attribute(null, ATT_USER_ALLOWED,
429 Boolean.toString(r.delegate.mUserAllowed));
430 }
431 out.endTag(null, TAG_DELEGATE);
432 }
433
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400434 for (NotificationChannelGroup group : r.groups.values()) {
435 group.writeXml(out);
436 }
437
438 for (NotificationChannel channel : r.channels.values()) {
439 if (forBackup) {
440 if (!channel.isDeleted()) {
441 channel.writeXmlForBackup(out, mContext);
442 }
443 } else {
444 channel.writeXml(out);
445 }
446 }
447
448 out.endTag(null, TAG_PACKAGE);
449 }
450 }
451 }
452 out.endTag(null, TAG_RANKING);
453 }
454
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500455 public void setAppOverlaysAllowed(String pkg, int uid, boolean allowed) {
456 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
457 p.appOverlay = allowed;
458 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_APP_OVERLAY;
459 }
460
461 public boolean areAppOverlaysAllowed(String pkg, int uid) {
462 return getOrCreatePackagePreferences(pkg, uid).appOverlay;
463 }
464
465 public int getAppLockedFields(String pkg, int uid) {
466 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
467 }
468
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400469 /**
470 * Gets importance.
471 */
472 @Override
473 public int getImportance(String packageName, int uid) {
474 return getOrCreatePackagePreferences(packageName, uid).importance;
475 }
476
477
478 /**
479 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
480 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
481 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
482 */
483 public boolean getIsAppImportanceLocked(String packageName, int uid) {
484 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
485 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
486 }
487
488 @Override
489 public boolean canShowBadge(String packageName, int uid) {
490 return getOrCreatePackagePreferences(packageName, uid).showBadge;
491 }
492
493 @Override
494 public void setShowBadge(String packageName, int uid, boolean showBadge) {
495 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
496 updateConfig();
497 }
498
499 @Override
500 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
501 if (groupId == null) {
502 return false;
503 }
504 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
505 NotificationChannelGroup group = r.groups.get(groupId);
506 if (group == null) {
507 return false;
508 }
509 return group.isBlocked();
510 }
511
512 int getPackagePriority(String pkg, int uid) {
513 return getOrCreatePackagePreferences(pkg, uid).priority;
514 }
515
516 int getPackageVisibility(String pkg, int uid) {
517 return getOrCreatePackagePreferences(pkg, uid).visibility;
518 }
519
520 @Override
521 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
522 boolean fromTargetApp) {
523 Preconditions.checkNotNull(pkg);
524 Preconditions.checkNotNull(group);
525 Preconditions.checkNotNull(group.getId());
526 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
527 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
528 if (r == null) {
529 throw new IllegalArgumentException("Invalid package");
530 }
531 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
532 if (!group.equals(oldGroup)) {
533 // will log for new entries as well as name/description changes
534 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
535 }
536 if (oldGroup != null) {
537 group.setChannels(oldGroup.getChannels());
538
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400539 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400540 if (fromTargetApp) {
541 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400542 group.unlockFields(group.getUserLockedFields());
543 group.lockFields(oldGroup.getUserLockedFields());
544 } else {
545 // but the system can
546 if (group.isBlocked() != oldGroup.isBlocked()) {
547 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500548 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400549 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400550 }
551 }
552 r.groups.put(group.getId(), group);
553 }
554
555 @Override
556 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
557 boolean fromTargetApp, boolean hasDndAccess) {
558 Preconditions.checkNotNull(pkg);
559 Preconditions.checkNotNull(channel);
560 Preconditions.checkNotNull(channel.getId());
561 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
562 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
563 if (r == null) {
564 throw new IllegalArgumentException("Invalid package");
565 }
566 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
567 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
568 }
569 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
570 throw new IllegalArgumentException("Reserved id");
571 }
572 NotificationChannel existing = r.channels.get(channel.getId());
573 // Keep most of the existing settings
574 if (existing != null && fromTargetApp) {
575 if (existing.isDeleted()) {
576 existing.setDeleted(false);
577
578 // log a resurrected channel as if it's new again
579 MetricsLogger.action(getChannelLog(channel, pkg).setType(
580 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
581 }
582
583 existing.setName(channel.getName().toString());
584 existing.setDescription(channel.getDescription());
585 existing.setBlockableSystem(channel.isBlockableSystem());
586 if (existing.getGroup() == null) {
587 existing.setGroup(channel.getGroup());
588 }
589
590 // Apps are allowed to downgrade channel importance if the user has not changed any
591 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500592 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400593 if (existing.getUserLockedFields() == 0 &&
594 channel.getImportance() < existing.getImportance()) {
595 existing.setImportance(channel.getImportance());
596 }
597
598 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
599 // fields on the channel yet
600 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
601 boolean bypassDnd = channel.canBypassDnd();
602 existing.setBypassDnd(bypassDnd);
603
Beverly0479cde22018-11-09 11:05:34 -0500604 if (bypassDnd != mAreChannelsBypassingDnd
605 || previousExistingImportance != existing.getImportance()) {
606 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400607 }
608 }
609
610 updateConfig();
611 return;
612 }
613 if (channel.getImportance() < IMPORTANCE_NONE
614 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
615 throw new IllegalArgumentException("Invalid importance level");
616 }
617
618 // Reset fields that apps aren't allowed to set.
619 if (fromTargetApp && !hasDndAccess) {
620 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
621 }
622 if (fromTargetApp) {
623 channel.setLockscreenVisibility(r.visibility);
624 }
625 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500626 channel.setImportanceLockedByOEM(r.oemLockedImportance);
627 if (!channel.isImportanceLockedByOEM()) {
628 if (r.futureOemLockedChannels.remove(channel.getId())) {
629 channel.setImportanceLockedByOEM(true);
630 }
631 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400632 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
633 channel.setLockscreenVisibility(
634 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
635 }
636 if (!r.showBadge) {
637 channel.setShowBadge(false);
638 }
639
640 r.channels.put(channel.getId(), channel);
641 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500642 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400643 }
644 MetricsLogger.action(getChannelLog(channel, pkg).setType(
645 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
646 }
647
648 void clearLockedFields(NotificationChannel channel) {
649 channel.unlockFields(channel.getUserLockedFields());
650 }
651
652 @Override
653 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
654 boolean fromUser) {
655 Preconditions.checkNotNull(updatedChannel);
656 Preconditions.checkNotNull(updatedChannel.getId());
657 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
658 if (r == null) {
659 throw new IllegalArgumentException("Invalid package");
660 }
661 NotificationChannel channel = r.channels.get(updatedChannel.getId());
662 if (channel == null || channel.isDeleted()) {
663 throw new IllegalArgumentException("Channel does not exist");
664 }
665 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
666 updatedChannel.setLockscreenVisibility(
667 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
668 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400669 if (fromUser) {
670 updatedChannel.lockFields(channel.getUserLockedFields());
671 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400672 } else {
673 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400674 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500675 // no importance updates are allowed if OEM blocked it
676 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
677 if (updatedChannel.isImportanceLockedByOEM()) {
678 updatedChannel.setImportance(channel.getImportance());
679 }
680
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400681 r.channels.put(updatedChannel.getId(), updatedChannel);
682
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400683 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400684 // copy settings to app level so they are inherited by new channels
685 // when the app migrates
686 r.importance = updatedChannel.getImportance();
687 r.priority = updatedChannel.canBypassDnd()
688 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
689 r.visibility = updatedChannel.getLockscreenVisibility();
690 r.showBadge = updatedChannel.canShowBadge();
691 }
692
693 if (!channel.equals(updatedChannel)) {
694 // only log if there are real changes
695 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
696 }
697
Beverly0479cde22018-11-09 11:05:34 -0500698 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
699 || channel.getImportance() != updatedChannel.getImportance()) {
700 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400701 }
702 updateConfig();
703 }
704
705 @Override
706 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
707 boolean includeDeleted) {
708 Preconditions.checkNotNull(pkg);
709 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
710 if (r == null) {
711 return null;
712 }
713 if (channelId == null) {
714 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
715 }
716 final NotificationChannel nc = r.channels.get(channelId);
717 if (nc != null && (includeDeleted || !nc.isDeleted())) {
718 return nc;
719 }
720 return null;
721 }
722
723 @Override
724 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
725 PackagePreferences r = getPackagePreferences(pkg, uid);
726 if (r == null) {
727 return;
728 }
729 NotificationChannel channel = r.channels.get(channelId);
730 if (channel != null) {
731 channel.setDeleted(true);
732 LogMaker lm = getChannelLog(channel, pkg);
733 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
734 MetricsLogger.action(lm);
735
736 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500737 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400738 }
739 }
740 }
741
742 @Override
743 @VisibleForTesting
744 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
745 Preconditions.checkNotNull(pkg);
746 Preconditions.checkNotNull(channelId);
747 PackagePreferences r = getPackagePreferences(pkg, uid);
748 if (r == null) {
749 return;
750 }
751 r.channels.remove(channelId);
752 }
753
754 @Override
755 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
756 Preconditions.checkNotNull(pkg);
757 PackagePreferences r = getPackagePreferences(pkg, uid);
758 if (r == null) {
759 return;
760 }
761 int N = r.channels.size() - 1;
762 for (int i = N; i >= 0; i--) {
763 String key = r.channels.keyAt(i);
764 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
765 r.channels.remove(key);
766 }
767 }
768 }
769
Julia Reynolds413ba842019-01-11 10:38:08 -0500770 public void lockChannelsForOEM(String[] appOrChannelList) {
771 if (appOrChannelList == null) {
772 return;
773 }
774 for (String appOrChannel : appOrChannelList) {
775 if (!TextUtils.isEmpty(appOrChannel)) {
776 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
777 if (appSplit != null && appSplit.length > 0) {
778 String appName = appSplit[0];
779 String channelId = appSplit.length == 2 ? appSplit[1] : null;
780
781 synchronized (mPackagePreferences) {
782 for (PackagePreferences r : mPackagePreferences.values()) {
783 if (r.pkg.equals(appName)) {
784 if (channelId == null) {
785 // lock all channels for the app
786 r.oemLockedImportance = true;
787 for (NotificationChannel channel : r.channels.values()) {
788 channel.setImportanceLockedByOEM(true);
789 }
790 } else {
791 NotificationChannel channel = r.channels.get(channelId);
792 if (channel != null) {
793 channel.setImportanceLockedByOEM(true);
794 } else {
795 // if this channel shows up in the future, make sure it'll
796 // be locked immediately
797 r.futureOemLockedChannels.add(channelId);
798 }
799 }
800 }
801 }
802 }
803 }
804 }
805 }
806 }
807
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400808 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
809 int uid, String groupId, boolean includeDeleted) {
810 Preconditions.checkNotNull(pkg);
811 PackagePreferences r = getPackagePreferences(pkg, uid);
812 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
813 return null;
814 }
815 NotificationChannelGroup group = r.groups.get(groupId).clone();
816 group.setChannels(new ArrayList<>());
817 int N = r.channels.size();
818 for (int i = 0; i < N; i++) {
819 final NotificationChannel nc = r.channels.valueAt(i);
820 if (includeDeleted || !nc.isDeleted()) {
821 if (groupId.equals(nc.getGroup())) {
822 group.addChannel(nc);
823 }
824 }
825 }
826 return group;
827 }
828
829 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
830 int uid) {
831 Preconditions.checkNotNull(pkg);
832 PackagePreferences r = getPackagePreferences(pkg, uid);
833 if (r == null) {
834 return null;
835 }
836 return r.groups.get(groupId);
837 }
838
839 @Override
840 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400841 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400842 Preconditions.checkNotNull(pkg);
843 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
844 PackagePreferences r = getPackagePreferences(pkg, uid);
845 if (r == null) {
846 return ParceledListSlice.emptyList();
847 }
848 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
849 int N = r.channels.size();
850 for (int i = 0; i < N; i++) {
851 final NotificationChannel nc = r.channels.valueAt(i);
852 if (includeDeleted || !nc.isDeleted()) {
853 if (nc.getGroup() != null) {
854 if (r.groups.get(nc.getGroup()) != null) {
855 NotificationChannelGroup ncg = groups.get(nc.getGroup());
856 if (ncg == null) {
857 ncg = r.groups.get(nc.getGroup()).clone();
858 ncg.setChannels(new ArrayList<>());
859 groups.put(nc.getGroup(), ncg);
860
861 }
862 ncg.addChannel(nc);
863 }
864 } else {
865 nonGrouped.addChannel(nc);
866 }
867 }
868 }
869 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
870 groups.put(null, nonGrouped);
871 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400872 if (includeEmpty) {
873 for (NotificationChannelGroup group : r.groups.values()) {
874 if (!groups.containsKey(group.getId())) {
875 groups.put(group.getId(), group);
876 }
877 }
878 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400879 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
880 }
881
882 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
883 String groupId) {
884 List<NotificationChannel> deletedChannels = new ArrayList<>();
885 PackagePreferences r = getPackagePreferences(pkg, uid);
886 if (r == null || TextUtils.isEmpty(groupId)) {
887 return deletedChannels;
888 }
889
890 r.groups.remove(groupId);
891
892 int N = r.channels.size();
893 for (int i = 0; i < N; i++) {
894 final NotificationChannel nc = r.channels.valueAt(i);
895 if (groupId.equals(nc.getGroup())) {
896 nc.setDeleted(true);
897 deletedChannels.add(nc);
898 }
899 }
900 return deletedChannels;
901 }
902
903 @Override
904 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
905 int uid) {
906 PackagePreferences r = getPackagePreferences(pkg, uid);
907 if (r == null) {
908 return new ArrayList<>();
909 }
910 return r.groups.values();
911 }
912
913 @Override
914 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
915 boolean includeDeleted) {
916 Preconditions.checkNotNull(pkg);
917 List<NotificationChannel> channels = new ArrayList<>();
918 PackagePreferences r = getPackagePreferences(pkg, uid);
919 if (r == null) {
920 return ParceledListSlice.emptyList();
921 }
922 int N = r.channels.size();
923 for (int i = 0; i < N; i++) {
924 final NotificationChannel nc = r.channels.valueAt(i);
925 if (includeDeleted || !nc.isDeleted()) {
926 channels.add(nc);
927 }
928 }
929 return new ParceledListSlice<>(channels);
930 }
931
932 /**
Beverly0479cde22018-11-09 11:05:34 -0500933 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
934 */
935 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
936 int userId) {
937 List<NotificationChannel> channels = new ArrayList<>();
938 synchronized (mPackagePreferences) {
939 final PackagePreferences r = mPackagePreferences.get(
940 packagePreferencesKey(pkg, userId));
941 // notifications from this package aren't blocked
942 if (r != null && r.importance != IMPORTANCE_NONE) {
943 for (NotificationChannel channel : r.channels.values()) {
944 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
945 channels.add(channel);
946 }
947 }
948 }
949 }
950 return new ParceledListSlice<>(channels);
951 }
952
953 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400954 * True for pre-O apps that only have the default channel, or pre O apps that have no
955 * channels yet. This method will create the default channel for pre-O apps that don't have it.
956 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
957 * upgrades.
958 */
959 public boolean onlyHasDefaultChannel(String pkg, int uid) {
960 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
961 if (r.channels.size() == 1
962 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
963 return true;
964 }
965 return false;
966 }
967
968 public int getDeletedChannelCount(String pkg, int uid) {
969 Preconditions.checkNotNull(pkg);
970 int deletedCount = 0;
971 PackagePreferences r = getPackagePreferences(pkg, uid);
972 if (r == null) {
973 return deletedCount;
974 }
975 int N = r.channels.size();
976 for (int i = 0; i < N; i++) {
977 final NotificationChannel nc = r.channels.valueAt(i);
978 if (nc.isDeleted()) {
979 deletedCount++;
980 }
981 }
982 return deletedCount;
983 }
984
985 public int getBlockedChannelCount(String pkg, int uid) {
986 Preconditions.checkNotNull(pkg);
987 int blockedCount = 0;
988 PackagePreferences r = getPackagePreferences(pkg, uid);
989 if (r == null) {
990 return blockedCount;
991 }
992 int N = r.channels.size();
993 for (int i = 0; i < N; i++) {
994 final NotificationChannel nc = r.channels.valueAt(i);
995 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
996 blockedCount++;
997 }
998 }
999 return blockedCount;
1000 }
1001
1002 public int getBlockedAppCount(int userId) {
1003 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001004 synchronized (mPackagePreferences) {
1005 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001006 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001007 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001008 if (userId == UserHandle.getUserId(r.uid)
1009 && r.importance == IMPORTANCE_NONE) {
1010 count++;
1011 }
1012 }
1013 }
1014 return count;
1015 }
1016
Beverly0479cde22018-11-09 11:05:34 -05001017 /**
1018 * Returns the number of apps that have at least one notification channel that can bypass DND
1019 * for given particular user
1020 */
1021 public int getAppsBypassingDndCount(int userId) {
1022 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001023 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001024 final int numPackagePreferences = mPackagePreferences.size();
1025 for (int i = 0; i < numPackagePreferences; i++) {
1026 final PackagePreferences r = mPackagePreferences.valueAt(i);
1027 // Package isn't associated with this userId or notifications from this package are
1028 // blocked
1029 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1030 continue;
1031 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001032
Beverly0479cde22018-11-09 11:05:34 -05001033 for (NotificationChannel channel : r.channels.values()) {
1034 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1035 count++;
1036 break;
1037 }
1038 }
1039 }
1040 }
1041 return count;
1042 }
1043
1044 /**
1045 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1046 * updating
1047 * @param userId
1048 */
1049 private void syncChannelsBypassingDnd(int userId) {
1050 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1051 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1052 updateChannelsBypassingDnd(userId);
1053 }
1054
1055 /**
1056 * Updates the user's NotificationPolicy based on whether the given userId
1057 * has channels bypassing DND
1058 * @param userId
1059 */
1060 private void updateChannelsBypassingDnd(int userId) {
1061 synchronized (mPackagePreferences) {
1062 final int numPackagePreferences = mPackagePreferences.size();
1063 for (int i = 0; i < numPackagePreferences; i++) {
1064 final PackagePreferences r = mPackagePreferences.valueAt(i);
1065 // Package isn't associated with this userId or notifications from this package are
1066 // blocked
1067 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1068 continue;
1069 }
1070
1071 for (NotificationChannel channel : r.channels.values()) {
1072 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001073 if (!mAreChannelsBypassingDnd) {
1074 mAreChannelsBypassingDnd = true;
1075 updateZenPolicy(true);
1076 }
1077 return;
1078 }
1079 }
1080 }
1081 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001082 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1083 if (mAreChannelsBypassingDnd) {
1084 mAreChannelsBypassingDnd = false;
1085 updateZenPolicy(false);
1086 }
1087 }
1088
Beverly0479cde22018-11-09 11:05:34 -05001089 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1090 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001091 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1092 return false;
Beverly0479cde22018-11-09 11:05:34 -05001093 }
1094
1095 // Channel is deleted or is blocked
1096 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1097 return false;
1098 }
1099
1100 return true;
1101 }
1102
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001103 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1104 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1105 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1106 policy.priorityCategories, policy.priorityCallSenders,
1107 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1108 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1109 : 0)));
1110 }
1111
1112 public boolean areChannelsBypassingDnd() {
1113 return mAreChannelsBypassingDnd;
1114 }
1115
1116 /**
1117 * Sets importance.
1118 */
1119 @Override
1120 public void setImportance(String pkgName, int uid, int importance) {
1121 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1122 updateConfig();
1123 }
1124
1125 public void setEnabled(String packageName, int uid, boolean enabled) {
1126 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1127 if (wasEnabled == enabled) {
1128 return;
1129 }
1130 setImportance(packageName, uid,
1131 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1132 }
1133
1134 /**
1135 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1136 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1137 * considered for sentiment adjustments (and thus never show a blocking helper).
1138 */
1139 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001140 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1141 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001142 return;
1143 }
1144
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001145 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001146 updateConfig();
1147 }
1148
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001149 /**
1150 * Returns the delegate for a given package, if it's allowed by the package and the user.
1151 */
1152 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1153 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1154
1155 if (prefs == null || prefs.delegate == null) {
1156 return null;
1157 }
1158 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1159 return null;
1160 }
1161 return prefs.delegate.mPkg;
1162 }
1163
1164 /**
1165 * Used by an app to delegate notification posting privileges to another apps.
1166 */
1167 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1168 String delegatePkg, int delegateUid) {
1169 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1170
1171 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1172 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1173 prefs.delegate = delegate;
1174 updateConfig();
1175 }
1176
1177 /**
1178 * Used by an app to turn off its notification delegate.
1179 */
1180 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1181 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1182 if (prefs != null && prefs.delegate != null) {
1183 prefs.delegate.mEnabled = false;
1184 updateConfig();
1185 }
1186 }
1187
1188 /**
1189 * Toggles whether an app can have a notification delegate on behalf of a user.
1190 */
1191 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1192 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1193 if (prefs != null && prefs.delegate != null) {
1194 prefs.delegate.mUserAllowed = userAllowed;
1195 updateConfig();
1196 }
1197 }
1198
1199 /**
1200 * Returns whether the given app is allowed on post notifications on behalf of the other given
1201 * app.
1202 */
1203 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1204 String potentialDelegatePkg, int potentialDelegateUid) {
1205 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1206
1207 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1208 }
1209
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001210 @VisibleForTesting
1211 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1212 if (original.canBypassDnd() != update.canBypassDnd()) {
1213 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1214 }
1215 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1216 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1217 }
1218 if (original.getImportance() != update.getImportance()) {
1219 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1220 }
1221 if (original.shouldShowLights() != update.shouldShowLights()
1222 || original.getLightColor() != update.getLightColor()) {
1223 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1224 }
1225 if (!Objects.equals(original.getSound(), update.getSound())) {
1226 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1227 }
1228 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1229 || original.shouldVibrate() != update.shouldVibrate()) {
1230 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1231 }
1232 if (original.canShowBadge() != update.canShowBadge()) {
1233 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1234 }
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001235 if (original.isAppOverlayAllowed() != update.isAppOverlayAllowed()) {
1236 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY);
1237 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001238 }
1239
1240 public void dump(PrintWriter pw, String prefix,
1241 @NonNull NotificationManagerService.DumpFilter filter) {
1242 pw.print(prefix);
1243 pw.println("per-package config:");
1244
1245 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001246 synchronized (mPackagePreferences) {
1247 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001248 }
1249 pw.println("Restored without uid:");
1250 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1251 }
1252
1253 public void dump(ProtoOutputStream proto,
1254 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001255 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001256 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001257 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001258 }
1259 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1260 mRestoredWithoutUids);
1261 }
1262
1263 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1264 @NonNull NotificationManagerService.DumpFilter filter,
1265 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1266 final int N = PackagePreferencess.size();
1267 for (int i = 0; i < N; i++) {
1268 final PackagePreferences r = PackagePreferencess.valueAt(i);
1269 if (filter.matches(r.pkg)) {
1270 pw.print(prefix);
1271 pw.print(" AppSettings: ");
1272 pw.print(r.pkg);
1273 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001274 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001275 pw.print(')');
1276 if (r.importance != DEFAULT_IMPORTANCE) {
1277 pw.print(" importance=");
1278 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1279 }
1280 if (r.priority != DEFAULT_PRIORITY) {
1281 pw.print(" priority=");
1282 pw.print(Notification.priorityToString(r.priority));
1283 }
1284 if (r.visibility != DEFAULT_VISIBILITY) {
1285 pw.print(" visibility=");
1286 pw.print(Notification.visibilityToString(r.visibility));
1287 }
1288 pw.print(" showBadge=");
1289 pw.print(Boolean.toString(r.showBadge));
1290 pw.println();
1291 for (NotificationChannel channel : r.channels.values()) {
1292 pw.print(prefix);
1293 channel.dump(pw, " ", filter.redact);
1294 }
1295 for (NotificationChannelGroup group : r.groups.values()) {
1296 pw.print(prefix);
1297 pw.print(" ");
1298 pw.print(" ");
1299 pw.println(group);
1300 }
1301 }
1302 }
1303 }
1304
1305 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1306 @NonNull NotificationManagerService.DumpFilter filter,
1307 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1308 final int N = PackagePreferencess.size();
1309 long fToken;
1310 for (int i = 0; i < N; i++) {
1311 final PackagePreferences r = PackagePreferencess.valueAt(i);
1312 if (filter.matches(r.pkg)) {
1313 fToken = proto.start(fieldId);
1314
1315 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1316 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1317 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1318 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1319 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1320 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1321
1322 for (NotificationChannel channel : r.channels.values()) {
1323 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1324 }
1325 for (NotificationChannelGroup group : r.groups.values()) {
1326 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1327 }
1328
1329 proto.end(fToken);
1330 }
1331 }
1332 }
1333
1334 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1335 JSONObject ranking = new JSONObject();
1336 JSONArray PackagePreferencess = new JSONArray();
1337 try {
1338 ranking.put("noUid", mRestoredWithoutUids.size());
1339 } catch (JSONException e) {
1340 // pass
1341 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001342 synchronized (mPackagePreferences) {
1343 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001344 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001345 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001346 if (filter == null || filter.matches(r.pkg)) {
1347 JSONObject PackagePreferences = new JSONObject();
1348 try {
1349 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1350 PackagePreferences.put("packageName", r.pkg);
1351 if (r.importance != DEFAULT_IMPORTANCE) {
1352 PackagePreferences.put("importance",
1353 NotificationListenerService.Ranking.importanceToString(
1354 r.importance));
1355 }
1356 if (r.priority != DEFAULT_PRIORITY) {
1357 PackagePreferences.put("priority",
1358 Notification.priorityToString(r.priority));
1359 }
1360 if (r.visibility != DEFAULT_VISIBILITY) {
1361 PackagePreferences.put("visibility",
1362 Notification.visibilityToString(r.visibility));
1363 }
1364 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1365 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1366 }
1367 JSONArray channels = new JSONArray();
1368 for (NotificationChannel channel : r.channels.values()) {
1369 channels.put(channel.toJson());
1370 }
1371 PackagePreferences.put("channels", channels);
1372 JSONArray groups = new JSONArray();
1373 for (NotificationChannelGroup group : r.groups.values()) {
1374 groups.put(group.toJson());
1375 }
1376 PackagePreferences.put("groups", groups);
1377 } catch (JSONException e) {
1378 // pass
1379 }
1380 PackagePreferencess.put(PackagePreferences);
1381 }
1382 }
1383 }
1384 try {
1385 ranking.put("PackagePreferencess", PackagePreferencess);
1386 } catch (JSONException e) {
1387 // pass
1388 }
1389 return ranking;
1390 }
1391
1392 /**
1393 * Dump only the ban information as structured JSON for the stats collector.
1394 *
1395 * This is intentionally redundant with {#link dumpJson} because the old
1396 * scraper will expect this format.
1397 *
1398 * @param filter
1399 * @return
1400 */
1401 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1402 JSONArray bans = new JSONArray();
1403 Map<Integer, String> packageBans = getPackageBans();
1404 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1405 final int userId = UserHandle.getUserId(ban.getKey());
1406 final String packageName = ban.getValue();
1407 if (filter == null || filter.matches(packageName)) {
1408 JSONObject banJson = new JSONObject();
1409 try {
1410 banJson.put("userId", userId);
1411 banJson.put("packageName", packageName);
1412 } catch (JSONException e) {
1413 e.printStackTrace();
1414 }
1415 bans.put(banJson);
1416 }
1417 }
1418 return bans;
1419 }
1420
1421 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001422 synchronized (mPackagePreferences) {
1423 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001424 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1425 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001426 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001427 if (r.importance == IMPORTANCE_NONE) {
1428 packageBans.put(r.uid, r.pkg);
1429 }
1430 }
1431
1432 return packageBans;
1433 }
1434 }
1435
1436 /**
1437 * Dump only the channel information as structured JSON for the stats collector.
1438 *
1439 * This is intentionally redundant with {#link dumpJson} because the old
1440 * scraper will expect this format.
1441 *
1442 * @param filter
1443 * @return
1444 */
1445 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1446 JSONArray channels = new JSONArray();
1447 Map<String, Integer> packageChannels = getPackageChannels();
1448 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1449 final String packageName = channelCount.getKey();
1450 if (filter == null || filter.matches(packageName)) {
1451 JSONObject channelCountJson = new JSONObject();
1452 try {
1453 channelCountJson.put("packageName", packageName);
1454 channelCountJson.put("channelCount", channelCount.getValue());
1455 } catch (JSONException e) {
1456 e.printStackTrace();
1457 }
1458 channels.put(channelCountJson);
1459 }
1460 }
1461 return channels;
1462 }
1463
1464 private Map<String, Integer> getPackageChannels() {
1465 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001466 synchronized (mPackagePreferences) {
1467 for (int i = 0; i < mPackagePreferences.size(); i++) {
1468 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001469 int channelCount = 0;
1470 for (int j = 0; j < r.channels.size(); j++) {
1471 if (!r.channels.valueAt(j).isDeleted()) {
1472 channelCount++;
1473 }
1474 }
1475 packageChannels.put(r.pkg, channelCount);
1476 }
1477 }
1478 return packageChannels;
1479 }
1480
Beverly0479cde22018-11-09 11:05:34 -05001481 /**
1482 * Called when user switches
1483 */
1484 public void onUserSwitched(int userId) {
1485 syncChannelsBypassingDnd(userId);
1486 }
1487
1488 /**
1489 * Called when user is unlocked
1490 */
1491 public void onUserUnlocked(int userId) {
1492 syncChannelsBypassingDnd(userId);
1493 }
1494
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001495 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001496 synchronized (mPackagePreferences) {
1497 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001498 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001499 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001500 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001501 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001502 }
1503 }
1504 }
1505 }
1506
1507 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001508 synchronized (mPackagePreferences) {
1509 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001510 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001511 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001512 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1513 if (PackagePreferences.channels.containsKey(
1514 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1515 PackagePreferences.channels.get(
1516 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1517 context.getResources().getString(
1518 R.string.default_notification_channel_label));
1519 }
1520 }
1521 }
1522 }
1523 }
1524
1525 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1526 int[] uidList) {
1527 if (pkgList == null || pkgList.length == 0) {
1528 return; // nothing to do
1529 }
1530 boolean updated = false;
1531 if (removingPackage) {
1532 // Remove notification settings for uninstalled package
1533 int size = Math.min(pkgList.length, uidList.length);
1534 for (int i = 0; i < size; i++) {
1535 final String pkg = pkgList[i];
1536 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001537 synchronized (mPackagePreferences) {
1538 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001539 }
1540 mRestoredWithoutUids.remove(pkg);
1541 updated = true;
1542 }
1543 } else {
1544 for (String pkg : pkgList) {
1545 // Package install
1546 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1547 if (r != null) {
1548 try {
1549 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1550 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001551 synchronized (mPackagePreferences) {
1552 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001553 }
1554 updated = true;
1555 } catch (PackageManager.NameNotFoundException e) {
1556 // noop
1557 }
1558 }
1559 // Package upgrade
1560 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001561 synchronized (mPackagePreferences) {
1562 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1563 mPm.getPackageUidAsUser(pkg, changeUserId));
1564 if (fullPackagePreferences != null) {
1565 createDefaultChannelIfNeeded(fullPackagePreferences);
1566 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1567 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001568 }
1569 } catch (PackageManager.NameNotFoundException e) {
1570 }
1571 }
1572 }
1573
1574 if (updated) {
1575 updateConfig();
1576 }
1577 }
1578
1579 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1580 return new LogMaker(
1581 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1582 .ACTION_NOTIFICATION_CHANNEL)
1583 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1584 .setPackageName(pkg)
1585 .addTaggedData(
1586 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1587 .FIELD_NOTIFICATION_CHANNEL_ID,
1588 channel.getId())
1589 .addTaggedData(
1590 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1591 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1592 channel.getImportance());
1593 }
1594
1595 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1596 return new LogMaker(
1597 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1598 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1599 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1600 .addTaggedData(
1601 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1602 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1603 groupId)
1604 .setPackageName(pkg);
1605 }
1606
1607
1608 public void updateBadgingEnabled() {
1609 if (mBadgingEnabled == null) {
1610 mBadgingEnabled = new SparseBooleanArray();
1611 }
1612 boolean changed = false;
1613 // update the cached values
1614 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1615 int userId = mBadgingEnabled.keyAt(index);
1616 final boolean oldValue = mBadgingEnabled.get(userId);
1617 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1618 Settings.Secure.NOTIFICATION_BADGING,
1619 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1620 mBadgingEnabled.put(userId, newValue);
1621 changed |= oldValue != newValue;
1622 }
1623 if (changed) {
1624 updateConfig();
1625 }
1626 }
1627
1628 public boolean badgingEnabled(UserHandle userHandle) {
1629 int userId = userHandle.getIdentifier();
1630 if (userId == UserHandle.USER_ALL) {
1631 return false;
1632 }
1633 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1634 mBadgingEnabled.put(userId,
1635 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1636 Settings.Secure.NOTIFICATION_BADGING,
1637 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1638 }
1639 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1640 }
1641
1642 private void updateConfig() {
1643 mRankingHandler.requestSort();
1644 }
1645
1646 private static String packagePreferencesKey(String pkg, int uid) {
1647 return pkg + "|" + uid;
1648 }
1649
1650 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001651 String pkg;
1652 int uid = UNKNOWN_UID;
1653 int importance = DEFAULT_IMPORTANCE;
1654 int priority = DEFAULT_PRIORITY;
1655 int visibility = DEFAULT_VISIBILITY;
1656 boolean showBadge = DEFAULT_SHOW_BADGE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -05001657 boolean appOverlay = DEFAULT_ALLOW_APP_OVERLAY;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001658 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001659 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1660 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001661
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001662 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001663 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1664 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001665
1666 public boolean isValidDelegate(String pkg, int uid) {
1667 return delegate != null && delegate.isAllowed(pkg, uid);
1668 }
1669 }
1670
1671 private static class Delegate {
1672 static final boolean DEFAULT_ENABLED = true;
1673 static final boolean DEFAULT_USER_ALLOWED = true;
1674 String mPkg;
1675 int mUid = UNKNOWN_UID;
1676 boolean mEnabled = DEFAULT_ENABLED;
1677 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1678
1679 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1680 mPkg = pkg;
1681 mUid = uid;
1682 mEnabled = enabled;
1683 mUserAllowed = userAllowed;
1684 }
1685
1686 public boolean isAllowed(String pkg, int uid) {
1687 if (pkg == null || uid == UNKNOWN_UID) {
1688 return false;
1689 }
1690 return pkg.equals(mPkg)
1691 && uid == mUid
1692 && (mUserAllowed && mEnabled);
1693 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001694 }
1695}