blob: 6ed4f5c0317153aed797809fc91e87a276e44c44 [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";
Julia Reynolds12ad7ca2019-01-28 09:29:16 -050079 private static final String TAG_STATUS_ICONS = "status_icons";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040080
81 private static final String ATT_VERSION = "version";
82 private static final String ATT_NAME = "name";
83 private static final String ATT_UID = "uid";
84 private static final String ATT_ID = "id";
Mady Mellorc39b4ae2019-01-09 17:11:37 -080085 private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040086 private static final String ATT_PRIORITY = "priority";
87 private static final String ATT_VISIBILITY = "visibility";
88 private static final String ATT_IMPORTANCE = "importance";
89 private static final String ATT_SHOW_BADGE = "show_badge";
90 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040091 private static final String ATT_ENABLED = "enabled";
92 private static final String ATT_USER_ALLOWED = "allowed";
Julia Reynolds12ad7ca2019-01-28 09:29:16 -050093 private static final String ATT_HIDE_SILENT = "hide_silent";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040094
95 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
96 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
97 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds12ad7ca2019-01-28 09:29:16 -050098 @VisibleForTesting
99 static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400100 private static final boolean DEFAULT_SHOW_BADGE = true;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800101 private static final boolean DEFAULT_ALLOW_BUBBLE = true;
Julia Reynolds413ba842019-01-11 10:38:08 -0500102 private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800103
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400104 /**
105 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
106 * fields.
107 */
108 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
109
110 /**
111 * All user-lockable fields for a given application.
112 */
113 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
114 public @interface LockableAppFields {
115 int USER_LOCKED_IMPORTANCE = 0x00000001;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800116 int USER_LOCKED_BUBBLE = 0x00000002;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400117 }
118
119 // pkg|uid => PackagePreferences
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400120 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400121 // pkg => PackagePreferences
122 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
123
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400124 private final Context mContext;
125 private final PackageManager mPm;
126 private final RankingHandler mRankingHandler;
127 private final ZenModeHelper mZenModeHelper;
128
129 private SparseBooleanArray mBadgingEnabled;
130 private boolean mAreChannelsBypassingDnd;
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500131 private boolean mHideSilentStatusBarIcons;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400132
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400133 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
134 ZenModeHelper zenHelper) {
135 mContext = context;
136 mZenModeHelper = zenHelper;
137 mRankingHandler = rankingHandler;
138 mPm = pm;
139
140 updateBadgingEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500141 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400142 }
143
144 public void readXml(XmlPullParser parser, boolean forRestore)
145 throws XmlPullParserException, IOException {
146 int type = parser.getEventType();
147 if (type != XmlPullParser.START_TAG) return;
148 String tag = parser.getName();
149 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400150 synchronized (mPackagePreferences) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500151 // Clobber groups and channels with the xml, but don't delete other data that wasn't
152 // present at the time of serialization.
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400153 mRestoredWithoutUids.clear();
154 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
155 tag = parser.getName();
156 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
157 return;
158 }
159 if (type == XmlPullParser.START_TAG) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500160 if (TAG_STATUS_ICONS.equals(tag)) {
161 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
162 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
163 } else if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400164 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
165 String name = parser.getAttributeValue(null, ATT_NAME);
166 if (!TextUtils.isEmpty(name)) {
167 if (forRestore) {
168 try {
169 //TODO: http://b/22388012
170 uid = mPm.getPackageUidAsUser(name,
171 UserHandle.USER_SYSTEM);
172 } catch (PackageManager.NameNotFoundException e) {
173 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400174 }
175 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400176
177 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
178 XmlUtils.readIntAttribute(
179 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
180 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
181 DEFAULT_PRIORITY),
182 XmlUtils.readIntAttribute(
183 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
184 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500185 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
186 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800187 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400188 r.importance = XmlUtils.readIntAttribute(
189 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
190 r.priority = XmlUtils.readIntAttribute(
191 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
192 r.visibility = XmlUtils.readIntAttribute(
193 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
194 r.showBadge = XmlUtils.readBooleanAttribute(
195 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
196 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
197 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
198
199 final int innerDepth = parser.getDepth();
200 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
201 && (type != XmlPullParser.END_TAG
202 || parser.getDepth() > innerDepth)) {
203 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
204 continue;
205 }
206
207 String tagName = parser.getName();
208 // Channel groups
209 if (TAG_GROUP.equals(tagName)) {
210 String id = parser.getAttributeValue(null, ATT_ID);
211 CharSequence groupName = parser.getAttributeValue(null,
212 ATT_NAME);
213 if (!TextUtils.isEmpty(id)) {
214 NotificationChannelGroup group
215 = new NotificationChannelGroup(id, groupName);
216 group.populateFromXml(parser);
217 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400218 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400219 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400220 // Channels
221 if (TAG_CHANNEL.equals(tagName)) {
222 String id = parser.getAttributeValue(null, ATT_ID);
223 String channelName = parser.getAttributeValue(null, ATT_NAME);
224 int channelImportance = XmlUtils.readIntAttribute(
225 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
226 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
227 NotificationChannel channel = new NotificationChannel(id,
228 channelName, channelImportance);
229 if (forRestore) {
230 channel.populateFromXmlForRestore(parser, mContext);
231 } else {
232 channel.populateFromXml(parser);
233 }
234 r.channels.put(id, channel);
235 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400236 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400237 // Delegate
238 if (TAG_DELEGATE.equals(tagName)) {
239 int delegateId =
240 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
241 String delegateName =
242 XmlUtils.readStringAttribute(parser, ATT_NAME);
243 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
244 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
245 boolean userAllowed = XmlUtils.readBooleanAttribute(
246 parser, ATT_USER_ALLOWED,
247 Delegate.DEFAULT_USER_ALLOWED);
248 Delegate d = null;
249 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
250 delegateName)) {
251 d = new Delegate(
252 delegateName, delegateId, delegateEnabled,
253 userAllowed);
254 }
255 r.delegate = d;
256 }
257
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400258 }
259
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400260 try {
261 deleteDefaultChannelIfNeeded(r);
262 } catch (PackageManager.NameNotFoundException e) {
263 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
264 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400265 }
266 }
267 }
268 }
269 }
270 throw new IllegalStateException("Failed to reach END_DOCUMENT");
271 }
272
273 private PackagePreferences getPackagePreferences(String pkg, int uid) {
274 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400275 synchronized (mPackagePreferences) {
276 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400277 }
278 }
279
280 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
281 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500282 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800283 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400284 }
285
286 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800287 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400288 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400289 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400290 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400291 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400292 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400293 if (r == null) {
294 r = new PackagePreferences();
295 r.pkg = pkg;
296 r.uid = uid;
297 r.importance = importance;
298 r.priority = priority;
299 r.visibility = visibility;
300 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800301 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400302
303 try {
304 createDefaultChannelIfNeeded(r);
305 } catch (PackageManager.NameNotFoundException e) {
306 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
307 }
308
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400309 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400310 mRestoredWithoutUids.put(pkg, r);
311 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400312 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400313 }
314 }
315 return r;
316 }
317 }
318
319 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
320 PackageManager.NameNotFoundException {
321 final int userId = UserHandle.getUserId(r.uid);
322 final ApplicationInfo applicationInfo =
323 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
324 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
325 // O apps should not have the default channel.
326 return false;
327 }
328
329 // Otherwise, this app should have the default channel.
330 return true;
331 }
332
333 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
334 PackageManager.NameNotFoundException {
335 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
336 // Not present
337 return;
338 }
339
340 if (shouldHaveDefaultChannel(r)) {
341 // Keep the default channel until upgraded.
342 return;
343 }
344
345 // Remove Default Channel.
346 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
347 }
348
349 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
350 PackageManager.NameNotFoundException {
351 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
352 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
353 com.android.internal.R.string.default_notification_channel_label));
354 return;
355 }
356
357 if (!shouldHaveDefaultChannel(r)) {
358 // Keep the default channel until upgraded.
359 return;
360 }
361
362 // Create Default Channel
363 NotificationChannel channel;
364 channel = new NotificationChannel(
365 NotificationChannel.DEFAULT_CHANNEL_ID,
366 mContext.getString(R.string.default_notification_channel_label),
367 r.importance);
368 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
369 channel.setLockscreenVisibility(r.visibility);
370 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
371 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
372 }
373 if (r.priority != DEFAULT_PRIORITY) {
374 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
375 }
376 if (r.visibility != DEFAULT_VISIBILITY) {
377 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
378 }
379 r.channels.put(channel.getId(), channel);
380 }
381
382 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
383 out.startTag(null, TAG_RANKING);
384 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500385 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
386 out.startTag(null, TAG_STATUS_ICONS);
387 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
388 out.endTag(null, TAG_STATUS_ICONS);
389 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400390
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400391 synchronized (mPackagePreferences) {
392 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400393 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400394 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400395 //TODO: http://b/22388012
396 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
397 continue;
398 }
399 final boolean hasNonDefaultSettings =
400 r.importance != DEFAULT_IMPORTANCE
401 || r.priority != DEFAULT_PRIORITY
402 || r.visibility != DEFAULT_VISIBILITY
403 || r.showBadge != DEFAULT_SHOW_BADGE
404 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
405 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400406 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500407 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800408 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400409 if (hasNonDefaultSettings) {
410 out.startTag(null, TAG_PACKAGE);
411 out.attribute(null, ATT_NAME, r.pkg);
412 if (r.importance != DEFAULT_IMPORTANCE) {
413 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
414 }
415 if (r.priority != DEFAULT_PRIORITY) {
416 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
417 }
418 if (r.visibility != DEFAULT_VISIBILITY) {
419 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
420 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800421 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
422 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500423 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400424 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
425 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
426 Integer.toString(r.lockedAppFields));
427
428 if (!forBackup) {
429 out.attribute(null, ATT_UID, Integer.toString(r.uid));
430 }
431
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400432 if (r.delegate != null) {
433 out.startTag(null, TAG_DELEGATE);
434
435 out.attribute(null, ATT_NAME, r.delegate.mPkg);
436 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
437 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
438 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
439 }
440 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
441 out.attribute(null, ATT_USER_ALLOWED,
442 Boolean.toString(r.delegate.mUserAllowed));
443 }
444 out.endTag(null, TAG_DELEGATE);
445 }
446
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400447 for (NotificationChannelGroup group : r.groups.values()) {
448 group.writeXml(out);
449 }
450
451 for (NotificationChannel channel : r.channels.values()) {
452 if (forBackup) {
453 if (!channel.isDeleted()) {
454 channel.writeXmlForBackup(out, mContext);
455 }
456 } else {
457 channel.writeXml(out);
458 }
459 }
460
461 out.endTag(null, TAG_PACKAGE);
462 }
463 }
464 }
465 out.endTag(null, TAG_RANKING);
466 }
467
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800468 /**
469 * Sets whether bubbles are allowed.
470 *
471 * @param pkg the package to allow or not allow bubbles for.
472 * @param uid the uid to allow or not allow bubbles for.
473 * @param allowed whether bubbles are allowed.
474 */
475 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500476 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800477 p.allowBubble = allowed;
478 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500479 }
480
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800481 /**
482 * Whether bubbles are allowed.
483 *
484 * @param pkg the package to check if bubbles are allowed for
485 * @param uid the uid to check if bubbles are allowed for.
486 * @return whether bubbles are allowed.
487 */
Mady Mellor9db685a2019-01-23 13:23:37 -0800488 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800489 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500490 }
491
492 public int getAppLockedFields(String pkg, int uid) {
493 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
494 }
495
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400496 /**
497 * Gets importance.
498 */
499 @Override
500 public int getImportance(String packageName, int uid) {
501 return getOrCreatePackagePreferences(packageName, uid).importance;
502 }
503
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400504 /**
505 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
506 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
507 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
508 */
509 public boolean getIsAppImportanceLocked(String packageName, int uid) {
510 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
511 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
512 }
513
514 @Override
515 public boolean canShowBadge(String packageName, int uid) {
516 return getOrCreatePackagePreferences(packageName, uid).showBadge;
517 }
518
519 @Override
520 public void setShowBadge(String packageName, int uid, boolean showBadge) {
521 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
522 updateConfig();
523 }
524
525 @Override
526 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
527 if (groupId == null) {
528 return false;
529 }
530 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
531 NotificationChannelGroup group = r.groups.get(groupId);
532 if (group == null) {
533 return false;
534 }
535 return group.isBlocked();
536 }
537
538 int getPackagePriority(String pkg, int uid) {
539 return getOrCreatePackagePreferences(pkg, uid).priority;
540 }
541
542 int getPackageVisibility(String pkg, int uid) {
543 return getOrCreatePackagePreferences(pkg, uid).visibility;
544 }
545
546 @Override
547 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
548 boolean fromTargetApp) {
549 Preconditions.checkNotNull(pkg);
550 Preconditions.checkNotNull(group);
551 Preconditions.checkNotNull(group.getId());
552 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
553 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
554 if (r == null) {
555 throw new IllegalArgumentException("Invalid package");
556 }
557 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
558 if (!group.equals(oldGroup)) {
559 // will log for new entries as well as name/description changes
560 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
561 }
562 if (oldGroup != null) {
563 group.setChannels(oldGroup.getChannels());
564
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400565 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400566 if (fromTargetApp) {
567 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400568 group.unlockFields(group.getUserLockedFields());
569 group.lockFields(oldGroup.getUserLockedFields());
570 } else {
571 // but the system can
572 if (group.isBlocked() != oldGroup.isBlocked()) {
573 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500574 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400575 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400576 }
577 }
578 r.groups.put(group.getId(), group);
579 }
580
581 @Override
582 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
583 boolean fromTargetApp, boolean hasDndAccess) {
584 Preconditions.checkNotNull(pkg);
585 Preconditions.checkNotNull(channel);
586 Preconditions.checkNotNull(channel.getId());
587 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
588 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
589 if (r == null) {
590 throw new IllegalArgumentException("Invalid package");
591 }
592 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
593 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
594 }
595 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
596 throw new IllegalArgumentException("Reserved id");
597 }
598 NotificationChannel existing = r.channels.get(channel.getId());
599 // Keep most of the existing settings
600 if (existing != null && fromTargetApp) {
601 if (existing.isDeleted()) {
602 existing.setDeleted(false);
603
604 // log a resurrected channel as if it's new again
605 MetricsLogger.action(getChannelLog(channel, pkg).setType(
606 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
607 }
608
609 existing.setName(channel.getName().toString());
610 existing.setDescription(channel.getDescription());
611 existing.setBlockableSystem(channel.isBlockableSystem());
612 if (existing.getGroup() == null) {
613 existing.setGroup(channel.getGroup());
614 }
615
616 // Apps are allowed to downgrade channel importance if the user has not changed any
617 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500618 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400619 if (existing.getUserLockedFields() == 0 &&
620 channel.getImportance() < existing.getImportance()) {
621 existing.setImportance(channel.getImportance());
622 }
623
624 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
625 // fields on the channel yet
626 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
627 boolean bypassDnd = channel.canBypassDnd();
628 existing.setBypassDnd(bypassDnd);
629
Beverly0479cde22018-11-09 11:05:34 -0500630 if (bypassDnd != mAreChannelsBypassingDnd
631 || previousExistingImportance != existing.getImportance()) {
632 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400633 }
634 }
635
636 updateConfig();
637 return;
638 }
639 if (channel.getImportance() < IMPORTANCE_NONE
640 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
641 throw new IllegalArgumentException("Invalid importance level");
642 }
643
644 // Reset fields that apps aren't allowed to set.
645 if (fromTargetApp && !hasDndAccess) {
646 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
647 }
648 if (fromTargetApp) {
649 channel.setLockscreenVisibility(r.visibility);
650 }
651 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500652 channel.setImportanceLockedByOEM(r.oemLockedImportance);
653 if (!channel.isImportanceLockedByOEM()) {
654 if (r.futureOemLockedChannels.remove(channel.getId())) {
655 channel.setImportanceLockedByOEM(true);
656 }
657 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400658 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
659 channel.setLockscreenVisibility(
660 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
661 }
662 if (!r.showBadge) {
663 channel.setShowBadge(false);
664 }
665
666 r.channels.put(channel.getId(), channel);
667 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500668 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400669 }
670 MetricsLogger.action(getChannelLog(channel, pkg).setType(
671 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
672 }
673
674 void clearLockedFields(NotificationChannel channel) {
675 channel.unlockFields(channel.getUserLockedFields());
676 }
677
678 @Override
679 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
680 boolean fromUser) {
681 Preconditions.checkNotNull(updatedChannel);
682 Preconditions.checkNotNull(updatedChannel.getId());
683 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
684 if (r == null) {
685 throw new IllegalArgumentException("Invalid package");
686 }
687 NotificationChannel channel = r.channels.get(updatedChannel.getId());
688 if (channel == null || channel.isDeleted()) {
689 throw new IllegalArgumentException("Channel does not exist");
690 }
691 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
692 updatedChannel.setLockscreenVisibility(
693 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
694 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400695 if (fromUser) {
696 updatedChannel.lockFields(channel.getUserLockedFields());
697 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400698 } else {
699 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400700 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500701 // no importance updates are allowed if OEM blocked it
702 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
703 if (updatedChannel.isImportanceLockedByOEM()) {
704 updatedChannel.setImportance(channel.getImportance());
705 }
706
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400707 r.channels.put(updatedChannel.getId(), updatedChannel);
708
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400709 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400710 // copy settings to app level so they are inherited by new channels
711 // when the app migrates
712 r.importance = updatedChannel.getImportance();
713 r.priority = updatedChannel.canBypassDnd()
714 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
715 r.visibility = updatedChannel.getLockscreenVisibility();
716 r.showBadge = updatedChannel.canShowBadge();
717 }
718
719 if (!channel.equals(updatedChannel)) {
720 // only log if there are real changes
721 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
722 }
723
Beverly0479cde22018-11-09 11:05:34 -0500724 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
725 || channel.getImportance() != updatedChannel.getImportance()) {
726 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400727 }
728 updateConfig();
729 }
730
731 @Override
732 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
733 boolean includeDeleted) {
734 Preconditions.checkNotNull(pkg);
735 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
736 if (r == null) {
737 return null;
738 }
739 if (channelId == null) {
740 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
741 }
742 final NotificationChannel nc = r.channels.get(channelId);
743 if (nc != null && (includeDeleted || !nc.isDeleted())) {
744 return nc;
745 }
746 return null;
747 }
748
749 @Override
750 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
751 PackagePreferences r = getPackagePreferences(pkg, uid);
752 if (r == null) {
753 return;
754 }
755 NotificationChannel channel = r.channels.get(channelId);
756 if (channel != null) {
757 channel.setDeleted(true);
758 LogMaker lm = getChannelLog(channel, pkg);
759 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
760 MetricsLogger.action(lm);
761
762 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500763 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400764 }
765 }
766 }
767
768 @Override
769 @VisibleForTesting
770 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
771 Preconditions.checkNotNull(pkg);
772 Preconditions.checkNotNull(channelId);
773 PackagePreferences r = getPackagePreferences(pkg, uid);
774 if (r == null) {
775 return;
776 }
777 r.channels.remove(channelId);
778 }
779
780 @Override
781 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
782 Preconditions.checkNotNull(pkg);
783 PackagePreferences r = getPackagePreferences(pkg, uid);
784 if (r == null) {
785 return;
786 }
787 int N = r.channels.size() - 1;
788 for (int i = N; i >= 0; i--) {
789 String key = r.channels.keyAt(i);
790 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
791 r.channels.remove(key);
792 }
793 }
794 }
795
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500796 public boolean shouldHideSilentStatusIcons() {
797 return mHideSilentStatusBarIcons;
798 }
799
800 public void setHideSilentStatusIcons(boolean hide) {
801 mHideSilentStatusBarIcons = hide;
802 }
803
Julia Reynolds413ba842019-01-11 10:38:08 -0500804 public void lockChannelsForOEM(String[] appOrChannelList) {
805 if (appOrChannelList == null) {
806 return;
807 }
808 for (String appOrChannel : appOrChannelList) {
809 if (!TextUtils.isEmpty(appOrChannel)) {
810 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
811 if (appSplit != null && appSplit.length > 0) {
812 String appName = appSplit[0];
813 String channelId = appSplit.length == 2 ? appSplit[1] : null;
814
815 synchronized (mPackagePreferences) {
816 for (PackagePreferences r : mPackagePreferences.values()) {
817 if (r.pkg.equals(appName)) {
818 if (channelId == null) {
819 // lock all channels for the app
820 r.oemLockedImportance = true;
821 for (NotificationChannel channel : r.channels.values()) {
822 channel.setImportanceLockedByOEM(true);
823 }
824 } else {
825 NotificationChannel channel = r.channels.get(channelId);
826 if (channel != null) {
827 channel.setImportanceLockedByOEM(true);
828 } else {
829 // if this channel shows up in the future, make sure it'll
830 // be locked immediately
831 r.futureOemLockedChannels.add(channelId);
832 }
833 }
834 }
835 }
836 }
837 }
838 }
839 }
840 }
841
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400842 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
843 int uid, String groupId, boolean includeDeleted) {
844 Preconditions.checkNotNull(pkg);
845 PackagePreferences r = getPackagePreferences(pkg, uid);
846 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
847 return null;
848 }
849 NotificationChannelGroup group = r.groups.get(groupId).clone();
850 group.setChannels(new ArrayList<>());
851 int N = r.channels.size();
852 for (int i = 0; i < N; i++) {
853 final NotificationChannel nc = r.channels.valueAt(i);
854 if (includeDeleted || !nc.isDeleted()) {
855 if (groupId.equals(nc.getGroup())) {
856 group.addChannel(nc);
857 }
858 }
859 }
860 return group;
861 }
862
863 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
864 int uid) {
865 Preconditions.checkNotNull(pkg);
866 PackagePreferences r = getPackagePreferences(pkg, uid);
867 if (r == null) {
868 return null;
869 }
870 return r.groups.get(groupId);
871 }
872
873 @Override
874 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400875 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400876 Preconditions.checkNotNull(pkg);
877 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
878 PackagePreferences r = getPackagePreferences(pkg, uid);
879 if (r == null) {
880 return ParceledListSlice.emptyList();
881 }
882 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
883 int N = r.channels.size();
884 for (int i = 0; i < N; i++) {
885 final NotificationChannel nc = r.channels.valueAt(i);
886 if (includeDeleted || !nc.isDeleted()) {
887 if (nc.getGroup() != null) {
888 if (r.groups.get(nc.getGroup()) != null) {
889 NotificationChannelGroup ncg = groups.get(nc.getGroup());
890 if (ncg == null) {
891 ncg = r.groups.get(nc.getGroup()).clone();
892 ncg.setChannels(new ArrayList<>());
893 groups.put(nc.getGroup(), ncg);
894
895 }
896 ncg.addChannel(nc);
897 }
898 } else {
899 nonGrouped.addChannel(nc);
900 }
901 }
902 }
903 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
904 groups.put(null, nonGrouped);
905 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400906 if (includeEmpty) {
907 for (NotificationChannelGroup group : r.groups.values()) {
908 if (!groups.containsKey(group.getId())) {
909 groups.put(group.getId(), group);
910 }
911 }
912 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400913 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
914 }
915
916 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
917 String groupId) {
918 List<NotificationChannel> deletedChannels = new ArrayList<>();
919 PackagePreferences r = getPackagePreferences(pkg, uid);
920 if (r == null || TextUtils.isEmpty(groupId)) {
921 return deletedChannels;
922 }
923
924 r.groups.remove(groupId);
925
926 int N = r.channels.size();
927 for (int i = 0; i < N; i++) {
928 final NotificationChannel nc = r.channels.valueAt(i);
929 if (groupId.equals(nc.getGroup())) {
930 nc.setDeleted(true);
931 deletedChannels.add(nc);
932 }
933 }
934 return deletedChannels;
935 }
936
937 @Override
938 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
939 int uid) {
940 PackagePreferences r = getPackagePreferences(pkg, uid);
941 if (r == null) {
942 return new ArrayList<>();
943 }
944 return r.groups.values();
945 }
946
947 @Override
948 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
949 boolean includeDeleted) {
950 Preconditions.checkNotNull(pkg);
951 List<NotificationChannel> channels = new ArrayList<>();
952 PackagePreferences r = getPackagePreferences(pkg, uid);
953 if (r == null) {
954 return ParceledListSlice.emptyList();
955 }
956 int N = r.channels.size();
957 for (int i = 0; i < N; i++) {
958 final NotificationChannel nc = r.channels.valueAt(i);
959 if (includeDeleted || !nc.isDeleted()) {
960 channels.add(nc);
961 }
962 }
963 return new ParceledListSlice<>(channels);
964 }
965
966 /**
Beverly0479cde22018-11-09 11:05:34 -0500967 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
968 */
969 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
970 int userId) {
971 List<NotificationChannel> channels = new ArrayList<>();
972 synchronized (mPackagePreferences) {
973 final PackagePreferences r = mPackagePreferences.get(
974 packagePreferencesKey(pkg, userId));
975 // notifications from this package aren't blocked
976 if (r != null && r.importance != IMPORTANCE_NONE) {
977 for (NotificationChannel channel : r.channels.values()) {
978 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
979 channels.add(channel);
980 }
981 }
982 }
983 }
984 return new ParceledListSlice<>(channels);
985 }
986
987 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400988 * True for pre-O apps that only have the default channel, or pre O apps that have no
989 * channels yet. This method will create the default channel for pre-O apps that don't have it.
990 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
991 * upgrades.
992 */
993 public boolean onlyHasDefaultChannel(String pkg, int uid) {
994 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
995 if (r.channels.size() == 1
996 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
997 return true;
998 }
999 return false;
1000 }
1001
1002 public int getDeletedChannelCount(String pkg, int uid) {
1003 Preconditions.checkNotNull(pkg);
1004 int deletedCount = 0;
1005 PackagePreferences r = getPackagePreferences(pkg, uid);
1006 if (r == null) {
1007 return deletedCount;
1008 }
1009 int N = r.channels.size();
1010 for (int i = 0; i < N; i++) {
1011 final NotificationChannel nc = r.channels.valueAt(i);
1012 if (nc.isDeleted()) {
1013 deletedCount++;
1014 }
1015 }
1016 return deletedCount;
1017 }
1018
1019 public int getBlockedChannelCount(String pkg, int uid) {
1020 Preconditions.checkNotNull(pkg);
1021 int blockedCount = 0;
1022 PackagePreferences r = getPackagePreferences(pkg, uid);
1023 if (r == null) {
1024 return blockedCount;
1025 }
1026 int N = r.channels.size();
1027 for (int i = 0; i < N; i++) {
1028 final NotificationChannel nc = r.channels.valueAt(i);
1029 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1030 blockedCount++;
1031 }
1032 }
1033 return blockedCount;
1034 }
1035
1036 public int getBlockedAppCount(int userId) {
1037 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001038 synchronized (mPackagePreferences) {
1039 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001040 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001041 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001042 if (userId == UserHandle.getUserId(r.uid)
1043 && r.importance == IMPORTANCE_NONE) {
1044 count++;
1045 }
1046 }
1047 }
1048 return count;
1049 }
1050
Beverly0479cde22018-11-09 11:05:34 -05001051 /**
1052 * Returns the number of apps that have at least one notification channel that can bypass DND
1053 * for given particular user
1054 */
1055 public int getAppsBypassingDndCount(int userId) {
1056 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001057 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001058 final int numPackagePreferences = mPackagePreferences.size();
1059 for (int i = 0; i < numPackagePreferences; i++) {
1060 final PackagePreferences r = mPackagePreferences.valueAt(i);
1061 // Package isn't associated with this userId or notifications from this package are
1062 // blocked
1063 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1064 continue;
1065 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001066
Beverly0479cde22018-11-09 11:05:34 -05001067 for (NotificationChannel channel : r.channels.values()) {
1068 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1069 count++;
1070 break;
1071 }
1072 }
1073 }
1074 }
1075 return count;
1076 }
1077
1078 /**
1079 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1080 * updating
1081 * @param userId
1082 */
1083 private void syncChannelsBypassingDnd(int userId) {
1084 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1085 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1086 updateChannelsBypassingDnd(userId);
1087 }
1088
1089 /**
1090 * Updates the user's NotificationPolicy based on whether the given userId
1091 * has channels bypassing DND
1092 * @param userId
1093 */
1094 private void updateChannelsBypassingDnd(int userId) {
1095 synchronized (mPackagePreferences) {
1096 final int numPackagePreferences = mPackagePreferences.size();
1097 for (int i = 0; i < numPackagePreferences; i++) {
1098 final PackagePreferences r = mPackagePreferences.valueAt(i);
1099 // Package isn't associated with this userId or notifications from this package are
1100 // blocked
1101 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1102 continue;
1103 }
1104
1105 for (NotificationChannel channel : r.channels.values()) {
1106 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001107 if (!mAreChannelsBypassingDnd) {
1108 mAreChannelsBypassingDnd = true;
1109 updateZenPolicy(true);
1110 }
1111 return;
1112 }
1113 }
1114 }
1115 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001116 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1117 if (mAreChannelsBypassingDnd) {
1118 mAreChannelsBypassingDnd = false;
1119 updateZenPolicy(false);
1120 }
1121 }
1122
Beverly0479cde22018-11-09 11:05:34 -05001123 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1124 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001125 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1126 return false;
Beverly0479cde22018-11-09 11:05:34 -05001127 }
1128
1129 // Channel is deleted or is blocked
1130 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1131 return false;
1132 }
1133
1134 return true;
1135 }
1136
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001137 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1138 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1139 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1140 policy.priorityCategories, policy.priorityCallSenders,
1141 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1142 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1143 : 0)));
1144 }
1145
1146 public boolean areChannelsBypassingDnd() {
1147 return mAreChannelsBypassingDnd;
1148 }
1149
1150 /**
1151 * Sets importance.
1152 */
1153 @Override
1154 public void setImportance(String pkgName, int uid, int importance) {
1155 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1156 updateConfig();
1157 }
1158
1159 public void setEnabled(String packageName, int uid, boolean enabled) {
1160 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1161 if (wasEnabled == enabled) {
1162 return;
1163 }
1164 setImportance(packageName, uid,
1165 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1166 }
1167
1168 /**
1169 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1170 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1171 * considered for sentiment adjustments (and thus never show a blocking helper).
1172 */
1173 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001174 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1175 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001176 return;
1177 }
1178
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001179 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001180 updateConfig();
1181 }
1182
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001183 /**
1184 * Returns the delegate for a given package, if it's allowed by the package and the user.
1185 */
1186 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1187 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1188
1189 if (prefs == null || prefs.delegate == null) {
1190 return null;
1191 }
1192 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1193 return null;
1194 }
1195 return prefs.delegate.mPkg;
1196 }
1197
1198 /**
1199 * Used by an app to delegate notification posting privileges to another apps.
1200 */
1201 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1202 String delegatePkg, int delegateUid) {
1203 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1204
1205 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1206 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1207 prefs.delegate = delegate;
1208 updateConfig();
1209 }
1210
1211 /**
1212 * Used by an app to turn off its notification delegate.
1213 */
1214 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1215 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1216 if (prefs != null && prefs.delegate != null) {
1217 prefs.delegate.mEnabled = false;
1218 updateConfig();
1219 }
1220 }
1221
1222 /**
1223 * Toggles whether an app can have a notification delegate on behalf of a user.
1224 */
1225 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1226 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1227 if (prefs != null && prefs.delegate != null) {
1228 prefs.delegate.mUserAllowed = userAllowed;
1229 updateConfig();
1230 }
1231 }
1232
1233 /**
1234 * Returns whether the given app is allowed on post notifications on behalf of the other given
1235 * app.
1236 */
1237 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1238 String potentialDelegatePkg, int potentialDelegateUid) {
1239 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1240
1241 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1242 }
1243
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001244 @VisibleForTesting
1245 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1246 if (original.canBypassDnd() != update.canBypassDnd()) {
1247 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1248 }
1249 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1250 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1251 }
1252 if (original.getImportance() != update.getImportance()) {
1253 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1254 }
1255 if (original.shouldShowLights() != update.shouldShowLights()
1256 || original.getLightColor() != update.getLightColor()) {
1257 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1258 }
1259 if (!Objects.equals(original.getSound(), update.getSound())) {
1260 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1261 }
1262 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1263 || original.shouldVibrate() != update.shouldVibrate()) {
1264 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1265 }
1266 if (original.canShowBadge() != update.canShowBadge()) {
1267 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1268 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001269 if (original.isBubbleAllowed() != update.isBubbleAllowed()) {
1270 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001271 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001272 }
1273
1274 public void dump(PrintWriter pw, String prefix,
1275 @NonNull NotificationManagerService.DumpFilter filter) {
1276 pw.print(prefix);
1277 pw.println("per-package config:");
1278
1279 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001280 synchronized (mPackagePreferences) {
1281 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001282 }
1283 pw.println("Restored without uid:");
1284 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1285 }
1286
1287 public void dump(ProtoOutputStream proto,
1288 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001289 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001290 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001291 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001292 }
1293 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1294 mRestoredWithoutUids);
1295 }
1296
1297 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1298 @NonNull NotificationManagerService.DumpFilter filter,
1299 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1300 final int N = PackagePreferencess.size();
1301 for (int i = 0; i < N; i++) {
1302 final PackagePreferences r = PackagePreferencess.valueAt(i);
1303 if (filter.matches(r.pkg)) {
1304 pw.print(prefix);
1305 pw.print(" AppSettings: ");
1306 pw.print(r.pkg);
1307 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001308 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001309 pw.print(')');
1310 if (r.importance != DEFAULT_IMPORTANCE) {
1311 pw.print(" importance=");
1312 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1313 }
1314 if (r.priority != DEFAULT_PRIORITY) {
1315 pw.print(" priority=");
1316 pw.print(Notification.priorityToString(r.priority));
1317 }
1318 if (r.visibility != DEFAULT_VISIBILITY) {
1319 pw.print(" visibility=");
1320 pw.print(Notification.visibilityToString(r.visibility));
1321 }
1322 pw.print(" showBadge=");
1323 pw.print(Boolean.toString(r.showBadge));
1324 pw.println();
1325 for (NotificationChannel channel : r.channels.values()) {
1326 pw.print(prefix);
1327 channel.dump(pw, " ", filter.redact);
1328 }
1329 for (NotificationChannelGroup group : r.groups.values()) {
1330 pw.print(prefix);
1331 pw.print(" ");
1332 pw.print(" ");
1333 pw.println(group);
1334 }
1335 }
1336 }
1337 }
1338
1339 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1340 @NonNull NotificationManagerService.DumpFilter filter,
1341 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1342 final int N = PackagePreferencess.size();
1343 long fToken;
1344 for (int i = 0; i < N; i++) {
1345 final PackagePreferences r = PackagePreferencess.valueAt(i);
1346 if (filter.matches(r.pkg)) {
1347 fToken = proto.start(fieldId);
1348
1349 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1350 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1351 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1352 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1353 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1354 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1355
1356 for (NotificationChannel channel : r.channels.values()) {
1357 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1358 }
1359 for (NotificationChannelGroup group : r.groups.values()) {
1360 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1361 }
1362
1363 proto.end(fToken);
1364 }
1365 }
1366 }
1367
1368 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1369 JSONObject ranking = new JSONObject();
1370 JSONArray PackagePreferencess = new JSONArray();
1371 try {
1372 ranking.put("noUid", mRestoredWithoutUids.size());
1373 } catch (JSONException e) {
1374 // pass
1375 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001376 synchronized (mPackagePreferences) {
1377 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001378 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001379 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001380 if (filter == null || filter.matches(r.pkg)) {
1381 JSONObject PackagePreferences = new JSONObject();
1382 try {
1383 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1384 PackagePreferences.put("packageName", r.pkg);
1385 if (r.importance != DEFAULT_IMPORTANCE) {
1386 PackagePreferences.put("importance",
1387 NotificationListenerService.Ranking.importanceToString(
1388 r.importance));
1389 }
1390 if (r.priority != DEFAULT_PRIORITY) {
1391 PackagePreferences.put("priority",
1392 Notification.priorityToString(r.priority));
1393 }
1394 if (r.visibility != DEFAULT_VISIBILITY) {
1395 PackagePreferences.put("visibility",
1396 Notification.visibilityToString(r.visibility));
1397 }
1398 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1399 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1400 }
1401 JSONArray channels = new JSONArray();
1402 for (NotificationChannel channel : r.channels.values()) {
1403 channels.put(channel.toJson());
1404 }
1405 PackagePreferences.put("channels", channels);
1406 JSONArray groups = new JSONArray();
1407 for (NotificationChannelGroup group : r.groups.values()) {
1408 groups.put(group.toJson());
1409 }
1410 PackagePreferences.put("groups", groups);
1411 } catch (JSONException e) {
1412 // pass
1413 }
1414 PackagePreferencess.put(PackagePreferences);
1415 }
1416 }
1417 }
1418 try {
1419 ranking.put("PackagePreferencess", PackagePreferencess);
1420 } catch (JSONException e) {
1421 // pass
1422 }
1423 return ranking;
1424 }
1425
1426 /**
1427 * Dump only the ban information as structured JSON for the stats collector.
1428 *
1429 * This is intentionally redundant with {#link dumpJson} because the old
1430 * scraper will expect this format.
1431 *
1432 * @param filter
1433 * @return
1434 */
1435 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1436 JSONArray bans = new JSONArray();
1437 Map<Integer, String> packageBans = getPackageBans();
1438 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1439 final int userId = UserHandle.getUserId(ban.getKey());
1440 final String packageName = ban.getValue();
1441 if (filter == null || filter.matches(packageName)) {
1442 JSONObject banJson = new JSONObject();
1443 try {
1444 banJson.put("userId", userId);
1445 banJson.put("packageName", packageName);
1446 } catch (JSONException e) {
1447 e.printStackTrace();
1448 }
1449 bans.put(banJson);
1450 }
1451 }
1452 return bans;
1453 }
1454
1455 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001456 synchronized (mPackagePreferences) {
1457 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001458 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1459 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001460 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001461 if (r.importance == IMPORTANCE_NONE) {
1462 packageBans.put(r.uid, r.pkg);
1463 }
1464 }
1465
1466 return packageBans;
1467 }
1468 }
1469
1470 /**
1471 * Dump only the channel information as structured JSON for the stats collector.
1472 *
1473 * This is intentionally redundant with {#link dumpJson} because the old
1474 * scraper will expect this format.
1475 *
1476 * @param filter
1477 * @return
1478 */
1479 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1480 JSONArray channels = new JSONArray();
1481 Map<String, Integer> packageChannels = getPackageChannels();
1482 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1483 final String packageName = channelCount.getKey();
1484 if (filter == null || filter.matches(packageName)) {
1485 JSONObject channelCountJson = new JSONObject();
1486 try {
1487 channelCountJson.put("packageName", packageName);
1488 channelCountJson.put("channelCount", channelCount.getValue());
1489 } catch (JSONException e) {
1490 e.printStackTrace();
1491 }
1492 channels.put(channelCountJson);
1493 }
1494 }
1495 return channels;
1496 }
1497
1498 private Map<String, Integer> getPackageChannels() {
1499 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001500 synchronized (mPackagePreferences) {
1501 for (int i = 0; i < mPackagePreferences.size(); i++) {
1502 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001503 int channelCount = 0;
1504 for (int j = 0; j < r.channels.size(); j++) {
1505 if (!r.channels.valueAt(j).isDeleted()) {
1506 channelCount++;
1507 }
1508 }
1509 packageChannels.put(r.pkg, channelCount);
1510 }
1511 }
1512 return packageChannels;
1513 }
1514
Beverly0479cde22018-11-09 11:05:34 -05001515 /**
1516 * Called when user switches
1517 */
1518 public void onUserSwitched(int userId) {
1519 syncChannelsBypassingDnd(userId);
1520 }
1521
1522 /**
1523 * Called when user is unlocked
1524 */
1525 public void onUserUnlocked(int userId) {
1526 syncChannelsBypassingDnd(userId);
1527 }
1528
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001529 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001530 synchronized (mPackagePreferences) {
1531 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001532 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001533 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001534 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001535 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001536 }
1537 }
1538 }
1539 }
1540
1541 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001542 synchronized (mPackagePreferences) {
1543 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001544 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001545 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001546 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1547 if (PackagePreferences.channels.containsKey(
1548 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1549 PackagePreferences.channels.get(
1550 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1551 context.getResources().getString(
1552 R.string.default_notification_channel_label));
1553 }
1554 }
1555 }
1556 }
1557 }
1558
1559 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1560 int[] uidList) {
1561 if (pkgList == null || pkgList.length == 0) {
1562 return; // nothing to do
1563 }
1564 boolean updated = false;
1565 if (removingPackage) {
1566 // Remove notification settings for uninstalled package
1567 int size = Math.min(pkgList.length, uidList.length);
1568 for (int i = 0; i < size; i++) {
1569 final String pkg = pkgList[i];
1570 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001571 synchronized (mPackagePreferences) {
1572 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001573 }
1574 mRestoredWithoutUids.remove(pkg);
1575 updated = true;
1576 }
1577 } else {
1578 for (String pkg : pkgList) {
1579 // Package install
1580 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1581 if (r != null) {
1582 try {
1583 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1584 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001585 synchronized (mPackagePreferences) {
1586 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001587 }
1588 updated = true;
1589 } catch (PackageManager.NameNotFoundException e) {
1590 // noop
1591 }
1592 }
1593 // Package upgrade
1594 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001595 synchronized (mPackagePreferences) {
1596 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1597 mPm.getPackageUidAsUser(pkg, changeUserId));
1598 if (fullPackagePreferences != null) {
1599 createDefaultChannelIfNeeded(fullPackagePreferences);
1600 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1601 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001602 }
1603 } catch (PackageManager.NameNotFoundException e) {
1604 }
1605 }
1606 }
1607
1608 if (updated) {
1609 updateConfig();
1610 }
1611 }
1612
1613 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1614 return new LogMaker(
1615 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1616 .ACTION_NOTIFICATION_CHANNEL)
1617 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1618 .setPackageName(pkg)
1619 .addTaggedData(
1620 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1621 .FIELD_NOTIFICATION_CHANNEL_ID,
1622 channel.getId())
1623 .addTaggedData(
1624 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1625 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1626 channel.getImportance());
1627 }
1628
1629 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1630 return new LogMaker(
1631 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1632 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1633 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1634 .addTaggedData(
1635 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1636 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1637 groupId)
1638 .setPackageName(pkg);
1639 }
1640
1641
1642 public void updateBadgingEnabled() {
1643 if (mBadgingEnabled == null) {
1644 mBadgingEnabled = new SparseBooleanArray();
1645 }
1646 boolean changed = false;
1647 // update the cached values
1648 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1649 int userId = mBadgingEnabled.keyAt(index);
1650 final boolean oldValue = mBadgingEnabled.get(userId);
1651 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1652 Settings.Secure.NOTIFICATION_BADGING,
1653 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1654 mBadgingEnabled.put(userId, newValue);
1655 changed |= oldValue != newValue;
1656 }
1657 if (changed) {
1658 updateConfig();
1659 }
1660 }
1661
1662 public boolean badgingEnabled(UserHandle userHandle) {
1663 int userId = userHandle.getIdentifier();
1664 if (userId == UserHandle.USER_ALL) {
1665 return false;
1666 }
1667 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1668 mBadgingEnabled.put(userId,
1669 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1670 Settings.Secure.NOTIFICATION_BADGING,
1671 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1672 }
1673 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1674 }
1675
1676 private void updateConfig() {
1677 mRankingHandler.requestSort();
1678 }
1679
1680 private static String packagePreferencesKey(String pkg, int uid) {
1681 return pkg + "|" + uid;
1682 }
1683
1684 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001685 String pkg;
1686 int uid = UNKNOWN_UID;
1687 int importance = DEFAULT_IMPORTANCE;
1688 int priority = DEFAULT_PRIORITY;
1689 int visibility = DEFAULT_VISIBILITY;
1690 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001691 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001692 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001693 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1694 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001695
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001696 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001697 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1698 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001699
1700 public boolean isValidDelegate(String pkg, int uid) {
1701 return delegate != null && delegate.isAllowed(pkg, uid);
1702 }
1703 }
1704
1705 private static class Delegate {
1706 static final boolean DEFAULT_ENABLED = true;
1707 static final boolean DEFAULT_USER_ALLOWED = true;
1708 String mPkg;
1709 int mUid = UNKNOWN_UID;
1710 boolean mEnabled = DEFAULT_ENABLED;
1711 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1712
1713 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1714 mPkg = pkg;
1715 mUid = uid;
1716 mEnabled = enabled;
1717 mUserAllowed = userAllowed;
1718 }
1719
1720 public boolean isAllowed(String pkg, int uid) {
1721 if (pkg == null || uid == UNKNOWN_UID) {
1722 return false;
1723 }
1724 return pkg.equals(mPkg)
1725 && uid == mUid
1726 && (mUserAllowed && mEnabled);
1727 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001728 }
1729}