blob: fd65ebe102acd7e4d0ac60e887c9cf1dd225aae7 [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
1018 if (!TextUtils.isEmpty(channel.getGroup())) {
1019 if (pkgPref.groups.get(channel.getGroup()).isBlocked()) {
1020 return false;
1021 }
1022 }
1023
1024 // Channel is deleted or is blocked
1025 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1026 return false;
1027 }
1028
1029 return true;
1030 }
1031
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001032 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1033 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1034 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1035 policy.priorityCategories, policy.priorityCallSenders,
1036 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1037 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1038 : 0)));
1039 }
1040
1041 public boolean areChannelsBypassingDnd() {
1042 return mAreChannelsBypassingDnd;
1043 }
1044
1045 /**
1046 * Sets importance.
1047 */
1048 @Override
1049 public void setImportance(String pkgName, int uid, int importance) {
1050 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1051 updateConfig();
1052 }
1053
1054 public void setEnabled(String packageName, int uid, boolean enabled) {
1055 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1056 if (wasEnabled == enabled) {
1057 return;
1058 }
1059 setImportance(packageName, uid,
1060 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1061 }
1062
1063 /**
1064 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1065 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1066 * considered for sentiment adjustments (and thus never show a blocking helper).
1067 */
1068 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001069 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1070 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001071 return;
1072 }
1073
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001074 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001075 updateConfig();
1076 }
1077
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001078 /**
1079 * Returns the delegate for a given package, if it's allowed by the package and the user.
1080 */
1081 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1082 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1083
1084 if (prefs == null || prefs.delegate == null) {
1085 return null;
1086 }
1087 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1088 return null;
1089 }
1090 return prefs.delegate.mPkg;
1091 }
1092
1093 /**
1094 * Used by an app to delegate notification posting privileges to another apps.
1095 */
1096 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1097 String delegatePkg, int delegateUid) {
1098 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1099
1100 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1101 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1102 prefs.delegate = delegate;
1103 updateConfig();
1104 }
1105
1106 /**
1107 * Used by an app to turn off its notification delegate.
1108 */
1109 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1110 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1111 if (prefs != null && prefs.delegate != null) {
1112 prefs.delegate.mEnabled = false;
1113 updateConfig();
1114 }
1115 }
1116
1117 /**
1118 * Toggles whether an app can have a notification delegate on behalf of a user.
1119 */
1120 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1121 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1122 if (prefs != null && prefs.delegate != null) {
1123 prefs.delegate.mUserAllowed = userAllowed;
1124 updateConfig();
1125 }
1126 }
1127
1128 /**
1129 * Returns whether the given app is allowed on post notifications on behalf of the other given
1130 * app.
1131 */
1132 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1133 String potentialDelegatePkg, int potentialDelegateUid) {
1134 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1135
1136 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1137 }
1138
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001139 @VisibleForTesting
1140 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1141 if (original.canBypassDnd() != update.canBypassDnd()) {
1142 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1143 }
1144 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1145 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1146 }
1147 if (original.getImportance() != update.getImportance()) {
1148 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1149 }
1150 if (original.shouldShowLights() != update.shouldShowLights()
1151 || original.getLightColor() != update.getLightColor()) {
1152 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1153 }
1154 if (!Objects.equals(original.getSound(), update.getSound())) {
1155 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1156 }
1157 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1158 || original.shouldVibrate() != update.shouldVibrate()) {
1159 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1160 }
1161 if (original.canShowBadge() != update.canShowBadge()) {
1162 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1163 }
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001164 if (original.isAppOverlayAllowed() != update.isAppOverlayAllowed()) {
1165 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_APP_OVERLAY);
1166 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001167 }
1168
1169 public void dump(PrintWriter pw, String prefix,
1170 @NonNull NotificationManagerService.DumpFilter filter) {
1171 pw.print(prefix);
1172 pw.println("per-package config:");
1173
1174 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001175 synchronized (mPackagePreferences) {
1176 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001177 }
1178 pw.println("Restored without uid:");
1179 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1180 }
1181
1182 public void dump(ProtoOutputStream proto,
1183 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001184 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001185 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001186 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001187 }
1188 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1189 mRestoredWithoutUids);
1190 }
1191
1192 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1193 @NonNull NotificationManagerService.DumpFilter filter,
1194 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1195 final int N = PackagePreferencess.size();
1196 for (int i = 0; i < N; i++) {
1197 final PackagePreferences r = PackagePreferencess.valueAt(i);
1198 if (filter.matches(r.pkg)) {
1199 pw.print(prefix);
1200 pw.print(" AppSettings: ");
1201 pw.print(r.pkg);
1202 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001203 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001204 pw.print(')');
1205 if (r.importance != DEFAULT_IMPORTANCE) {
1206 pw.print(" importance=");
1207 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1208 }
1209 if (r.priority != DEFAULT_PRIORITY) {
1210 pw.print(" priority=");
1211 pw.print(Notification.priorityToString(r.priority));
1212 }
1213 if (r.visibility != DEFAULT_VISIBILITY) {
1214 pw.print(" visibility=");
1215 pw.print(Notification.visibilityToString(r.visibility));
1216 }
1217 pw.print(" showBadge=");
1218 pw.print(Boolean.toString(r.showBadge));
1219 pw.println();
1220 for (NotificationChannel channel : r.channels.values()) {
1221 pw.print(prefix);
1222 channel.dump(pw, " ", filter.redact);
1223 }
1224 for (NotificationChannelGroup group : r.groups.values()) {
1225 pw.print(prefix);
1226 pw.print(" ");
1227 pw.print(" ");
1228 pw.println(group);
1229 }
1230 }
1231 }
1232 }
1233
1234 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1235 @NonNull NotificationManagerService.DumpFilter filter,
1236 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1237 final int N = PackagePreferencess.size();
1238 long fToken;
1239 for (int i = 0; i < N; i++) {
1240 final PackagePreferences r = PackagePreferencess.valueAt(i);
1241 if (filter.matches(r.pkg)) {
1242 fToken = proto.start(fieldId);
1243
1244 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1245 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1246 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1247 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1248 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1249 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1250
1251 for (NotificationChannel channel : r.channels.values()) {
1252 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1253 }
1254 for (NotificationChannelGroup group : r.groups.values()) {
1255 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1256 }
1257
1258 proto.end(fToken);
1259 }
1260 }
1261 }
1262
1263 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1264 JSONObject ranking = new JSONObject();
1265 JSONArray PackagePreferencess = new JSONArray();
1266 try {
1267 ranking.put("noUid", mRestoredWithoutUids.size());
1268 } catch (JSONException e) {
1269 // pass
1270 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001271 synchronized (mPackagePreferences) {
1272 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001273 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001274 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001275 if (filter == null || filter.matches(r.pkg)) {
1276 JSONObject PackagePreferences = new JSONObject();
1277 try {
1278 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1279 PackagePreferences.put("packageName", r.pkg);
1280 if (r.importance != DEFAULT_IMPORTANCE) {
1281 PackagePreferences.put("importance",
1282 NotificationListenerService.Ranking.importanceToString(
1283 r.importance));
1284 }
1285 if (r.priority != DEFAULT_PRIORITY) {
1286 PackagePreferences.put("priority",
1287 Notification.priorityToString(r.priority));
1288 }
1289 if (r.visibility != DEFAULT_VISIBILITY) {
1290 PackagePreferences.put("visibility",
1291 Notification.visibilityToString(r.visibility));
1292 }
1293 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1294 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1295 }
1296 JSONArray channels = new JSONArray();
1297 for (NotificationChannel channel : r.channels.values()) {
1298 channels.put(channel.toJson());
1299 }
1300 PackagePreferences.put("channels", channels);
1301 JSONArray groups = new JSONArray();
1302 for (NotificationChannelGroup group : r.groups.values()) {
1303 groups.put(group.toJson());
1304 }
1305 PackagePreferences.put("groups", groups);
1306 } catch (JSONException e) {
1307 // pass
1308 }
1309 PackagePreferencess.put(PackagePreferences);
1310 }
1311 }
1312 }
1313 try {
1314 ranking.put("PackagePreferencess", PackagePreferencess);
1315 } catch (JSONException e) {
1316 // pass
1317 }
1318 return ranking;
1319 }
1320
1321 /**
1322 * Dump only the ban information as structured JSON for the stats collector.
1323 *
1324 * This is intentionally redundant with {#link dumpJson} because the old
1325 * scraper will expect this format.
1326 *
1327 * @param filter
1328 * @return
1329 */
1330 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1331 JSONArray bans = new JSONArray();
1332 Map<Integer, String> packageBans = getPackageBans();
1333 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1334 final int userId = UserHandle.getUserId(ban.getKey());
1335 final String packageName = ban.getValue();
1336 if (filter == null || filter.matches(packageName)) {
1337 JSONObject banJson = new JSONObject();
1338 try {
1339 banJson.put("userId", userId);
1340 banJson.put("packageName", packageName);
1341 } catch (JSONException e) {
1342 e.printStackTrace();
1343 }
1344 bans.put(banJson);
1345 }
1346 }
1347 return bans;
1348 }
1349
1350 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001351 synchronized (mPackagePreferences) {
1352 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001353 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1354 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001355 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001356 if (r.importance == IMPORTANCE_NONE) {
1357 packageBans.put(r.uid, r.pkg);
1358 }
1359 }
1360
1361 return packageBans;
1362 }
1363 }
1364
1365 /**
1366 * Dump only the channel information as structured JSON for the stats collector.
1367 *
1368 * This is intentionally redundant with {#link dumpJson} because the old
1369 * scraper will expect this format.
1370 *
1371 * @param filter
1372 * @return
1373 */
1374 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1375 JSONArray channels = new JSONArray();
1376 Map<String, Integer> packageChannels = getPackageChannels();
1377 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1378 final String packageName = channelCount.getKey();
1379 if (filter == null || filter.matches(packageName)) {
1380 JSONObject channelCountJson = new JSONObject();
1381 try {
1382 channelCountJson.put("packageName", packageName);
1383 channelCountJson.put("channelCount", channelCount.getValue());
1384 } catch (JSONException e) {
1385 e.printStackTrace();
1386 }
1387 channels.put(channelCountJson);
1388 }
1389 }
1390 return channels;
1391 }
1392
1393 private Map<String, Integer> getPackageChannels() {
1394 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001395 synchronized (mPackagePreferences) {
1396 for (int i = 0; i < mPackagePreferences.size(); i++) {
1397 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001398 int channelCount = 0;
1399 for (int j = 0; j < r.channels.size(); j++) {
1400 if (!r.channels.valueAt(j).isDeleted()) {
1401 channelCount++;
1402 }
1403 }
1404 packageChannels.put(r.pkg, channelCount);
1405 }
1406 }
1407 return packageChannels;
1408 }
1409
Beverly0479cde22018-11-09 11:05:34 -05001410 /**
1411 * Called when user switches
1412 */
1413 public void onUserSwitched(int userId) {
1414 syncChannelsBypassingDnd(userId);
1415 }
1416
1417 /**
1418 * Called when user is unlocked
1419 */
1420 public void onUserUnlocked(int userId) {
1421 syncChannelsBypassingDnd(userId);
1422 }
1423
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001424 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001425 synchronized (mPackagePreferences) {
1426 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001427 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001428 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001429 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001430 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001431 }
1432 }
1433 }
1434 }
1435
1436 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001437 synchronized (mPackagePreferences) {
1438 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001439 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001440 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001441 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1442 if (PackagePreferences.channels.containsKey(
1443 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1444 PackagePreferences.channels.get(
1445 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1446 context.getResources().getString(
1447 R.string.default_notification_channel_label));
1448 }
1449 }
1450 }
1451 }
1452 }
1453
1454 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1455 int[] uidList) {
1456 if (pkgList == null || pkgList.length == 0) {
1457 return; // nothing to do
1458 }
1459 boolean updated = false;
1460 if (removingPackage) {
1461 // Remove notification settings for uninstalled package
1462 int size = Math.min(pkgList.length, uidList.length);
1463 for (int i = 0; i < size; i++) {
1464 final String pkg = pkgList[i];
1465 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001466 synchronized (mPackagePreferences) {
1467 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001468 }
1469 mRestoredWithoutUids.remove(pkg);
1470 updated = true;
1471 }
1472 } else {
1473 for (String pkg : pkgList) {
1474 // Package install
1475 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1476 if (r != null) {
1477 try {
1478 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1479 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001480 synchronized (mPackagePreferences) {
1481 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001482 }
1483 updated = true;
1484 } catch (PackageManager.NameNotFoundException e) {
1485 // noop
1486 }
1487 }
1488 // Package upgrade
1489 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001490 synchronized (mPackagePreferences) {
1491 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1492 mPm.getPackageUidAsUser(pkg, changeUserId));
1493 if (fullPackagePreferences != null) {
1494 createDefaultChannelIfNeeded(fullPackagePreferences);
1495 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1496 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001497 }
1498 } catch (PackageManager.NameNotFoundException e) {
1499 }
1500 }
1501 }
1502
1503 if (updated) {
1504 updateConfig();
1505 }
1506 }
1507
1508 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1509 return new LogMaker(
1510 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1511 .ACTION_NOTIFICATION_CHANNEL)
1512 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1513 .setPackageName(pkg)
1514 .addTaggedData(
1515 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1516 .FIELD_NOTIFICATION_CHANNEL_ID,
1517 channel.getId())
1518 .addTaggedData(
1519 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1520 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1521 channel.getImportance());
1522 }
1523
1524 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1525 return new LogMaker(
1526 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1527 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1528 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1529 .addTaggedData(
1530 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1531 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1532 groupId)
1533 .setPackageName(pkg);
1534 }
1535
1536
1537 public void updateBadgingEnabled() {
1538 if (mBadgingEnabled == null) {
1539 mBadgingEnabled = new SparseBooleanArray();
1540 }
1541 boolean changed = false;
1542 // update the cached values
1543 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1544 int userId = mBadgingEnabled.keyAt(index);
1545 final boolean oldValue = mBadgingEnabled.get(userId);
1546 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1547 Settings.Secure.NOTIFICATION_BADGING,
1548 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1549 mBadgingEnabled.put(userId, newValue);
1550 changed |= oldValue != newValue;
1551 }
1552 if (changed) {
1553 updateConfig();
1554 }
1555 }
1556
1557 public boolean badgingEnabled(UserHandle userHandle) {
1558 int userId = userHandle.getIdentifier();
1559 if (userId == UserHandle.USER_ALL) {
1560 return false;
1561 }
1562 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1563 mBadgingEnabled.put(userId,
1564 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1565 Settings.Secure.NOTIFICATION_BADGING,
1566 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1567 }
1568 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1569 }
1570
1571 private void updateConfig() {
1572 mRankingHandler.requestSort();
1573 }
1574
1575 private static String packagePreferencesKey(String pkg, int uid) {
1576 return pkg + "|" + uid;
1577 }
1578
1579 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001580 String pkg;
1581 int uid = UNKNOWN_UID;
1582 int importance = DEFAULT_IMPORTANCE;
1583 int priority = DEFAULT_PRIORITY;
1584 int visibility = DEFAULT_VISIBILITY;
1585 boolean showBadge = DEFAULT_SHOW_BADGE;
1586 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
1587
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001588 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001589 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1590 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001591
1592 public boolean isValidDelegate(String pkg, int uid) {
1593 return delegate != null && delegate.isAllowed(pkg, uid);
1594 }
1595 }
1596
1597 private static class Delegate {
1598 static final boolean DEFAULT_ENABLED = true;
1599 static final boolean DEFAULT_USER_ALLOWED = true;
1600 String mPkg;
1601 int mUid = UNKNOWN_UID;
1602 boolean mEnabled = DEFAULT_ENABLED;
1603 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1604
1605 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1606 mPkg = pkg;
1607 mUid = uid;
1608 mEnabled = enabled;
1609 mUserAllowed = userAllowed;
1610 }
1611
1612 public boolean isAllowed(String pkg, int uid) {
1613 if (pkg == null || uid == UNKNOWN_UID) {
1614 return false;
1615 }
1616 return pkg.equals(mPkg)
1617 && uid == mUid
1618 && (mUserAllowed && mEnabled);
1619 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001620 }
1621}