blob: eb46d53b515766c942467e0efad9fd60f2db8275 [file] [log] [blame]
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001/**
2 * Copyright (c) 2018, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.notification;
18
19import static android.app.NotificationManager.IMPORTANCE_NONE;
20
21import android.annotation.IntDef;
22import android.annotation.NonNull;
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040023import android.annotation.Nullable;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040024import android.app.Notification;
25import android.app.NotificationChannel;
26import android.app.NotificationChannelGroup;
27import android.app.NotificationManager;
28import android.content.Context;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ParceledListSlice;
32import android.metrics.LogMaker;
33import android.os.Build;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.service.notification.NotificationListenerService;
37import android.service.notification.RankingHelperProto;
38import android.text.TextUtils;
39import android.util.ArrayMap;
40import android.util.Slog;
41import android.util.SparseBooleanArray;
42import android.util.proto.ProtoOutputStream;
43
44import com.android.internal.R;
45import com.android.internal.annotations.VisibleForTesting;
46import com.android.internal.logging.MetricsLogger;
47import com.android.internal.util.Preconditions;
48import com.android.internal.util.XmlUtils;
49
50import org.json.JSONArray;
51import org.json.JSONException;
52import org.json.JSONObject;
53import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55import org.xmlpull.v1.XmlSerializer;
56
57import java.io.IOException;
58import java.io.PrintWriter;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.Collection;
62import java.util.List;
63import java.util.Map;
64import java.util.Objects;
65import java.util.concurrent.ConcurrentHashMap;
66
67public class PreferencesHelper implements RankingConfig {
68 private static final String TAG = "NotificationPrefHelper";
69 private static final int XML_VERSION = 1;
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040070 private static final int UNKNOWN_UID = UserHandle.USER_NULL;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040071
72 @VisibleForTesting
73 static final String TAG_RANKING = "ranking";
74 private static final String TAG_PACKAGE = "package";
75 private static final String TAG_CHANNEL = "channel";
76 private static final String TAG_GROUP = "channelGroup";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040077 private static final String TAG_DELEGATE = "delegate";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040078
79 private static final String ATT_VERSION = "version";
80 private static final String ATT_NAME = "name";
81 private static final String ATT_UID = "uid";
82 private static final String ATT_ID = "id";
83 private static final String ATT_PRIORITY = "priority";
84 private static final String ATT_VISIBILITY = "visibility";
85 private static final String ATT_IMPORTANCE = "importance";
86 private static final String ATT_SHOW_BADGE = "show_badge";
87 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040088 private static final String ATT_ENABLED = "enabled";
89 private static final String ATT_USER_ALLOWED = "allowed";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040090
91 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
92 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
93 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
94 private static final boolean DEFAULT_SHOW_BADGE = true;
95 /**
96 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
97 * fields.
98 */
99 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
100
101 /**
102 * All user-lockable fields for a given application.
103 */
104 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
105 public @interface LockableAppFields {
106 int USER_LOCKED_IMPORTANCE = 0x00000001;
107 }
108
109 // pkg|uid => PackagePreferences
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400110 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400111 // pkg => PackagePreferences
112 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
113
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400114 private final Context mContext;
115 private final PackageManager mPm;
116 private final RankingHandler mRankingHandler;
117 private final ZenModeHelper mZenModeHelper;
118
119 private SparseBooleanArray mBadgingEnabled;
120 private boolean mAreChannelsBypassingDnd;
121
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400122 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
123 ZenModeHelper zenHelper) {
124 mContext = context;
125 mZenModeHelper = zenHelper;
126 mRankingHandler = rankingHandler;
127 mPm = pm;
128
129 updateBadgingEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500130 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400131 }
132
133 public void readXml(XmlPullParser parser, boolean forRestore)
134 throws XmlPullParserException, IOException {
135 int type = parser.getEventType();
136 if (type != XmlPullParser.START_TAG) return;
137 String tag = parser.getName();
138 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400139 synchronized (mPackagePreferences) {
140 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400141
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400142 // at the time of serialization.
143 mRestoredWithoutUids.clear();
144 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
145 tag = parser.getName();
146 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
147 return;
148 }
149 if (type == XmlPullParser.START_TAG) {
150 if (TAG_PACKAGE.equals(tag)) {
151 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
152 String name = parser.getAttributeValue(null, ATT_NAME);
153 if (!TextUtils.isEmpty(name)) {
154 if (forRestore) {
155 try {
156 //TODO: http://b/22388012
157 uid = mPm.getPackageUidAsUser(name,
158 UserHandle.USER_SYSTEM);
159 } catch (PackageManager.NameNotFoundException e) {
160 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400161 }
162 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400163
164 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
165 XmlUtils.readIntAttribute(
166 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
167 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
168 DEFAULT_PRIORITY),
169 XmlUtils.readIntAttribute(
170 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
171 XmlUtils.readBooleanAttribute(
172 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
173 r.importance = XmlUtils.readIntAttribute(
174 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
175 r.priority = XmlUtils.readIntAttribute(
176 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
177 r.visibility = XmlUtils.readIntAttribute(
178 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
179 r.showBadge = XmlUtils.readBooleanAttribute(
180 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
181 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
182 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
183
184 final int innerDepth = parser.getDepth();
185 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
186 && (type != XmlPullParser.END_TAG
187 || parser.getDepth() > innerDepth)) {
188 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
189 continue;
190 }
191
192 String tagName = parser.getName();
193 // Channel groups
194 if (TAG_GROUP.equals(tagName)) {
195 String id = parser.getAttributeValue(null, ATT_ID);
196 CharSequence groupName = parser.getAttributeValue(null,
197 ATT_NAME);
198 if (!TextUtils.isEmpty(id)) {
199 NotificationChannelGroup group
200 = new NotificationChannelGroup(id, groupName);
201 group.populateFromXml(parser);
202 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400203 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400204 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400205 // Channels
206 if (TAG_CHANNEL.equals(tagName)) {
207 String id = parser.getAttributeValue(null, ATT_ID);
208 String channelName = parser.getAttributeValue(null, ATT_NAME);
209 int channelImportance = XmlUtils.readIntAttribute(
210 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
211 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
212 NotificationChannel channel = new NotificationChannel(id,
213 channelName, channelImportance);
214 if (forRestore) {
215 channel.populateFromXmlForRestore(parser, mContext);
216 } else {
217 channel.populateFromXml(parser);
218 }
219 r.channels.put(id, channel);
220 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400221 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400222 // Delegate
223 if (TAG_DELEGATE.equals(tagName)) {
224 int delegateId =
225 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
226 String delegateName =
227 XmlUtils.readStringAttribute(parser, ATT_NAME);
228 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
229 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
230 boolean userAllowed = XmlUtils.readBooleanAttribute(
231 parser, ATT_USER_ALLOWED,
232 Delegate.DEFAULT_USER_ALLOWED);
233 Delegate d = null;
234 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
235 delegateName)) {
236 d = new Delegate(
237 delegateName, delegateId, delegateEnabled,
238 userAllowed);
239 }
240 r.delegate = d;
241 }
242
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400243 }
244
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400245 try {
246 deleteDefaultChannelIfNeeded(r);
247 } catch (PackageManager.NameNotFoundException e) {
248 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
249 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400250 }
251 }
252 }
253 }
254 }
255 throw new IllegalStateException("Failed to reach END_DOCUMENT");
256 }
257
258 private PackagePreferences getPackagePreferences(String pkg, int uid) {
259 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400260 synchronized (mPackagePreferences) {
261 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400262 }
263 }
264
265 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
266 return getOrCreatePackagePreferences(pkg, uid,
267 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
268 }
269
270 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
271 int priority, int visibility, boolean showBadge) {
272 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400273 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400274 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400275 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400276 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400277 if (r == null) {
278 r = new PackagePreferences();
279 r.pkg = pkg;
280 r.uid = uid;
281 r.importance = importance;
282 r.priority = priority;
283 r.visibility = visibility;
284 r.showBadge = showBadge;
285
286 try {
287 createDefaultChannelIfNeeded(r);
288 } catch (PackageManager.NameNotFoundException e) {
289 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
290 }
291
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400292 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400293 mRestoredWithoutUids.put(pkg, r);
294 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400295 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400296 }
297 }
298 return r;
299 }
300 }
301
302 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
303 PackageManager.NameNotFoundException {
304 final int userId = UserHandle.getUserId(r.uid);
305 final ApplicationInfo applicationInfo =
306 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
307 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
308 // O apps should not have the default channel.
309 return false;
310 }
311
312 // Otherwise, this app should have the default channel.
313 return true;
314 }
315
316 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
317 PackageManager.NameNotFoundException {
318 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
319 // Not present
320 return;
321 }
322
323 if (shouldHaveDefaultChannel(r)) {
324 // Keep the default channel until upgraded.
325 return;
326 }
327
328 // Remove Default Channel.
329 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
330 }
331
332 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
333 PackageManager.NameNotFoundException {
334 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
335 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
336 com.android.internal.R.string.default_notification_channel_label));
337 return;
338 }
339
340 if (!shouldHaveDefaultChannel(r)) {
341 // Keep the default channel until upgraded.
342 return;
343 }
344
345 // Create Default Channel
346 NotificationChannel channel;
347 channel = new NotificationChannel(
348 NotificationChannel.DEFAULT_CHANNEL_ID,
349 mContext.getString(R.string.default_notification_channel_label),
350 r.importance);
351 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
352 channel.setLockscreenVisibility(r.visibility);
353 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
354 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
355 }
356 if (r.priority != DEFAULT_PRIORITY) {
357 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
358 }
359 if (r.visibility != DEFAULT_VISIBILITY) {
360 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
361 }
362 r.channels.put(channel.getId(), channel);
363 }
364
365 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
366 out.startTag(null, TAG_RANKING);
367 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
368
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400369 synchronized (mPackagePreferences) {
370 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400371 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400372 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400373 //TODO: http://b/22388012
374 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
375 continue;
376 }
377 final boolean hasNonDefaultSettings =
378 r.importance != DEFAULT_IMPORTANCE
379 || r.priority != DEFAULT_PRIORITY
380 || r.visibility != DEFAULT_VISIBILITY
381 || r.showBadge != DEFAULT_SHOW_BADGE
382 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
383 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400384 || r.groups.size() > 0
385 || r.delegate != null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400386 if (hasNonDefaultSettings) {
387 out.startTag(null, TAG_PACKAGE);
388 out.attribute(null, ATT_NAME, r.pkg);
389 if (r.importance != DEFAULT_IMPORTANCE) {
390 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
391 }
392 if (r.priority != DEFAULT_PRIORITY) {
393 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
394 }
395 if (r.visibility != DEFAULT_VISIBILITY) {
396 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
397 }
398 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
399 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
400 Integer.toString(r.lockedAppFields));
401
402 if (!forBackup) {
403 out.attribute(null, ATT_UID, Integer.toString(r.uid));
404 }
405
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400406 if (r.delegate != null) {
407 out.startTag(null, TAG_DELEGATE);
408
409 out.attribute(null, ATT_NAME, r.delegate.mPkg);
410 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
411 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
412 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
413 }
414 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
415 out.attribute(null, ATT_USER_ALLOWED,
416 Boolean.toString(r.delegate.mUserAllowed));
417 }
418 out.endTag(null, TAG_DELEGATE);
419 }
420
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400421 for (NotificationChannelGroup group : r.groups.values()) {
422 group.writeXml(out);
423 }
424
425 for (NotificationChannel channel : r.channels.values()) {
426 if (forBackup) {
427 if (!channel.isDeleted()) {
428 channel.writeXmlForBackup(out, mContext);
429 }
430 } else {
431 channel.writeXml(out);
432 }
433 }
434
435 out.endTag(null, TAG_PACKAGE);
436 }
437 }
438 }
439 out.endTag(null, TAG_RANKING);
440 }
441
442 /**
443 * Gets importance.
444 */
445 @Override
446 public int getImportance(String packageName, int uid) {
447 return getOrCreatePackagePreferences(packageName, uid).importance;
448 }
449
450
451 /**
452 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
453 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
454 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
455 */
456 public boolean getIsAppImportanceLocked(String packageName, int uid) {
457 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
458 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
459 }
460
461 @Override
462 public boolean canShowBadge(String packageName, int uid) {
463 return getOrCreatePackagePreferences(packageName, uid).showBadge;
464 }
465
466 @Override
467 public void setShowBadge(String packageName, int uid, boolean showBadge) {
468 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
469 updateConfig();
470 }
471
472 @Override
473 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
474 if (groupId == null) {
475 return false;
476 }
477 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
478 NotificationChannelGroup group = r.groups.get(groupId);
479 if (group == null) {
480 return false;
481 }
482 return group.isBlocked();
483 }
484
485 int getPackagePriority(String pkg, int uid) {
486 return getOrCreatePackagePreferences(pkg, uid).priority;
487 }
488
489 int getPackageVisibility(String pkg, int uid) {
490 return getOrCreatePackagePreferences(pkg, uid).visibility;
491 }
492
493 @Override
494 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
495 boolean fromTargetApp) {
496 Preconditions.checkNotNull(pkg);
497 Preconditions.checkNotNull(group);
498 Preconditions.checkNotNull(group.getId());
499 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
500 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
501 if (r == null) {
502 throw new IllegalArgumentException("Invalid package");
503 }
504 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
505 if (!group.equals(oldGroup)) {
506 // will log for new entries as well as name/description changes
507 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
508 }
509 if (oldGroup != null) {
510 group.setChannels(oldGroup.getChannels());
511
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400512 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400513 if (fromTargetApp) {
514 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400515 group.setAllowAppOverlay(oldGroup.canOverlayApps());
516 group.unlockFields(group.getUserLockedFields());
517 group.lockFields(oldGroup.getUserLockedFields());
518 } else {
519 // but the system can
520 if (group.isBlocked() != oldGroup.isBlocked()) {
521 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500522 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400523 }
524 if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
525 group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
526 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400527 }
528 }
529 r.groups.put(group.getId(), group);
530 }
531
532 @Override
533 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
534 boolean fromTargetApp, boolean hasDndAccess) {
535 Preconditions.checkNotNull(pkg);
536 Preconditions.checkNotNull(channel);
537 Preconditions.checkNotNull(channel.getId());
538 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
539 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
540 if (r == null) {
541 throw new IllegalArgumentException("Invalid package");
542 }
543 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
544 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
545 }
546 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
547 throw new IllegalArgumentException("Reserved id");
548 }
549 NotificationChannel existing = r.channels.get(channel.getId());
550 // Keep most of the existing settings
551 if (existing != null && fromTargetApp) {
552 if (existing.isDeleted()) {
553 existing.setDeleted(false);
554
555 // log a resurrected channel as if it's new again
556 MetricsLogger.action(getChannelLog(channel, pkg).setType(
557 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
558 }
559
560 existing.setName(channel.getName().toString());
561 existing.setDescription(channel.getDescription());
562 existing.setBlockableSystem(channel.isBlockableSystem());
563 if (existing.getGroup() == null) {
564 existing.setGroup(channel.getGroup());
565 }
566
567 // Apps are allowed to downgrade channel importance if the user has not changed any
568 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500569 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400570 if (existing.getUserLockedFields() == 0 &&
571 channel.getImportance() < existing.getImportance()) {
572 existing.setImportance(channel.getImportance());
573 }
574
575 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
576 // fields on the channel yet
577 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
578 boolean bypassDnd = channel.canBypassDnd();
579 existing.setBypassDnd(bypassDnd);
580
Beverly0479cde22018-11-09 11:05:34 -0500581 if (bypassDnd != mAreChannelsBypassingDnd
582 || previousExistingImportance != existing.getImportance()) {
583 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400584 }
585 }
586
587 updateConfig();
588 return;
589 }
590 if (channel.getImportance() < IMPORTANCE_NONE
591 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
592 throw new IllegalArgumentException("Invalid importance level");
593 }
594
595 // Reset fields that apps aren't allowed to set.
596 if (fromTargetApp && !hasDndAccess) {
597 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
598 }
599 if (fromTargetApp) {
600 channel.setLockscreenVisibility(r.visibility);
601 }
602 clearLockedFields(channel);
603 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
604 channel.setLockscreenVisibility(
605 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
606 }
607 if (!r.showBadge) {
608 channel.setShowBadge(false);
609 }
610
611 r.channels.put(channel.getId(), channel);
612 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500613 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400614 }
615 MetricsLogger.action(getChannelLog(channel, pkg).setType(
616 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
617 }
618
619 void clearLockedFields(NotificationChannel channel) {
620 channel.unlockFields(channel.getUserLockedFields());
621 }
622
623 @Override
624 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
625 boolean fromUser) {
626 Preconditions.checkNotNull(updatedChannel);
627 Preconditions.checkNotNull(updatedChannel.getId());
628 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
629 if (r == null) {
630 throw new IllegalArgumentException("Invalid package");
631 }
632 NotificationChannel channel = r.channels.get(updatedChannel.getId());
633 if (channel == null || channel.isDeleted()) {
634 throw new IllegalArgumentException("Channel does not exist");
635 }
636 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
637 updatedChannel.setLockscreenVisibility(
638 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
639 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400640 if (fromUser) {
641 updatedChannel.lockFields(channel.getUserLockedFields());
642 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400643 } else {
644 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400645 }
646 r.channels.put(updatedChannel.getId(), updatedChannel);
647
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400648 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400649 // copy settings to app level so they are inherited by new channels
650 // when the app migrates
651 r.importance = updatedChannel.getImportance();
652 r.priority = updatedChannel.canBypassDnd()
653 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
654 r.visibility = updatedChannel.getLockscreenVisibility();
655 r.showBadge = updatedChannel.canShowBadge();
656 }
657
658 if (!channel.equals(updatedChannel)) {
659 // only log if there are real changes
660 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
661 }
662
Beverly0479cde22018-11-09 11:05:34 -0500663 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
664 || channel.getImportance() != updatedChannel.getImportance()) {
665 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400666 }
667 updateConfig();
668 }
669
670 @Override
671 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
672 boolean includeDeleted) {
673 Preconditions.checkNotNull(pkg);
674 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
675 if (r == null) {
676 return null;
677 }
678 if (channelId == null) {
679 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
680 }
681 final NotificationChannel nc = r.channels.get(channelId);
682 if (nc != null && (includeDeleted || !nc.isDeleted())) {
683 return nc;
684 }
685 return null;
686 }
687
688 @Override
689 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
690 PackagePreferences r = getPackagePreferences(pkg, uid);
691 if (r == null) {
692 return;
693 }
694 NotificationChannel channel = r.channels.get(channelId);
695 if (channel != null) {
696 channel.setDeleted(true);
697 LogMaker lm = getChannelLog(channel, pkg);
698 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
699 MetricsLogger.action(lm);
700
701 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500702 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400703 }
704 }
705 }
706
707 @Override
708 @VisibleForTesting
709 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
710 Preconditions.checkNotNull(pkg);
711 Preconditions.checkNotNull(channelId);
712 PackagePreferences r = getPackagePreferences(pkg, uid);
713 if (r == null) {
714 return;
715 }
716 r.channels.remove(channelId);
717 }
718
719 @Override
720 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
721 Preconditions.checkNotNull(pkg);
722 PackagePreferences r = getPackagePreferences(pkg, uid);
723 if (r == null) {
724 return;
725 }
726 int N = r.channels.size() - 1;
727 for (int i = N; i >= 0; i--) {
728 String key = r.channels.keyAt(i);
729 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
730 r.channels.remove(key);
731 }
732 }
733 }
734
735 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
736 int uid, String groupId, boolean includeDeleted) {
737 Preconditions.checkNotNull(pkg);
738 PackagePreferences r = getPackagePreferences(pkg, uid);
739 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
740 return null;
741 }
742 NotificationChannelGroup group = r.groups.get(groupId).clone();
743 group.setChannels(new ArrayList<>());
744 int N = r.channels.size();
745 for (int i = 0; i < N; i++) {
746 final NotificationChannel nc = r.channels.valueAt(i);
747 if (includeDeleted || !nc.isDeleted()) {
748 if (groupId.equals(nc.getGroup())) {
749 group.addChannel(nc);
750 }
751 }
752 }
753 return group;
754 }
755
756 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
757 int uid) {
758 Preconditions.checkNotNull(pkg);
759 PackagePreferences r = getPackagePreferences(pkg, uid);
760 if (r == null) {
761 return null;
762 }
763 return r.groups.get(groupId);
764 }
765
766 @Override
767 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400768 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400769 Preconditions.checkNotNull(pkg);
770 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
771 PackagePreferences r = getPackagePreferences(pkg, uid);
772 if (r == null) {
773 return ParceledListSlice.emptyList();
774 }
775 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
776 int N = r.channels.size();
777 for (int i = 0; i < N; i++) {
778 final NotificationChannel nc = r.channels.valueAt(i);
779 if (includeDeleted || !nc.isDeleted()) {
780 if (nc.getGroup() != null) {
781 if (r.groups.get(nc.getGroup()) != null) {
782 NotificationChannelGroup ncg = groups.get(nc.getGroup());
783 if (ncg == null) {
784 ncg = r.groups.get(nc.getGroup()).clone();
785 ncg.setChannels(new ArrayList<>());
786 groups.put(nc.getGroup(), ncg);
787
788 }
789 ncg.addChannel(nc);
790 }
791 } else {
792 nonGrouped.addChannel(nc);
793 }
794 }
795 }
796 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
797 groups.put(null, nonGrouped);
798 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400799 if (includeEmpty) {
800 for (NotificationChannelGroup group : r.groups.values()) {
801 if (!groups.containsKey(group.getId())) {
802 groups.put(group.getId(), group);
803 }
804 }
805 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400806 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
807 }
808
809 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
810 String groupId) {
811 List<NotificationChannel> deletedChannels = new ArrayList<>();
812 PackagePreferences r = getPackagePreferences(pkg, uid);
813 if (r == null || TextUtils.isEmpty(groupId)) {
814 return deletedChannels;
815 }
816
817 r.groups.remove(groupId);
818
819 int N = r.channels.size();
820 for (int i = 0; i < N; i++) {
821 final NotificationChannel nc = r.channels.valueAt(i);
822 if (groupId.equals(nc.getGroup())) {
823 nc.setDeleted(true);
824 deletedChannels.add(nc);
825 }
826 }
827 return deletedChannels;
828 }
829
830 @Override
831 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
832 int uid) {
833 PackagePreferences r = getPackagePreferences(pkg, uid);
834 if (r == null) {
835 return new ArrayList<>();
836 }
837 return r.groups.values();
838 }
839
840 @Override
841 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
842 boolean includeDeleted) {
843 Preconditions.checkNotNull(pkg);
844 List<NotificationChannel> channels = new ArrayList<>();
845 PackagePreferences r = getPackagePreferences(pkg, uid);
846 if (r == null) {
847 return ParceledListSlice.emptyList();
848 }
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 channels.add(nc);
854 }
855 }
856 return new ParceledListSlice<>(channels);
857 }
858
859 /**
Beverly0479cde22018-11-09 11:05:34 -0500860 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
861 */
862 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
863 int userId) {
864 List<NotificationChannel> channels = new ArrayList<>();
865 synchronized (mPackagePreferences) {
866 final PackagePreferences r = mPackagePreferences.get(
867 packagePreferencesKey(pkg, userId));
868 // notifications from this package aren't blocked
869 if (r != null && r.importance != IMPORTANCE_NONE) {
870 for (NotificationChannel channel : r.channels.values()) {
871 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
872 channels.add(channel);
873 }
874 }
875 }
876 }
877 return new ParceledListSlice<>(channels);
878 }
879
880 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400881 * True for pre-O apps that only have the default channel, or pre O apps that have no
882 * channels yet. This method will create the default channel for pre-O apps that don't have it.
883 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
884 * upgrades.
885 */
886 public boolean onlyHasDefaultChannel(String pkg, int uid) {
887 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
888 if (r.channels.size() == 1
889 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
890 return true;
891 }
892 return false;
893 }
894
895 public int getDeletedChannelCount(String pkg, int uid) {
896 Preconditions.checkNotNull(pkg);
897 int deletedCount = 0;
898 PackagePreferences r = getPackagePreferences(pkg, uid);
899 if (r == null) {
900 return deletedCount;
901 }
902 int N = r.channels.size();
903 for (int i = 0; i < N; i++) {
904 final NotificationChannel nc = r.channels.valueAt(i);
905 if (nc.isDeleted()) {
906 deletedCount++;
907 }
908 }
909 return deletedCount;
910 }
911
912 public int getBlockedChannelCount(String pkg, int uid) {
913 Preconditions.checkNotNull(pkg);
914 int blockedCount = 0;
915 PackagePreferences r = getPackagePreferences(pkg, uid);
916 if (r == null) {
917 return blockedCount;
918 }
919 int N = r.channels.size();
920 for (int i = 0; i < N; i++) {
921 final NotificationChannel nc = r.channels.valueAt(i);
922 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
923 blockedCount++;
924 }
925 }
926 return blockedCount;
927 }
928
929 public int getBlockedAppCount(int userId) {
930 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400931 synchronized (mPackagePreferences) {
932 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400933 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400934 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400935 if (userId == UserHandle.getUserId(r.uid)
936 && r.importance == IMPORTANCE_NONE) {
937 count++;
938 }
939 }
940 }
941 return count;
942 }
943
Beverly0479cde22018-11-09 11:05:34 -0500944 /**
945 * Returns the number of apps that have at least one notification channel that can bypass DND
946 * for given particular user
947 */
948 public int getAppsBypassingDndCount(int userId) {
949 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400950 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -0500951 final int numPackagePreferences = mPackagePreferences.size();
952 for (int i = 0; i < numPackagePreferences; i++) {
953 final PackagePreferences r = mPackagePreferences.valueAt(i);
954 // Package isn't associated with this userId or notifications from this package are
955 // blocked
956 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
957 continue;
958 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400959
Beverly0479cde22018-11-09 11:05:34 -0500960 for (NotificationChannel channel : r.channels.values()) {
961 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
962 count++;
963 break;
964 }
965 }
966 }
967 }
968 return count;
969 }
970
971 /**
972 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
973 * updating
974 * @param userId
975 */
976 private void syncChannelsBypassingDnd(int userId) {
977 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
978 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
979 updateChannelsBypassingDnd(userId);
980 }
981
982 /**
983 * Updates the user's NotificationPolicy based on whether the given userId
984 * has channels bypassing DND
985 * @param userId
986 */
987 private void updateChannelsBypassingDnd(int userId) {
988 synchronized (mPackagePreferences) {
989 final int numPackagePreferences = mPackagePreferences.size();
990 for (int i = 0; i < numPackagePreferences; i++) {
991 final PackagePreferences r = mPackagePreferences.valueAt(i);
992 // Package isn't associated with this userId or notifications from this package are
993 // blocked
994 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
995 continue;
996 }
997
998 for (NotificationChannel channel : r.channels.values()) {
999 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001000 if (!mAreChannelsBypassingDnd) {
1001 mAreChannelsBypassingDnd = true;
1002 updateZenPolicy(true);
1003 }
1004 return;
1005 }
1006 }
1007 }
1008 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001009 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1010 if (mAreChannelsBypassingDnd) {
1011 mAreChannelsBypassingDnd = false;
1012 updateZenPolicy(false);
1013 }
1014 }
1015
Beverly0479cde22018-11-09 11:05:34 -05001016 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1017 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001018 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1019 return false;
Beverly0479cde22018-11-09 11:05:34 -05001020 }
1021
1022 // Channel is deleted or is blocked
1023 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1024 return false;
1025 }
1026
1027 return true;
1028 }
1029
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001030 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1031 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1032 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1033 policy.priorityCategories, policy.priorityCallSenders,
1034 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1035 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1036 : 0)));
1037 }
1038
1039 public boolean areChannelsBypassingDnd() {
1040 return mAreChannelsBypassingDnd;
1041 }
1042
1043 /**
1044 * Sets importance.
1045 */
1046 @Override
1047 public void setImportance(String pkgName, int uid, int importance) {
1048 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1049 updateConfig();
1050 }
1051
1052 public void setEnabled(String packageName, int uid, boolean enabled) {
1053 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1054 if (wasEnabled == enabled) {
1055 return;
1056 }
1057 setImportance(packageName, uid,
1058 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1059 }
1060
1061 /**
1062 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1063 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1064 * considered for sentiment adjustments (and thus never show a blocking helper).
1065 */
1066 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001067 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1068 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001069 return;
1070 }
1071
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001072 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001073 updateConfig();
1074 }
1075
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001076 /**
1077 * Returns the delegate for a given package, if it's allowed by the package and the user.
1078 */
1079 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1080 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1081
1082 if (prefs == null || prefs.delegate == null) {
1083 return null;
1084 }
1085 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1086 return null;
1087 }
1088 return prefs.delegate.mPkg;
1089 }
1090
1091 /**
1092 * Used by an app to delegate notification posting privileges to another apps.
1093 */
1094 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1095 String delegatePkg, int delegateUid) {
1096 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1097
1098 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1099 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1100 prefs.delegate = delegate;
1101 updateConfig();
1102 }
1103
1104 /**
1105 * Used by an app to turn off its notification delegate.
1106 */
1107 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1108 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1109 if (prefs != null && prefs.delegate != null) {
1110 prefs.delegate.mEnabled = false;
1111 updateConfig();
1112 }
1113 }
1114
1115 /**
1116 * Toggles whether an app can have a notification delegate on behalf of a user.
1117 */
1118 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1119 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1120 if (prefs != null && prefs.delegate != null) {
1121 prefs.delegate.mUserAllowed = userAllowed;
1122 updateConfig();
1123 }
1124 }
1125
1126 /**
1127 * Returns whether the given app is allowed on post notifications on behalf of the other given
1128 * app.
1129 */
1130 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1131 String potentialDelegatePkg, int potentialDelegateUid) {
1132 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1133
1134 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1135 }
1136
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001137 @VisibleForTesting
1138 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1139 if (original.canBypassDnd() != update.canBypassDnd()) {
1140 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1141 }
1142 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1143 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1144 }
1145 if (original.getImportance() != update.getImportance()) {
1146 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1147 }
1148 if (original.shouldShowLights() != update.shouldShowLights()
1149 || original.getLightColor() != update.getLightColor()) {
1150 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1151 }
1152 if (!Objects.equals(original.getSound(), update.getSound())) {
1153 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1154 }
1155 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1156 || original.shouldVibrate() != update.shouldVibrate()) {
1157 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1158 }
1159 if (original.canShowBadge() != update.canShowBadge()) {
1160 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1161 }
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001162 if (original.isAppOverlayAllowed() != update.isAppOverlayAllowed()) {
1163 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY);
1164 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001165 }
1166
1167 public void dump(PrintWriter pw, String prefix,
1168 @NonNull NotificationManagerService.DumpFilter filter) {
1169 pw.print(prefix);
1170 pw.println("per-package config:");
1171
1172 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001173 synchronized (mPackagePreferences) {
1174 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001175 }
1176 pw.println("Restored without uid:");
1177 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1178 }
1179
1180 public void dump(ProtoOutputStream proto,
1181 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001182 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001183 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001184 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001185 }
1186 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1187 mRestoredWithoutUids);
1188 }
1189
1190 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1191 @NonNull NotificationManagerService.DumpFilter filter,
1192 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1193 final int N = PackagePreferencess.size();
1194 for (int i = 0; i < N; i++) {
1195 final PackagePreferences r = PackagePreferencess.valueAt(i);
1196 if (filter.matches(r.pkg)) {
1197 pw.print(prefix);
1198 pw.print(" AppSettings: ");
1199 pw.print(r.pkg);
1200 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001201 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001202 pw.print(')');
1203 if (r.importance != DEFAULT_IMPORTANCE) {
1204 pw.print(" importance=");
1205 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1206 }
1207 if (r.priority != DEFAULT_PRIORITY) {
1208 pw.print(" priority=");
1209 pw.print(Notification.priorityToString(r.priority));
1210 }
1211 if (r.visibility != DEFAULT_VISIBILITY) {
1212 pw.print(" visibility=");
1213 pw.print(Notification.visibilityToString(r.visibility));
1214 }
1215 pw.print(" showBadge=");
1216 pw.print(Boolean.toString(r.showBadge));
1217 pw.println();
1218 for (NotificationChannel channel : r.channels.values()) {
1219 pw.print(prefix);
1220 channel.dump(pw, " ", filter.redact);
1221 }
1222 for (NotificationChannelGroup group : r.groups.values()) {
1223 pw.print(prefix);
1224 pw.print(" ");
1225 pw.print(" ");
1226 pw.println(group);
1227 }
1228 }
1229 }
1230 }
1231
1232 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1233 @NonNull NotificationManagerService.DumpFilter filter,
1234 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1235 final int N = PackagePreferencess.size();
1236 long fToken;
1237 for (int i = 0; i < N; i++) {
1238 final PackagePreferences r = PackagePreferencess.valueAt(i);
1239 if (filter.matches(r.pkg)) {
1240 fToken = proto.start(fieldId);
1241
1242 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1243 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1244 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1245 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1246 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1247 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1248
1249 for (NotificationChannel channel : r.channels.values()) {
1250 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1251 }
1252 for (NotificationChannelGroup group : r.groups.values()) {
1253 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1254 }
1255
1256 proto.end(fToken);
1257 }
1258 }
1259 }
1260
1261 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1262 JSONObject ranking = new JSONObject();
1263 JSONArray PackagePreferencess = new JSONArray();
1264 try {
1265 ranking.put("noUid", mRestoredWithoutUids.size());
1266 } catch (JSONException e) {
1267 // pass
1268 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001269 synchronized (mPackagePreferences) {
1270 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001271 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001272 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001273 if (filter == null || filter.matches(r.pkg)) {
1274 JSONObject PackagePreferences = new JSONObject();
1275 try {
1276 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1277 PackagePreferences.put("packageName", r.pkg);
1278 if (r.importance != DEFAULT_IMPORTANCE) {
1279 PackagePreferences.put("importance",
1280 NotificationListenerService.Ranking.importanceToString(
1281 r.importance));
1282 }
1283 if (r.priority != DEFAULT_PRIORITY) {
1284 PackagePreferences.put("priority",
1285 Notification.priorityToString(r.priority));
1286 }
1287 if (r.visibility != DEFAULT_VISIBILITY) {
1288 PackagePreferences.put("visibility",
1289 Notification.visibilityToString(r.visibility));
1290 }
1291 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1292 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1293 }
1294 JSONArray channels = new JSONArray();
1295 for (NotificationChannel channel : r.channels.values()) {
1296 channels.put(channel.toJson());
1297 }
1298 PackagePreferences.put("channels", channels);
1299 JSONArray groups = new JSONArray();
1300 for (NotificationChannelGroup group : r.groups.values()) {
1301 groups.put(group.toJson());
1302 }
1303 PackagePreferences.put("groups", groups);
1304 } catch (JSONException e) {
1305 // pass
1306 }
1307 PackagePreferencess.put(PackagePreferences);
1308 }
1309 }
1310 }
1311 try {
1312 ranking.put("PackagePreferencess", PackagePreferencess);
1313 } catch (JSONException e) {
1314 // pass
1315 }
1316 return ranking;
1317 }
1318
1319 /**
1320 * Dump only the ban information as structured JSON for the stats collector.
1321 *
1322 * This is intentionally redundant with {#link dumpJson} because the old
1323 * scraper will expect this format.
1324 *
1325 * @param filter
1326 * @return
1327 */
1328 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1329 JSONArray bans = new JSONArray();
1330 Map<Integer, String> packageBans = getPackageBans();
1331 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1332 final int userId = UserHandle.getUserId(ban.getKey());
1333 final String packageName = ban.getValue();
1334 if (filter == null || filter.matches(packageName)) {
1335 JSONObject banJson = new JSONObject();
1336 try {
1337 banJson.put("userId", userId);
1338 banJson.put("packageName", packageName);
1339 } catch (JSONException e) {
1340 e.printStackTrace();
1341 }
1342 bans.put(banJson);
1343 }
1344 }
1345 return bans;
1346 }
1347
1348 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001349 synchronized (mPackagePreferences) {
1350 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001351 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1352 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001353 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001354 if (r.importance == IMPORTANCE_NONE) {
1355 packageBans.put(r.uid, r.pkg);
1356 }
1357 }
1358
1359 return packageBans;
1360 }
1361 }
1362
1363 /**
1364 * Dump only the channel information as structured JSON for the stats collector.
1365 *
1366 * This is intentionally redundant with {#link dumpJson} because the old
1367 * scraper will expect this format.
1368 *
1369 * @param filter
1370 * @return
1371 */
1372 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1373 JSONArray channels = new JSONArray();
1374 Map<String, Integer> packageChannels = getPackageChannels();
1375 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1376 final String packageName = channelCount.getKey();
1377 if (filter == null || filter.matches(packageName)) {
1378 JSONObject channelCountJson = new JSONObject();
1379 try {
1380 channelCountJson.put("packageName", packageName);
1381 channelCountJson.put("channelCount", channelCount.getValue());
1382 } catch (JSONException e) {
1383 e.printStackTrace();
1384 }
1385 channels.put(channelCountJson);
1386 }
1387 }
1388 return channels;
1389 }
1390
1391 private Map<String, Integer> getPackageChannels() {
1392 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001393 synchronized (mPackagePreferences) {
1394 for (int i = 0; i < mPackagePreferences.size(); i++) {
1395 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001396 int channelCount = 0;
1397 for (int j = 0; j < r.channels.size(); j++) {
1398 if (!r.channels.valueAt(j).isDeleted()) {
1399 channelCount++;
1400 }
1401 }
1402 packageChannels.put(r.pkg, channelCount);
1403 }
1404 }
1405 return packageChannels;
1406 }
1407
Beverly0479cde22018-11-09 11:05:34 -05001408 /**
1409 * Called when user switches
1410 */
1411 public void onUserSwitched(int userId) {
1412 syncChannelsBypassingDnd(userId);
1413 }
1414
1415 /**
1416 * Called when user is unlocked
1417 */
1418 public void onUserUnlocked(int userId) {
1419 syncChannelsBypassingDnd(userId);
1420 }
1421
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001422 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001423 synchronized (mPackagePreferences) {
1424 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001425 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001426 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001427 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001428 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001429 }
1430 }
1431 }
1432 }
1433
1434 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001435 synchronized (mPackagePreferences) {
1436 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001437 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001438 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001439 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1440 if (PackagePreferences.channels.containsKey(
1441 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1442 PackagePreferences.channels.get(
1443 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1444 context.getResources().getString(
1445 R.string.default_notification_channel_label));
1446 }
1447 }
1448 }
1449 }
1450 }
1451
1452 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1453 int[] uidList) {
1454 if (pkgList == null || pkgList.length == 0) {
1455 return; // nothing to do
1456 }
1457 boolean updated = false;
1458 if (removingPackage) {
1459 // Remove notification settings for uninstalled package
1460 int size = Math.min(pkgList.length, uidList.length);
1461 for (int i = 0; i < size; i++) {
1462 final String pkg = pkgList[i];
1463 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001464 synchronized (mPackagePreferences) {
1465 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001466 }
1467 mRestoredWithoutUids.remove(pkg);
1468 updated = true;
1469 }
1470 } else {
1471 for (String pkg : pkgList) {
1472 // Package install
1473 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1474 if (r != null) {
1475 try {
1476 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1477 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001478 synchronized (mPackagePreferences) {
1479 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001480 }
1481 updated = true;
1482 } catch (PackageManager.NameNotFoundException e) {
1483 // noop
1484 }
1485 }
1486 // Package upgrade
1487 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001488 synchronized (mPackagePreferences) {
1489 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1490 mPm.getPackageUidAsUser(pkg, changeUserId));
1491 if (fullPackagePreferences != null) {
1492 createDefaultChannelIfNeeded(fullPackagePreferences);
1493 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1494 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001495 }
1496 } catch (PackageManager.NameNotFoundException e) {
1497 }
1498 }
1499 }
1500
1501 if (updated) {
1502 updateConfig();
1503 }
1504 }
1505
1506 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1507 return new LogMaker(
1508 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1509 .ACTION_NOTIFICATION_CHANNEL)
1510 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1511 .setPackageName(pkg)
1512 .addTaggedData(
1513 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1514 .FIELD_NOTIFICATION_CHANNEL_ID,
1515 channel.getId())
1516 .addTaggedData(
1517 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1518 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1519 channel.getImportance());
1520 }
1521
1522 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1523 return new LogMaker(
1524 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1525 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1526 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1527 .addTaggedData(
1528 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1529 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1530 groupId)
1531 .setPackageName(pkg);
1532 }
1533
1534
1535 public void updateBadgingEnabled() {
1536 if (mBadgingEnabled == null) {
1537 mBadgingEnabled = new SparseBooleanArray();
1538 }
1539 boolean changed = false;
1540 // update the cached values
1541 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1542 int userId = mBadgingEnabled.keyAt(index);
1543 final boolean oldValue = mBadgingEnabled.get(userId);
1544 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1545 Settings.Secure.NOTIFICATION_BADGING,
1546 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1547 mBadgingEnabled.put(userId, newValue);
1548 changed |= oldValue != newValue;
1549 }
1550 if (changed) {
1551 updateConfig();
1552 }
1553 }
1554
1555 public boolean badgingEnabled(UserHandle userHandle) {
1556 int userId = userHandle.getIdentifier();
1557 if (userId == UserHandle.USER_ALL) {
1558 return false;
1559 }
1560 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1561 mBadgingEnabled.put(userId,
1562 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1563 Settings.Secure.NOTIFICATION_BADGING,
1564 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1565 }
1566 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1567 }
1568
1569 private void updateConfig() {
1570 mRankingHandler.requestSort();
1571 }
1572
1573 private static String packagePreferencesKey(String pkg, int uid) {
1574 return pkg + "|" + uid;
1575 }
1576
1577 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001578 String pkg;
1579 int uid = UNKNOWN_UID;
1580 int importance = DEFAULT_IMPORTANCE;
1581 int priority = DEFAULT_PRIORITY;
1582 int visibility = DEFAULT_VISIBILITY;
1583 boolean showBadge = DEFAULT_SHOW_BADGE;
1584 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1585
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001586 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001587 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1588 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001589
1590 public boolean isValidDelegate(String pkg, int uid) {
1591 return delegate != null && delegate.isAllowed(pkg, uid);
1592 }
1593 }
1594
1595 private static class Delegate {
1596 static final boolean DEFAULT_ENABLED = true;
1597 static final boolean DEFAULT_USER_ALLOWED = true;
1598 String mPkg;
1599 int mUid = UNKNOWN_UID;
1600 boolean mEnabled = DEFAULT_ENABLED;
1601 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1602
1603 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1604 mPkg = pkg;
1605 mUid = uid;
1606 mEnabled = enabled;
1607 mUserAllowed = userAllowed;
1608 }
1609
1610 public boolean isAllowed(String pkg, int uid) {
1611 if (pkg == null || uid == UNKNOWN_UID) {
1612 return false;
1613 }
1614 return pkg.equals(mPkg)
1615 && uid == mUid
1616 && (mUserAllowed && mEnabled);
1617 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001618 }
1619}