blob: 3f0043cecc49e2215cc3064c1c9420b5781e98a5 [file] [log] [blame]
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001/**
2 * Copyright (c) 2018, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.notification;
18
19import static android.app.NotificationManager.IMPORTANCE_NONE;
20
21import android.annotation.IntDef;
22import android.annotation.NonNull;
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040023import android.annotation.Nullable;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040024import android.app.Notification;
25import android.app.NotificationChannel;
26import android.app.NotificationChannelGroup;
27import android.app.NotificationManager;
28import android.content.Context;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ParceledListSlice;
32import android.metrics.LogMaker;
33import android.os.Build;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.service.notification.NotificationListenerService;
37import android.service.notification.RankingHelperProto;
38import android.text.TextUtils;
39import android.util.ArrayMap;
40import android.util.Slog;
41import android.util.SparseBooleanArray;
42import android.util.proto.ProtoOutputStream;
43
44import com.android.internal.R;
45import com.android.internal.annotations.VisibleForTesting;
46import com.android.internal.logging.MetricsLogger;
47import com.android.internal.util.Preconditions;
48import com.android.internal.util.XmlUtils;
49
50import org.json.JSONArray;
51import org.json.JSONException;
52import org.json.JSONObject;
53import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55import org.xmlpull.v1.XmlSerializer;
56
57import java.io.IOException;
58import java.io.PrintWriter;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.Collection;
62import java.util.List;
63import java.util.Map;
64import java.util.Objects;
65import java.util.concurrent.ConcurrentHashMap;
66
67public class PreferencesHelper implements RankingConfig {
68 private static final String TAG = "NotificationPrefHelper";
69 private static final int XML_VERSION = 1;
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040070 private static final int UNKNOWN_UID = UserHandle.USER_NULL;
Julia Reynolds413ba842019-01-11 10:38:08 -050071 private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040072
73 @VisibleForTesting
74 static final String TAG_RANKING = "ranking";
75 private static final String TAG_PACKAGE = "package";
76 private static final String TAG_CHANNEL = "channel";
77 private static final String TAG_GROUP = "channelGroup";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040078 private static final String TAG_DELEGATE = "delegate";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040079
80 private static final String ATT_VERSION = "version";
81 private static final String ATT_NAME = "name";
82 private static final String ATT_UID = "uid";
83 private static final String ATT_ID = "id";
Mady Mellorc39b4ae2019-01-09 17:11:37 -080084 private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040085 private static final String ATT_PRIORITY = "priority";
86 private static final String ATT_VISIBILITY = "visibility";
87 private static final String ATT_IMPORTANCE = "importance";
88 private static final String ATT_SHOW_BADGE = "show_badge";
89 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040090 private static final String ATT_ENABLED = "enabled";
91 private static final String ATT_USER_ALLOWED = "allowed";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040092
93 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
94 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
95 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
96 private static final boolean DEFAULT_SHOW_BADGE = true;
Mady Mellorc39b4ae2019-01-09 17:11:37 -080097 private static final boolean DEFAULT_ALLOW_BUBBLE = true;
Julia Reynolds413ba842019-01-11 10:38:08 -050098 private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
Mady Mellorc39b4ae2019-01-09 17:11:37 -080099
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400100 /**
101 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
102 * fields.
103 */
104 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
105
106 /**
107 * All user-lockable fields for a given application.
108 */
109 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
110 public @interface LockableAppFields {
111 int USER_LOCKED_IMPORTANCE = 0x00000001;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800112 int USER_LOCKED_BUBBLE = 0x00000002;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400113 }
114
115 // pkg|uid => PackagePreferences
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400116 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400117 // pkg => PackagePreferences
118 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
119
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400120 private final Context mContext;
121 private final PackageManager mPm;
122 private final RankingHandler mRankingHandler;
123 private final ZenModeHelper mZenModeHelper;
124
125 private SparseBooleanArray mBadgingEnabled;
126 private boolean mAreChannelsBypassingDnd;
127
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400128 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
129 ZenModeHelper zenHelper) {
130 mContext = context;
131 mZenModeHelper = zenHelper;
132 mRankingHandler = rankingHandler;
133 mPm = pm;
134
135 updateBadgingEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500136 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400137 }
138
139 public void readXml(XmlPullParser parser, boolean forRestore)
140 throws XmlPullParserException, IOException {
141 int type = parser.getEventType();
142 if (type != XmlPullParser.START_TAG) return;
143 String tag = parser.getName();
144 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400145 synchronized (mPackagePreferences) {
146 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400147
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400148 // at the time of serialization.
149 mRestoredWithoutUids.clear();
150 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
151 tag = parser.getName();
152 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
153 return;
154 }
155 if (type == XmlPullParser.START_TAG) {
156 if (TAG_PACKAGE.equals(tag)) {
157 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
158 String name = parser.getAttributeValue(null, ATT_NAME);
159 if (!TextUtils.isEmpty(name)) {
160 if (forRestore) {
161 try {
162 //TODO: http://b/22388012
163 uid = mPm.getPackageUidAsUser(name,
164 UserHandle.USER_SYSTEM);
165 } catch (PackageManager.NameNotFoundException e) {
166 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400167 }
168 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400169
170 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
171 XmlUtils.readIntAttribute(
172 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
173 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
174 DEFAULT_PRIORITY),
175 XmlUtils.readIntAttribute(
176 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
177 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500178 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
179 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800180 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400181 r.importance = XmlUtils.readIntAttribute(
182 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
183 r.priority = XmlUtils.readIntAttribute(
184 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
185 r.visibility = XmlUtils.readIntAttribute(
186 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
187 r.showBadge = XmlUtils.readBooleanAttribute(
188 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
189 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
190 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
191
192 final int innerDepth = parser.getDepth();
193 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
194 && (type != XmlPullParser.END_TAG
195 || parser.getDepth() > innerDepth)) {
196 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
197 continue;
198 }
199
200 String tagName = parser.getName();
201 // Channel groups
202 if (TAG_GROUP.equals(tagName)) {
203 String id = parser.getAttributeValue(null, ATT_ID);
204 CharSequence groupName = parser.getAttributeValue(null,
205 ATT_NAME);
206 if (!TextUtils.isEmpty(id)) {
207 NotificationChannelGroup group
208 = new NotificationChannelGroup(id, groupName);
209 group.populateFromXml(parser);
210 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400211 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400212 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400213 // Channels
214 if (TAG_CHANNEL.equals(tagName)) {
215 String id = parser.getAttributeValue(null, ATT_ID);
216 String channelName = parser.getAttributeValue(null, ATT_NAME);
217 int channelImportance = XmlUtils.readIntAttribute(
218 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
219 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
220 NotificationChannel channel = new NotificationChannel(id,
221 channelName, channelImportance);
222 if (forRestore) {
223 channel.populateFromXmlForRestore(parser, mContext);
224 } else {
225 channel.populateFromXml(parser);
226 }
227 r.channels.put(id, channel);
228 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400229 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400230 // Delegate
231 if (TAG_DELEGATE.equals(tagName)) {
232 int delegateId =
233 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
234 String delegateName =
235 XmlUtils.readStringAttribute(parser, ATT_NAME);
236 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
237 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
238 boolean userAllowed = XmlUtils.readBooleanAttribute(
239 parser, ATT_USER_ALLOWED,
240 Delegate.DEFAULT_USER_ALLOWED);
241 Delegate d = null;
242 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
243 delegateName)) {
244 d = new Delegate(
245 delegateName, delegateId, delegateEnabled,
246 userAllowed);
247 }
248 r.delegate = d;
249 }
250
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400251 }
252
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400253 try {
254 deleteDefaultChannelIfNeeded(r);
255 } catch (PackageManager.NameNotFoundException e) {
256 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
257 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400258 }
259 }
260 }
261 }
262 }
263 throw new IllegalStateException("Failed to reach END_DOCUMENT");
264 }
265
266 private PackagePreferences getPackagePreferences(String pkg, int uid) {
267 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400268 synchronized (mPackagePreferences) {
269 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400270 }
271 }
272
273 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
274 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500275 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800276 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400277 }
278
279 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800280 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400281 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400282 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400283 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400284 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400285 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400286 if (r == null) {
287 r = new PackagePreferences();
288 r.pkg = pkg;
289 r.uid = uid;
290 r.importance = importance;
291 r.priority = priority;
292 r.visibility = visibility;
293 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800294 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400295
296 try {
297 createDefaultChannelIfNeeded(r);
298 } catch (PackageManager.NameNotFoundException e) {
299 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
300 }
301
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400302 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400303 mRestoredWithoutUids.put(pkg, r);
304 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400305 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400306 }
307 }
308 return r;
309 }
310 }
311
312 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
313 PackageManager.NameNotFoundException {
314 final int userId = UserHandle.getUserId(r.uid);
315 final ApplicationInfo applicationInfo =
316 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
317 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
318 // O apps should not have the default channel.
319 return false;
320 }
321
322 // Otherwise, this app should have the default channel.
323 return true;
324 }
325
326 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
327 PackageManager.NameNotFoundException {
328 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
329 // Not present
330 return;
331 }
332
333 if (shouldHaveDefaultChannel(r)) {
334 // Keep the default channel until upgraded.
335 return;
336 }
337
338 // Remove Default Channel.
339 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
340 }
341
342 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
343 PackageManager.NameNotFoundException {
344 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
345 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
346 com.android.internal.R.string.default_notification_channel_label));
347 return;
348 }
349
350 if (!shouldHaveDefaultChannel(r)) {
351 // Keep the default channel until upgraded.
352 return;
353 }
354
355 // Create Default Channel
356 NotificationChannel channel;
357 channel = new NotificationChannel(
358 NotificationChannel.DEFAULT_CHANNEL_ID,
359 mContext.getString(R.string.default_notification_channel_label),
360 r.importance);
361 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
362 channel.setLockscreenVisibility(r.visibility);
363 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
364 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
365 }
366 if (r.priority != DEFAULT_PRIORITY) {
367 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
368 }
369 if (r.visibility != DEFAULT_VISIBILITY) {
370 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
371 }
372 r.channels.put(channel.getId(), channel);
373 }
374
375 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
376 out.startTag(null, TAG_RANKING);
377 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
378
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400379 synchronized (mPackagePreferences) {
380 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400381 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400382 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400383 //TODO: http://b/22388012
384 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
385 continue;
386 }
387 final boolean hasNonDefaultSettings =
388 r.importance != DEFAULT_IMPORTANCE
389 || r.priority != DEFAULT_PRIORITY
390 || r.visibility != DEFAULT_VISIBILITY
391 || r.showBadge != DEFAULT_SHOW_BADGE
392 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
393 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400394 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500395 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800396 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400397 if (hasNonDefaultSettings) {
398 out.startTag(null, TAG_PACKAGE);
399 out.attribute(null, ATT_NAME, r.pkg);
400 if (r.importance != DEFAULT_IMPORTANCE) {
401 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
402 }
403 if (r.priority != DEFAULT_PRIORITY) {
404 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
405 }
406 if (r.visibility != DEFAULT_VISIBILITY) {
407 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
408 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800409 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
410 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500411 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400412 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
413 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
414 Integer.toString(r.lockedAppFields));
415
416 if (!forBackup) {
417 out.attribute(null, ATT_UID, Integer.toString(r.uid));
418 }
419
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400420 if (r.delegate != null) {
421 out.startTag(null, TAG_DELEGATE);
422
423 out.attribute(null, ATT_NAME, r.delegate.mPkg);
424 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
425 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
426 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
427 }
428 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
429 out.attribute(null, ATT_USER_ALLOWED,
430 Boolean.toString(r.delegate.mUserAllowed));
431 }
432 out.endTag(null, TAG_DELEGATE);
433 }
434
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400435 for (NotificationChannelGroup group : r.groups.values()) {
436 group.writeXml(out);
437 }
438
439 for (NotificationChannel channel : r.channels.values()) {
440 if (forBackup) {
441 if (!channel.isDeleted()) {
442 channel.writeXmlForBackup(out, mContext);
443 }
444 } else {
445 channel.writeXml(out);
446 }
447 }
448
449 out.endTag(null, TAG_PACKAGE);
450 }
451 }
452 }
453 out.endTag(null, TAG_RANKING);
454 }
455
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800456 /**
457 * Sets whether bubbles are allowed.
458 *
459 * @param pkg the package to allow or not allow bubbles for.
460 * @param uid the uid to allow or not allow bubbles for.
461 * @param allowed whether bubbles are allowed.
462 */
463 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500464 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800465 p.allowBubble = allowed;
466 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500467 }
468
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800469 /**
470 * Whether bubbles are allowed.
471 *
472 * @param pkg the package to check if bubbles are allowed for
473 * @param uid the uid to check if bubbles are allowed for.
474 * @return whether bubbles are allowed.
475 */
Mady Mellor9db685a2019-01-23 13:23:37 -0800476 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800477 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500478 }
479
480 public int getAppLockedFields(String pkg, int uid) {
481 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
482 }
483
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400484 /**
485 * Gets importance.
486 */
487 @Override
488 public int getImportance(String packageName, int uid) {
489 return getOrCreatePackagePreferences(packageName, uid).importance;
490 }
491
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400492 /**
493 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
494 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
495 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
496 */
497 public boolean getIsAppImportanceLocked(String packageName, int uid) {
498 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
499 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
500 }
501
502 @Override
503 public boolean canShowBadge(String packageName, int uid) {
504 return getOrCreatePackagePreferences(packageName, uid).showBadge;
505 }
506
507 @Override
508 public void setShowBadge(String packageName, int uid, boolean showBadge) {
509 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
510 updateConfig();
511 }
512
513 @Override
514 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
515 if (groupId == null) {
516 return false;
517 }
518 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
519 NotificationChannelGroup group = r.groups.get(groupId);
520 if (group == null) {
521 return false;
522 }
523 return group.isBlocked();
524 }
525
526 int getPackagePriority(String pkg, int uid) {
527 return getOrCreatePackagePreferences(pkg, uid).priority;
528 }
529
530 int getPackageVisibility(String pkg, int uid) {
531 return getOrCreatePackagePreferences(pkg, uid).visibility;
532 }
533
534 @Override
535 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
536 boolean fromTargetApp) {
537 Preconditions.checkNotNull(pkg);
538 Preconditions.checkNotNull(group);
539 Preconditions.checkNotNull(group.getId());
540 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
541 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
542 if (r == null) {
543 throw new IllegalArgumentException("Invalid package");
544 }
545 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
546 if (!group.equals(oldGroup)) {
547 // will log for new entries as well as name/description changes
548 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
549 }
550 if (oldGroup != null) {
551 group.setChannels(oldGroup.getChannels());
552
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400553 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400554 if (fromTargetApp) {
555 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400556 group.unlockFields(group.getUserLockedFields());
557 group.lockFields(oldGroup.getUserLockedFields());
558 } else {
559 // but the system can
560 if (group.isBlocked() != oldGroup.isBlocked()) {
561 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500562 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400563 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400564 }
565 }
566 r.groups.put(group.getId(), group);
567 }
568
569 @Override
570 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
571 boolean fromTargetApp, boolean hasDndAccess) {
572 Preconditions.checkNotNull(pkg);
573 Preconditions.checkNotNull(channel);
574 Preconditions.checkNotNull(channel.getId());
575 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
576 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
577 if (r == null) {
578 throw new IllegalArgumentException("Invalid package");
579 }
580 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
581 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
582 }
583 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
584 throw new IllegalArgumentException("Reserved id");
585 }
586 NotificationChannel existing = r.channels.get(channel.getId());
587 // Keep most of the existing settings
588 if (existing != null && fromTargetApp) {
589 if (existing.isDeleted()) {
590 existing.setDeleted(false);
591
592 // log a resurrected channel as if it's new again
593 MetricsLogger.action(getChannelLog(channel, pkg).setType(
594 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
595 }
596
597 existing.setName(channel.getName().toString());
598 existing.setDescription(channel.getDescription());
599 existing.setBlockableSystem(channel.isBlockableSystem());
600 if (existing.getGroup() == null) {
601 existing.setGroup(channel.getGroup());
602 }
603
604 // Apps are allowed to downgrade channel importance if the user has not changed any
605 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500606 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400607 if (existing.getUserLockedFields() == 0 &&
608 channel.getImportance() < existing.getImportance()) {
609 existing.setImportance(channel.getImportance());
610 }
611
612 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
613 // fields on the channel yet
614 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
615 boolean bypassDnd = channel.canBypassDnd();
616 existing.setBypassDnd(bypassDnd);
617
Beverly0479cde22018-11-09 11:05:34 -0500618 if (bypassDnd != mAreChannelsBypassingDnd
619 || previousExistingImportance != existing.getImportance()) {
620 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400621 }
622 }
623
624 updateConfig();
625 return;
626 }
627 if (channel.getImportance() < IMPORTANCE_NONE
628 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
629 throw new IllegalArgumentException("Invalid importance level");
630 }
631
632 // Reset fields that apps aren't allowed to set.
633 if (fromTargetApp && !hasDndAccess) {
634 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
635 }
636 if (fromTargetApp) {
637 channel.setLockscreenVisibility(r.visibility);
638 }
639 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500640 channel.setImportanceLockedByOEM(r.oemLockedImportance);
641 if (!channel.isImportanceLockedByOEM()) {
642 if (r.futureOemLockedChannels.remove(channel.getId())) {
643 channel.setImportanceLockedByOEM(true);
644 }
645 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400646 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
647 channel.setLockscreenVisibility(
648 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
649 }
650 if (!r.showBadge) {
651 channel.setShowBadge(false);
652 }
653
654 r.channels.put(channel.getId(), channel);
655 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500656 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400657 }
658 MetricsLogger.action(getChannelLog(channel, pkg).setType(
659 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
660 }
661
662 void clearLockedFields(NotificationChannel channel) {
663 channel.unlockFields(channel.getUserLockedFields());
664 }
665
666 @Override
667 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
668 boolean fromUser) {
669 Preconditions.checkNotNull(updatedChannel);
670 Preconditions.checkNotNull(updatedChannel.getId());
671 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
672 if (r == null) {
673 throw new IllegalArgumentException("Invalid package");
674 }
675 NotificationChannel channel = r.channels.get(updatedChannel.getId());
676 if (channel == null || channel.isDeleted()) {
677 throw new IllegalArgumentException("Channel does not exist");
678 }
679 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
680 updatedChannel.setLockscreenVisibility(
681 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
682 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400683 if (fromUser) {
684 updatedChannel.lockFields(channel.getUserLockedFields());
685 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400686 } else {
687 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400688 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500689 // no importance updates are allowed if OEM blocked it
690 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
691 if (updatedChannel.isImportanceLockedByOEM()) {
692 updatedChannel.setImportance(channel.getImportance());
693 }
694
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400695 r.channels.put(updatedChannel.getId(), updatedChannel);
696
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400697 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400698 // copy settings to app level so they are inherited by new channels
699 // when the app migrates
700 r.importance = updatedChannel.getImportance();
701 r.priority = updatedChannel.canBypassDnd()
702 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
703 r.visibility = updatedChannel.getLockscreenVisibility();
704 r.showBadge = updatedChannel.canShowBadge();
705 }
706
707 if (!channel.equals(updatedChannel)) {
708 // only log if there are real changes
709 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
710 }
711
Beverly0479cde22018-11-09 11:05:34 -0500712 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
713 || channel.getImportance() != updatedChannel.getImportance()) {
714 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400715 }
716 updateConfig();
717 }
718
719 @Override
720 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
721 boolean includeDeleted) {
722 Preconditions.checkNotNull(pkg);
723 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
724 if (r == null) {
725 return null;
726 }
727 if (channelId == null) {
728 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
729 }
730 final NotificationChannel nc = r.channels.get(channelId);
731 if (nc != null && (includeDeleted || !nc.isDeleted())) {
732 return nc;
733 }
734 return null;
735 }
736
737 @Override
738 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
739 PackagePreferences r = getPackagePreferences(pkg, uid);
740 if (r == null) {
741 return;
742 }
743 NotificationChannel channel = r.channels.get(channelId);
744 if (channel != null) {
745 channel.setDeleted(true);
746 LogMaker lm = getChannelLog(channel, pkg);
747 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
748 MetricsLogger.action(lm);
749
750 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500751 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400752 }
753 }
754 }
755
756 @Override
757 @VisibleForTesting
758 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
759 Preconditions.checkNotNull(pkg);
760 Preconditions.checkNotNull(channelId);
761 PackagePreferences r = getPackagePreferences(pkg, uid);
762 if (r == null) {
763 return;
764 }
765 r.channels.remove(channelId);
766 }
767
768 @Override
769 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
770 Preconditions.checkNotNull(pkg);
771 PackagePreferences r = getPackagePreferences(pkg, uid);
772 if (r == null) {
773 return;
774 }
775 int N = r.channels.size() - 1;
776 for (int i = N; i >= 0; i--) {
777 String key = r.channels.keyAt(i);
778 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
779 r.channels.remove(key);
780 }
781 }
782 }
783
Julia Reynolds413ba842019-01-11 10:38:08 -0500784 public void lockChannelsForOEM(String[] appOrChannelList) {
785 if (appOrChannelList == null) {
786 return;
787 }
788 for (String appOrChannel : appOrChannelList) {
789 if (!TextUtils.isEmpty(appOrChannel)) {
790 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
791 if (appSplit != null && appSplit.length > 0) {
792 String appName = appSplit[0];
793 String channelId = appSplit.length == 2 ? appSplit[1] : null;
794
795 synchronized (mPackagePreferences) {
796 for (PackagePreferences r : mPackagePreferences.values()) {
797 if (r.pkg.equals(appName)) {
798 if (channelId == null) {
799 // lock all channels for the app
800 r.oemLockedImportance = true;
801 for (NotificationChannel channel : r.channels.values()) {
802 channel.setImportanceLockedByOEM(true);
803 }
804 } else {
805 NotificationChannel channel = r.channels.get(channelId);
806 if (channel != null) {
807 channel.setImportanceLockedByOEM(true);
808 } else {
809 // if this channel shows up in the future, make sure it'll
810 // be locked immediately
811 r.futureOemLockedChannels.add(channelId);
812 }
813 }
814 }
815 }
816 }
817 }
818 }
819 }
820 }
821
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400822 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
823 int uid, String groupId, boolean includeDeleted) {
824 Preconditions.checkNotNull(pkg);
825 PackagePreferences r = getPackagePreferences(pkg, uid);
826 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
827 return null;
828 }
829 NotificationChannelGroup group = r.groups.get(groupId).clone();
830 group.setChannels(new ArrayList<>());
831 int N = r.channels.size();
832 for (int i = 0; i < N; i++) {
833 final NotificationChannel nc = r.channels.valueAt(i);
834 if (includeDeleted || !nc.isDeleted()) {
835 if (groupId.equals(nc.getGroup())) {
836 group.addChannel(nc);
837 }
838 }
839 }
840 return group;
841 }
842
843 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
844 int uid) {
845 Preconditions.checkNotNull(pkg);
846 PackagePreferences r = getPackagePreferences(pkg, uid);
847 if (r == null) {
848 return null;
849 }
850 return r.groups.get(groupId);
851 }
852
853 @Override
854 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400855 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400856 Preconditions.checkNotNull(pkg);
857 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
858 PackagePreferences r = getPackagePreferences(pkg, uid);
859 if (r == null) {
860 return ParceledListSlice.emptyList();
861 }
862 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
863 int N = r.channels.size();
864 for (int i = 0; i < N; i++) {
865 final NotificationChannel nc = r.channels.valueAt(i);
866 if (includeDeleted || !nc.isDeleted()) {
867 if (nc.getGroup() != null) {
868 if (r.groups.get(nc.getGroup()) != null) {
869 NotificationChannelGroup ncg = groups.get(nc.getGroup());
870 if (ncg == null) {
871 ncg = r.groups.get(nc.getGroup()).clone();
872 ncg.setChannels(new ArrayList<>());
873 groups.put(nc.getGroup(), ncg);
874
875 }
876 ncg.addChannel(nc);
877 }
878 } else {
879 nonGrouped.addChannel(nc);
880 }
881 }
882 }
883 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
884 groups.put(null, nonGrouped);
885 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400886 if (includeEmpty) {
887 for (NotificationChannelGroup group : r.groups.values()) {
888 if (!groups.containsKey(group.getId())) {
889 groups.put(group.getId(), group);
890 }
891 }
892 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400893 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
894 }
895
896 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
897 String groupId) {
898 List<NotificationChannel> deletedChannels = new ArrayList<>();
899 PackagePreferences r = getPackagePreferences(pkg, uid);
900 if (r == null || TextUtils.isEmpty(groupId)) {
901 return deletedChannels;
902 }
903
904 r.groups.remove(groupId);
905
906 int N = r.channels.size();
907 for (int i = 0; i < N; i++) {
908 final NotificationChannel nc = r.channels.valueAt(i);
909 if (groupId.equals(nc.getGroup())) {
910 nc.setDeleted(true);
911 deletedChannels.add(nc);
912 }
913 }
914 return deletedChannels;
915 }
916
917 @Override
918 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
919 int uid) {
920 PackagePreferences r = getPackagePreferences(pkg, uid);
921 if (r == null) {
922 return new ArrayList<>();
923 }
924 return r.groups.values();
925 }
926
927 @Override
928 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
929 boolean includeDeleted) {
930 Preconditions.checkNotNull(pkg);
931 List<NotificationChannel> channels = new ArrayList<>();
932 PackagePreferences r = getPackagePreferences(pkg, uid);
933 if (r == null) {
934 return ParceledListSlice.emptyList();
935 }
936 int N = r.channels.size();
937 for (int i = 0; i < N; i++) {
938 final NotificationChannel nc = r.channels.valueAt(i);
939 if (includeDeleted || !nc.isDeleted()) {
940 channels.add(nc);
941 }
942 }
943 return new ParceledListSlice<>(channels);
944 }
945
946 /**
Beverly0479cde22018-11-09 11:05:34 -0500947 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
948 */
949 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
950 int userId) {
951 List<NotificationChannel> channels = new ArrayList<>();
952 synchronized (mPackagePreferences) {
953 final PackagePreferences r = mPackagePreferences.get(
954 packagePreferencesKey(pkg, userId));
955 // notifications from this package aren't blocked
956 if (r != null && r.importance != IMPORTANCE_NONE) {
957 for (NotificationChannel channel : r.channels.values()) {
958 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
959 channels.add(channel);
960 }
961 }
962 }
963 }
964 return new ParceledListSlice<>(channels);
965 }
966
967 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400968 * True for pre-O apps that only have the default channel, or pre O apps that have no
969 * channels yet. This method will create the default channel for pre-O apps that don't have it.
970 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
971 * upgrades.
972 */
973 public boolean onlyHasDefaultChannel(String pkg, int uid) {
974 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
975 if (r.channels.size() == 1
976 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
977 return true;
978 }
979 return false;
980 }
981
982 public int getDeletedChannelCount(String pkg, int uid) {
983 Preconditions.checkNotNull(pkg);
984 int deletedCount = 0;
985 PackagePreferences r = getPackagePreferences(pkg, uid);
986 if (r == null) {
987 return deletedCount;
988 }
989 int N = r.channels.size();
990 for (int i = 0; i < N; i++) {
991 final NotificationChannel nc = r.channels.valueAt(i);
992 if (nc.isDeleted()) {
993 deletedCount++;
994 }
995 }
996 return deletedCount;
997 }
998
999 public int getBlockedChannelCount(String pkg, int uid) {
1000 Preconditions.checkNotNull(pkg);
1001 int blockedCount = 0;
1002 PackagePreferences r = getPackagePreferences(pkg, uid);
1003 if (r == null) {
1004 return blockedCount;
1005 }
1006 int N = r.channels.size();
1007 for (int i = 0; i < N; i++) {
1008 final NotificationChannel nc = r.channels.valueAt(i);
1009 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1010 blockedCount++;
1011 }
1012 }
1013 return blockedCount;
1014 }
1015
1016 public int getBlockedAppCount(int userId) {
1017 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001018 synchronized (mPackagePreferences) {
1019 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001020 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001021 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001022 if (userId == UserHandle.getUserId(r.uid)
1023 && r.importance == IMPORTANCE_NONE) {
1024 count++;
1025 }
1026 }
1027 }
1028 return count;
1029 }
1030
Beverly0479cde22018-11-09 11:05:34 -05001031 /**
1032 * Returns the number of apps that have at least one notification channel that can bypass DND
1033 * for given particular user
1034 */
1035 public int getAppsBypassingDndCount(int userId) {
1036 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001037 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001038 final int numPackagePreferences = mPackagePreferences.size();
1039 for (int i = 0; i < numPackagePreferences; i++) {
1040 final PackagePreferences r = mPackagePreferences.valueAt(i);
1041 // Package isn't associated with this userId or notifications from this package are
1042 // blocked
1043 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1044 continue;
1045 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001046
Beverly0479cde22018-11-09 11:05:34 -05001047 for (NotificationChannel channel : r.channels.values()) {
1048 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1049 count++;
1050 break;
1051 }
1052 }
1053 }
1054 }
1055 return count;
1056 }
1057
1058 /**
1059 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1060 * updating
1061 * @param userId
1062 */
1063 private void syncChannelsBypassingDnd(int userId) {
1064 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1065 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1066 updateChannelsBypassingDnd(userId);
1067 }
1068
1069 /**
1070 * Updates the user's NotificationPolicy based on whether the given userId
1071 * has channels bypassing DND
1072 * @param userId
1073 */
1074 private void updateChannelsBypassingDnd(int userId) {
1075 synchronized (mPackagePreferences) {
1076 final int numPackagePreferences = mPackagePreferences.size();
1077 for (int i = 0; i < numPackagePreferences; i++) {
1078 final PackagePreferences r = mPackagePreferences.valueAt(i);
1079 // Package isn't associated with this userId or notifications from this package are
1080 // blocked
1081 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1082 continue;
1083 }
1084
1085 for (NotificationChannel channel : r.channels.values()) {
1086 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001087 if (!mAreChannelsBypassingDnd) {
1088 mAreChannelsBypassingDnd = true;
1089 updateZenPolicy(true);
1090 }
1091 return;
1092 }
1093 }
1094 }
1095 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001096 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1097 if (mAreChannelsBypassingDnd) {
1098 mAreChannelsBypassingDnd = false;
1099 updateZenPolicy(false);
1100 }
1101 }
1102
Beverly0479cde22018-11-09 11:05:34 -05001103 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1104 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001105 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1106 return false;
Beverly0479cde22018-11-09 11:05:34 -05001107 }
1108
1109 // Channel is deleted or is blocked
1110 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1111 return false;
1112 }
1113
1114 return true;
1115 }
1116
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001117 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1118 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1119 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1120 policy.priorityCategories, policy.priorityCallSenders,
1121 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1122 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1123 : 0)));
1124 }
1125
1126 public boolean areChannelsBypassingDnd() {
1127 return mAreChannelsBypassingDnd;
1128 }
1129
1130 /**
1131 * Sets importance.
1132 */
1133 @Override
1134 public void setImportance(String pkgName, int uid, int importance) {
1135 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1136 updateConfig();
1137 }
1138
1139 public void setEnabled(String packageName, int uid, boolean enabled) {
1140 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1141 if (wasEnabled == enabled) {
1142 return;
1143 }
1144 setImportance(packageName, uid,
1145 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1146 }
1147
1148 /**
1149 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1150 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1151 * considered for sentiment adjustments (and thus never show a blocking helper).
1152 */
1153 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001154 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1155 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001156 return;
1157 }
1158
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001159 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001160 updateConfig();
1161 }
1162
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001163 /**
1164 * Returns the delegate for a given package, if it's allowed by the package and the user.
1165 */
1166 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1167 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1168
1169 if (prefs == null || prefs.delegate == null) {
1170 return null;
1171 }
1172 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1173 return null;
1174 }
1175 return prefs.delegate.mPkg;
1176 }
1177
1178 /**
1179 * Used by an app to delegate notification posting privileges to another apps.
1180 */
1181 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1182 String delegatePkg, int delegateUid) {
1183 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1184
1185 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1186 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1187 prefs.delegate = delegate;
1188 updateConfig();
1189 }
1190
1191 /**
1192 * Used by an app to turn off its notification delegate.
1193 */
1194 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1195 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1196 if (prefs != null && prefs.delegate != null) {
1197 prefs.delegate.mEnabled = false;
1198 updateConfig();
1199 }
1200 }
1201
1202 /**
1203 * Toggles whether an app can have a notification delegate on behalf of a user.
1204 */
1205 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1206 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1207 if (prefs != null && prefs.delegate != null) {
1208 prefs.delegate.mUserAllowed = userAllowed;
1209 updateConfig();
1210 }
1211 }
1212
1213 /**
1214 * Returns whether the given app is allowed on post notifications on behalf of the other given
1215 * app.
1216 */
1217 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1218 String potentialDelegatePkg, int potentialDelegateUid) {
1219 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1220
1221 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1222 }
1223
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001224 @VisibleForTesting
1225 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1226 if (original.canBypassDnd() != update.canBypassDnd()) {
1227 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1228 }
1229 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1230 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1231 }
1232 if (original.getImportance() != update.getImportance()) {
1233 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1234 }
1235 if (original.shouldShowLights() != update.shouldShowLights()
1236 || original.getLightColor() != update.getLightColor()) {
1237 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1238 }
1239 if (!Objects.equals(original.getSound(), update.getSound())) {
1240 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1241 }
1242 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1243 || original.shouldVibrate() != update.shouldVibrate()) {
1244 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1245 }
1246 if (original.canShowBadge() != update.canShowBadge()) {
1247 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1248 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001249 if (original.isBubbleAllowed() != update.isBubbleAllowed()) {
1250 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001251 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001252 }
1253
1254 public void dump(PrintWriter pw, String prefix,
1255 @NonNull NotificationManagerService.DumpFilter filter) {
1256 pw.print(prefix);
1257 pw.println("per-package config:");
1258
1259 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001260 synchronized (mPackagePreferences) {
1261 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001262 }
1263 pw.println("Restored without uid:");
1264 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1265 }
1266
1267 public void dump(ProtoOutputStream proto,
1268 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001269 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001270 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001271 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001272 }
1273 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1274 mRestoredWithoutUids);
1275 }
1276
1277 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1278 @NonNull NotificationManagerService.DumpFilter filter,
1279 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1280 final int N = PackagePreferencess.size();
1281 for (int i = 0; i < N; i++) {
1282 final PackagePreferences r = PackagePreferencess.valueAt(i);
1283 if (filter.matches(r.pkg)) {
1284 pw.print(prefix);
1285 pw.print(" AppSettings: ");
1286 pw.print(r.pkg);
1287 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001288 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001289 pw.print(')');
1290 if (r.importance != DEFAULT_IMPORTANCE) {
1291 pw.print(" importance=");
1292 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1293 }
1294 if (r.priority != DEFAULT_PRIORITY) {
1295 pw.print(" priority=");
1296 pw.print(Notification.priorityToString(r.priority));
1297 }
1298 if (r.visibility != DEFAULT_VISIBILITY) {
1299 pw.print(" visibility=");
1300 pw.print(Notification.visibilityToString(r.visibility));
1301 }
1302 pw.print(" showBadge=");
1303 pw.print(Boolean.toString(r.showBadge));
1304 pw.println();
1305 for (NotificationChannel channel : r.channels.values()) {
1306 pw.print(prefix);
1307 channel.dump(pw, " ", filter.redact);
1308 }
1309 for (NotificationChannelGroup group : r.groups.values()) {
1310 pw.print(prefix);
1311 pw.print(" ");
1312 pw.print(" ");
1313 pw.println(group);
1314 }
1315 }
1316 }
1317 }
1318
1319 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1320 @NonNull NotificationManagerService.DumpFilter filter,
1321 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1322 final int N = PackagePreferencess.size();
1323 long fToken;
1324 for (int i = 0; i < N; i++) {
1325 final PackagePreferences r = PackagePreferencess.valueAt(i);
1326 if (filter.matches(r.pkg)) {
1327 fToken = proto.start(fieldId);
1328
1329 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1330 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1331 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1332 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1333 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1334 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1335
1336 for (NotificationChannel channel : r.channels.values()) {
1337 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1338 }
1339 for (NotificationChannelGroup group : r.groups.values()) {
1340 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1341 }
1342
1343 proto.end(fToken);
1344 }
1345 }
1346 }
1347
1348 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1349 JSONObject ranking = new JSONObject();
1350 JSONArray PackagePreferencess = new JSONArray();
1351 try {
1352 ranking.put("noUid", mRestoredWithoutUids.size());
1353 } catch (JSONException e) {
1354 // pass
1355 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001356 synchronized (mPackagePreferences) {
1357 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001358 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001359 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001360 if (filter == null || filter.matches(r.pkg)) {
1361 JSONObject PackagePreferences = new JSONObject();
1362 try {
1363 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1364 PackagePreferences.put("packageName", r.pkg);
1365 if (r.importance != DEFAULT_IMPORTANCE) {
1366 PackagePreferences.put("importance",
1367 NotificationListenerService.Ranking.importanceToString(
1368 r.importance));
1369 }
1370 if (r.priority != DEFAULT_PRIORITY) {
1371 PackagePreferences.put("priority",
1372 Notification.priorityToString(r.priority));
1373 }
1374 if (r.visibility != DEFAULT_VISIBILITY) {
1375 PackagePreferences.put("visibility",
1376 Notification.visibilityToString(r.visibility));
1377 }
1378 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1379 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1380 }
1381 JSONArray channels = new JSONArray();
1382 for (NotificationChannel channel : r.channels.values()) {
1383 channels.put(channel.toJson());
1384 }
1385 PackagePreferences.put("channels", channels);
1386 JSONArray groups = new JSONArray();
1387 for (NotificationChannelGroup group : r.groups.values()) {
1388 groups.put(group.toJson());
1389 }
1390 PackagePreferences.put("groups", groups);
1391 } catch (JSONException e) {
1392 // pass
1393 }
1394 PackagePreferencess.put(PackagePreferences);
1395 }
1396 }
1397 }
1398 try {
1399 ranking.put("PackagePreferencess", PackagePreferencess);
1400 } catch (JSONException e) {
1401 // pass
1402 }
1403 return ranking;
1404 }
1405
1406 /**
1407 * Dump only the ban information as structured JSON for the stats collector.
1408 *
1409 * This is intentionally redundant with {#link dumpJson} because the old
1410 * scraper will expect this format.
1411 *
1412 * @param filter
1413 * @return
1414 */
1415 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1416 JSONArray bans = new JSONArray();
1417 Map<Integer, String> packageBans = getPackageBans();
1418 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1419 final int userId = UserHandle.getUserId(ban.getKey());
1420 final String packageName = ban.getValue();
1421 if (filter == null || filter.matches(packageName)) {
1422 JSONObject banJson = new JSONObject();
1423 try {
1424 banJson.put("userId", userId);
1425 banJson.put("packageName", packageName);
1426 } catch (JSONException e) {
1427 e.printStackTrace();
1428 }
1429 bans.put(banJson);
1430 }
1431 }
1432 return bans;
1433 }
1434
1435 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001436 synchronized (mPackagePreferences) {
1437 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001438 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1439 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001440 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001441 if (r.importance == IMPORTANCE_NONE) {
1442 packageBans.put(r.uid, r.pkg);
1443 }
1444 }
1445
1446 return packageBans;
1447 }
1448 }
1449
1450 /**
1451 * Dump only the channel information as structured JSON for the stats collector.
1452 *
1453 * This is intentionally redundant with {#link dumpJson} because the old
1454 * scraper will expect this format.
1455 *
1456 * @param filter
1457 * @return
1458 */
1459 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1460 JSONArray channels = new JSONArray();
1461 Map<String, Integer> packageChannels = getPackageChannels();
1462 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1463 final String packageName = channelCount.getKey();
1464 if (filter == null || filter.matches(packageName)) {
1465 JSONObject channelCountJson = new JSONObject();
1466 try {
1467 channelCountJson.put("packageName", packageName);
1468 channelCountJson.put("channelCount", channelCount.getValue());
1469 } catch (JSONException e) {
1470 e.printStackTrace();
1471 }
1472 channels.put(channelCountJson);
1473 }
1474 }
1475 return channels;
1476 }
1477
1478 private Map<String, Integer> getPackageChannels() {
1479 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001480 synchronized (mPackagePreferences) {
1481 for (int i = 0; i < mPackagePreferences.size(); i++) {
1482 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001483 int channelCount = 0;
1484 for (int j = 0; j < r.channels.size(); j++) {
1485 if (!r.channels.valueAt(j).isDeleted()) {
1486 channelCount++;
1487 }
1488 }
1489 packageChannels.put(r.pkg, channelCount);
1490 }
1491 }
1492 return packageChannels;
1493 }
1494
Beverly0479cde22018-11-09 11:05:34 -05001495 /**
1496 * Called when user switches
1497 */
1498 public void onUserSwitched(int userId) {
1499 syncChannelsBypassingDnd(userId);
1500 }
1501
1502 /**
1503 * Called when user is unlocked
1504 */
1505 public void onUserUnlocked(int userId) {
1506 syncChannelsBypassingDnd(userId);
1507 }
1508
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001509 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001510 synchronized (mPackagePreferences) {
1511 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001512 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001513 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001514 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001515 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001516 }
1517 }
1518 }
1519 }
1520
1521 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001522 synchronized (mPackagePreferences) {
1523 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001524 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001525 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001526 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1527 if (PackagePreferences.channels.containsKey(
1528 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1529 PackagePreferences.channels.get(
1530 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1531 context.getResources().getString(
1532 R.string.default_notification_channel_label));
1533 }
1534 }
1535 }
1536 }
1537 }
1538
1539 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1540 int[] uidList) {
1541 if (pkgList == null || pkgList.length == 0) {
1542 return; // nothing to do
1543 }
1544 boolean updated = false;
1545 if (removingPackage) {
1546 // Remove notification settings for uninstalled package
1547 int size = Math.min(pkgList.length, uidList.length);
1548 for (int i = 0; i < size; i++) {
1549 final String pkg = pkgList[i];
1550 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001551 synchronized (mPackagePreferences) {
1552 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001553 }
1554 mRestoredWithoutUids.remove(pkg);
1555 updated = true;
1556 }
1557 } else {
1558 for (String pkg : pkgList) {
1559 // Package install
1560 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1561 if (r != null) {
1562 try {
1563 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1564 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001565 synchronized (mPackagePreferences) {
1566 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001567 }
1568 updated = true;
1569 } catch (PackageManager.NameNotFoundException e) {
1570 // noop
1571 }
1572 }
1573 // Package upgrade
1574 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001575 synchronized (mPackagePreferences) {
1576 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1577 mPm.getPackageUidAsUser(pkg, changeUserId));
1578 if (fullPackagePreferences != null) {
1579 createDefaultChannelIfNeeded(fullPackagePreferences);
1580 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1581 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001582 }
1583 } catch (PackageManager.NameNotFoundException e) {
1584 }
1585 }
1586 }
1587
1588 if (updated) {
1589 updateConfig();
1590 }
1591 }
1592
1593 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1594 return new LogMaker(
1595 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1596 .ACTION_NOTIFICATION_CHANNEL)
1597 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1598 .setPackageName(pkg)
1599 .addTaggedData(
1600 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1601 .FIELD_NOTIFICATION_CHANNEL_ID,
1602 channel.getId())
1603 .addTaggedData(
1604 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1605 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1606 channel.getImportance());
1607 }
1608
1609 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1610 return new LogMaker(
1611 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1612 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1613 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1614 .addTaggedData(
1615 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1616 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1617 groupId)
1618 .setPackageName(pkg);
1619 }
1620
1621
1622 public void updateBadgingEnabled() {
1623 if (mBadgingEnabled == null) {
1624 mBadgingEnabled = new SparseBooleanArray();
1625 }
1626 boolean changed = false;
1627 // update the cached values
1628 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1629 int userId = mBadgingEnabled.keyAt(index);
1630 final boolean oldValue = mBadgingEnabled.get(userId);
1631 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1632 Settings.Secure.NOTIFICATION_BADGING,
1633 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1634 mBadgingEnabled.put(userId, newValue);
1635 changed |= oldValue != newValue;
1636 }
1637 if (changed) {
1638 updateConfig();
1639 }
1640 }
1641
1642 public boolean badgingEnabled(UserHandle userHandle) {
1643 int userId = userHandle.getIdentifier();
1644 if (userId == UserHandle.USER_ALL) {
1645 return false;
1646 }
1647 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1648 mBadgingEnabled.put(userId,
1649 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1650 Settings.Secure.NOTIFICATION_BADGING,
1651 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1652 }
1653 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1654 }
1655
1656 private void updateConfig() {
1657 mRankingHandler.requestSort();
1658 }
1659
1660 private static String packagePreferencesKey(String pkg, int uid) {
1661 return pkg + "|" + uid;
1662 }
1663
1664 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001665 String pkg;
1666 int uid = UNKNOWN_UID;
1667 int importance = DEFAULT_IMPORTANCE;
1668 int priority = DEFAULT_PRIORITY;
1669 int visibility = DEFAULT_VISIBILITY;
1670 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001671 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001672 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001673 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1674 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001675
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001676 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001677 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1678 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001679
1680 public boolean isValidDelegate(String pkg, int uid) {
1681 return delegate != null && delegate.isAllowed(pkg, uid);
1682 }
1683 }
1684
1685 private static class Delegate {
1686 static final boolean DEFAULT_ENABLED = true;
1687 static final boolean DEFAULT_USER_ALLOWED = true;
1688 String mPkg;
1689 int mUid = UNKNOWN_UID;
1690 boolean mEnabled = DEFAULT_ENABLED;
1691 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1692
1693 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1694 mPkg = pkg;
1695 mUid = uid;
1696 mEnabled = enabled;
1697 mUserAllowed = userAllowed;
1698 }
1699
1700 public boolean isAllowed(String pkg, int uid) {
1701 if (pkg == null || uid == UNKNOWN_UID) {
1702 return false;
1703 }
1704 return pkg.equals(mPkg)
1705 && uid == mUid
1706 && (mUserAllowed && mEnabled);
1707 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001708 }
1709}