blob: 555593654d9337e84fa8b3292317df44139edc18 [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
Annie Meng8b646fd2019-02-01 18:46:42 +0000144 public void readXml(XmlPullParser parser, boolean forRestore, int userId)
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400145 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)) {
Annie Meng8b646fd2019-02-01 18:46:42 +0000161 if (forRestore && userId != UserHandle.USER_SYSTEM) {
162 continue;
163 }
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500164 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
165 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
166 } else if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400167 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
168 String name = parser.getAttributeValue(null, ATT_NAME);
169 if (!TextUtils.isEmpty(name)) {
170 if (forRestore) {
171 try {
Annie Meng8b646fd2019-02-01 18:46:42 +0000172 uid = mPm.getPackageUidAsUser(name, userId);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400173 } catch (PackageManager.NameNotFoundException e) {
174 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400175 }
176 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400177
178 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
179 XmlUtils.readIntAttribute(
180 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
181 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
182 DEFAULT_PRIORITY),
183 XmlUtils.readIntAttribute(
184 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
185 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500186 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
187 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800188 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400189 r.importance = XmlUtils.readIntAttribute(
190 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
191 r.priority = XmlUtils.readIntAttribute(
192 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
193 r.visibility = XmlUtils.readIntAttribute(
194 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
195 r.showBadge = XmlUtils.readBooleanAttribute(
196 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
197 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
198 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
199
200 final int innerDepth = parser.getDepth();
201 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
202 && (type != XmlPullParser.END_TAG
203 || parser.getDepth() > innerDepth)) {
204 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
205 continue;
206 }
207
208 String tagName = parser.getName();
209 // Channel groups
210 if (TAG_GROUP.equals(tagName)) {
211 String id = parser.getAttributeValue(null, ATT_ID);
212 CharSequence groupName = parser.getAttributeValue(null,
213 ATT_NAME);
214 if (!TextUtils.isEmpty(id)) {
215 NotificationChannelGroup group
216 = new NotificationChannelGroup(id, groupName);
217 group.populateFromXml(parser);
218 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400219 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400220 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400221 // Channels
222 if (TAG_CHANNEL.equals(tagName)) {
223 String id = parser.getAttributeValue(null, ATT_ID);
224 String channelName = parser.getAttributeValue(null, ATT_NAME);
225 int channelImportance = XmlUtils.readIntAttribute(
226 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
227 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
228 NotificationChannel channel = new NotificationChannel(id,
229 channelName, channelImportance);
230 if (forRestore) {
231 channel.populateFromXmlForRestore(parser, mContext);
232 } else {
233 channel.populateFromXml(parser);
234 }
235 r.channels.put(id, channel);
236 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400237 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400238 // Delegate
239 if (TAG_DELEGATE.equals(tagName)) {
240 int delegateId =
241 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
242 String delegateName =
243 XmlUtils.readStringAttribute(parser, ATT_NAME);
244 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
245 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
246 boolean userAllowed = XmlUtils.readBooleanAttribute(
247 parser, ATT_USER_ALLOWED,
248 Delegate.DEFAULT_USER_ALLOWED);
249 Delegate d = null;
250 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
251 delegateName)) {
252 d = new Delegate(
253 delegateName, delegateId, delegateEnabled,
254 userAllowed);
255 }
256 r.delegate = d;
257 }
258
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400259 }
260
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400261 try {
262 deleteDefaultChannelIfNeeded(r);
263 } catch (PackageManager.NameNotFoundException e) {
264 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
265 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400266 }
267 }
268 }
269 }
270 }
271 throw new IllegalStateException("Failed to reach END_DOCUMENT");
272 }
273
274 private PackagePreferences getPackagePreferences(String pkg, int uid) {
275 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400276 synchronized (mPackagePreferences) {
277 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400278 }
279 }
280
281 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
282 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500283 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800284 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400285 }
286
287 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800288 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400289 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400290 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400291 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400292 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400293 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400294 if (r == null) {
295 r = new PackagePreferences();
296 r.pkg = pkg;
297 r.uid = uid;
298 r.importance = importance;
299 r.priority = priority;
300 r.visibility = visibility;
301 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800302 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400303
304 try {
305 createDefaultChannelIfNeeded(r);
306 } catch (PackageManager.NameNotFoundException e) {
307 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
308 }
309
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400310 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400311 mRestoredWithoutUids.put(pkg, r);
312 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400313 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400314 }
315 }
316 return r;
317 }
318 }
319
320 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
321 PackageManager.NameNotFoundException {
322 final int userId = UserHandle.getUserId(r.uid);
323 final ApplicationInfo applicationInfo =
324 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
325 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
326 // O apps should not have the default channel.
327 return false;
328 }
329
330 // Otherwise, this app should have the default channel.
331 return true;
332 }
333
334 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
335 PackageManager.NameNotFoundException {
336 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
337 // Not present
338 return;
339 }
340
341 if (shouldHaveDefaultChannel(r)) {
342 // Keep the default channel until upgraded.
343 return;
344 }
345
346 // Remove Default Channel.
347 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
348 }
349
350 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
351 PackageManager.NameNotFoundException {
352 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
353 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
354 com.android.internal.R.string.default_notification_channel_label));
355 return;
356 }
357
358 if (!shouldHaveDefaultChannel(r)) {
359 // Keep the default channel until upgraded.
360 return;
361 }
362
363 // Create Default Channel
364 NotificationChannel channel;
365 channel = new NotificationChannel(
366 NotificationChannel.DEFAULT_CHANNEL_ID,
367 mContext.getString(R.string.default_notification_channel_label),
368 r.importance);
369 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
370 channel.setLockscreenVisibility(r.visibility);
371 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
372 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
373 }
374 if (r.priority != DEFAULT_PRIORITY) {
375 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
376 }
377 if (r.visibility != DEFAULT_VISIBILITY) {
378 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
379 }
380 r.channels.put(channel.getId(), channel);
381 }
382
Annie Meng8b646fd2019-02-01 18:46:42 +0000383 public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400384 out.startTag(null, TAG_RANKING);
385 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
Annie Meng8b646fd2019-02-01 18:46:42 +0000386 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS
387 && (!forBackup || userId == UserHandle.USER_SYSTEM)) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500388 out.startTag(null, TAG_STATUS_ICONS);
389 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
390 out.endTag(null, TAG_STATUS_ICONS);
391 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400392
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400393 synchronized (mPackagePreferences) {
394 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400395 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400396 final PackagePreferences r = mPackagePreferences.valueAt(i);
Annie Meng8b646fd2019-02-01 18:46:42 +0000397 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400398 continue;
399 }
400 final boolean hasNonDefaultSettings =
401 r.importance != DEFAULT_IMPORTANCE
402 || r.priority != DEFAULT_PRIORITY
403 || r.visibility != DEFAULT_VISIBILITY
404 || r.showBadge != DEFAULT_SHOW_BADGE
405 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
406 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400407 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500408 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800409 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400410 if (hasNonDefaultSettings) {
411 out.startTag(null, TAG_PACKAGE);
412 out.attribute(null, ATT_NAME, r.pkg);
413 if (r.importance != DEFAULT_IMPORTANCE) {
414 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
415 }
416 if (r.priority != DEFAULT_PRIORITY) {
417 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
418 }
419 if (r.visibility != DEFAULT_VISIBILITY) {
420 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
421 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800422 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
423 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500424 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400425 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
426 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
427 Integer.toString(r.lockedAppFields));
428
429 if (!forBackup) {
430 out.attribute(null, ATT_UID, Integer.toString(r.uid));
431 }
432
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400433 if (r.delegate != null) {
434 out.startTag(null, TAG_DELEGATE);
435
436 out.attribute(null, ATT_NAME, r.delegate.mPkg);
437 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
438 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
439 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
440 }
441 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
442 out.attribute(null, ATT_USER_ALLOWED,
443 Boolean.toString(r.delegate.mUserAllowed));
444 }
445 out.endTag(null, TAG_DELEGATE);
446 }
447
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400448 for (NotificationChannelGroup group : r.groups.values()) {
449 group.writeXml(out);
450 }
451
452 for (NotificationChannel channel : r.channels.values()) {
453 if (forBackup) {
454 if (!channel.isDeleted()) {
455 channel.writeXmlForBackup(out, mContext);
456 }
457 } else {
458 channel.writeXml(out);
459 }
460 }
461
462 out.endTag(null, TAG_PACKAGE);
463 }
464 }
465 }
466 out.endTag(null, TAG_RANKING);
467 }
468
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800469 /**
470 * Sets whether bubbles are allowed.
471 *
472 * @param pkg the package to allow or not allow bubbles for.
473 * @param uid the uid to allow or not allow bubbles for.
474 * @param allowed whether bubbles are allowed.
475 */
476 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500477 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800478 p.allowBubble = allowed;
479 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500480 }
481
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800482 /**
483 * Whether bubbles are allowed.
484 *
485 * @param pkg the package to check if bubbles are allowed for
486 * @param uid the uid to check if bubbles are allowed for.
487 * @return whether bubbles are allowed.
488 */
Mady Mellor9db685a2019-01-23 13:23:37 -0800489 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800490 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500491 }
492
493 public int getAppLockedFields(String pkg, int uid) {
494 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
495 }
496
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400497 /**
498 * Gets importance.
499 */
500 @Override
501 public int getImportance(String packageName, int uid) {
502 return getOrCreatePackagePreferences(packageName, uid).importance;
503 }
504
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400505 /**
506 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
507 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
508 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
509 */
510 public boolean getIsAppImportanceLocked(String packageName, int uid) {
511 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
512 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
513 }
514
515 @Override
516 public boolean canShowBadge(String packageName, int uid) {
517 return getOrCreatePackagePreferences(packageName, uid).showBadge;
518 }
519
520 @Override
521 public void setShowBadge(String packageName, int uid, boolean showBadge) {
522 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
523 updateConfig();
524 }
525
526 @Override
527 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
528 if (groupId == null) {
529 return false;
530 }
531 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
532 NotificationChannelGroup group = r.groups.get(groupId);
533 if (group == null) {
534 return false;
535 }
536 return group.isBlocked();
537 }
538
539 int getPackagePriority(String pkg, int uid) {
540 return getOrCreatePackagePreferences(pkg, uid).priority;
541 }
542
543 int getPackageVisibility(String pkg, int uid) {
544 return getOrCreatePackagePreferences(pkg, uid).visibility;
545 }
546
547 @Override
548 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
549 boolean fromTargetApp) {
550 Preconditions.checkNotNull(pkg);
551 Preconditions.checkNotNull(group);
552 Preconditions.checkNotNull(group.getId());
553 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
554 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
555 if (r == null) {
556 throw new IllegalArgumentException("Invalid package");
557 }
558 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
559 if (!group.equals(oldGroup)) {
560 // will log for new entries as well as name/description changes
561 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
562 }
563 if (oldGroup != null) {
564 group.setChannels(oldGroup.getChannels());
565
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400566 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400567 if (fromTargetApp) {
568 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400569 group.unlockFields(group.getUserLockedFields());
570 group.lockFields(oldGroup.getUserLockedFields());
571 } else {
572 // but the system can
573 if (group.isBlocked() != oldGroup.isBlocked()) {
574 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500575 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400576 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400577 }
578 }
579 r.groups.put(group.getId(), group);
580 }
581
582 @Override
583 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
584 boolean fromTargetApp, boolean hasDndAccess) {
585 Preconditions.checkNotNull(pkg);
586 Preconditions.checkNotNull(channel);
587 Preconditions.checkNotNull(channel.getId());
588 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
589 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
590 if (r == null) {
591 throw new IllegalArgumentException("Invalid package");
592 }
593 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
594 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
595 }
596 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
597 throw new IllegalArgumentException("Reserved id");
598 }
599 NotificationChannel existing = r.channels.get(channel.getId());
600 // Keep most of the existing settings
601 if (existing != null && fromTargetApp) {
602 if (existing.isDeleted()) {
603 existing.setDeleted(false);
604
605 // log a resurrected channel as if it's new again
606 MetricsLogger.action(getChannelLog(channel, pkg).setType(
607 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
608 }
609
610 existing.setName(channel.getName().toString());
611 existing.setDescription(channel.getDescription());
612 existing.setBlockableSystem(channel.isBlockableSystem());
613 if (existing.getGroup() == null) {
614 existing.setGroup(channel.getGroup());
615 }
616
617 // Apps are allowed to downgrade channel importance if the user has not changed any
618 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500619 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400620 if (existing.getUserLockedFields() == 0 &&
621 channel.getImportance() < existing.getImportance()) {
622 existing.setImportance(channel.getImportance());
623 }
624
625 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
626 // fields on the channel yet
627 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
628 boolean bypassDnd = channel.canBypassDnd();
629 existing.setBypassDnd(bypassDnd);
630
Beverly0479cde22018-11-09 11:05:34 -0500631 if (bypassDnd != mAreChannelsBypassingDnd
632 || previousExistingImportance != existing.getImportance()) {
633 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400634 }
635 }
636
637 updateConfig();
638 return;
639 }
640 if (channel.getImportance() < IMPORTANCE_NONE
641 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
642 throw new IllegalArgumentException("Invalid importance level");
643 }
644
645 // Reset fields that apps aren't allowed to set.
646 if (fromTargetApp && !hasDndAccess) {
647 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
648 }
649 if (fromTargetApp) {
650 channel.setLockscreenVisibility(r.visibility);
651 }
652 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500653 channel.setImportanceLockedByOEM(r.oemLockedImportance);
654 if (!channel.isImportanceLockedByOEM()) {
655 if (r.futureOemLockedChannels.remove(channel.getId())) {
656 channel.setImportanceLockedByOEM(true);
657 }
658 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400659 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
660 channel.setLockscreenVisibility(
661 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
662 }
663 if (!r.showBadge) {
664 channel.setShowBadge(false);
665 }
666
667 r.channels.put(channel.getId(), channel);
668 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500669 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400670 }
671 MetricsLogger.action(getChannelLog(channel, pkg).setType(
672 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
673 }
674
675 void clearLockedFields(NotificationChannel channel) {
676 channel.unlockFields(channel.getUserLockedFields());
677 }
678
679 @Override
680 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
681 boolean fromUser) {
682 Preconditions.checkNotNull(updatedChannel);
683 Preconditions.checkNotNull(updatedChannel.getId());
684 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
685 if (r == null) {
686 throw new IllegalArgumentException("Invalid package");
687 }
688 NotificationChannel channel = r.channels.get(updatedChannel.getId());
689 if (channel == null || channel.isDeleted()) {
690 throw new IllegalArgumentException("Channel does not exist");
691 }
692 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
693 updatedChannel.setLockscreenVisibility(
694 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
695 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400696 if (fromUser) {
697 updatedChannel.lockFields(channel.getUserLockedFields());
698 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400699 } else {
700 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400701 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500702 // no importance updates are allowed if OEM blocked it
703 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
704 if (updatedChannel.isImportanceLockedByOEM()) {
705 updatedChannel.setImportance(channel.getImportance());
706 }
707
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400708 r.channels.put(updatedChannel.getId(), updatedChannel);
709
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400710 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400711 // copy settings to app level so they are inherited by new channels
712 // when the app migrates
713 r.importance = updatedChannel.getImportance();
714 r.priority = updatedChannel.canBypassDnd()
715 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
716 r.visibility = updatedChannel.getLockscreenVisibility();
717 r.showBadge = updatedChannel.canShowBadge();
718 }
719
720 if (!channel.equals(updatedChannel)) {
721 // only log if there are real changes
722 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
723 }
724
Beverly0479cde22018-11-09 11:05:34 -0500725 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
726 || channel.getImportance() != updatedChannel.getImportance()) {
727 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400728 }
729 updateConfig();
730 }
731
732 @Override
733 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
734 boolean includeDeleted) {
735 Preconditions.checkNotNull(pkg);
736 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
737 if (r == null) {
738 return null;
739 }
740 if (channelId == null) {
741 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
742 }
743 final NotificationChannel nc = r.channels.get(channelId);
744 if (nc != null && (includeDeleted || !nc.isDeleted())) {
745 return nc;
746 }
747 return null;
748 }
749
750 @Override
751 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
752 PackagePreferences r = getPackagePreferences(pkg, uid);
753 if (r == null) {
754 return;
755 }
756 NotificationChannel channel = r.channels.get(channelId);
757 if (channel != null) {
758 channel.setDeleted(true);
759 LogMaker lm = getChannelLog(channel, pkg);
760 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
761 MetricsLogger.action(lm);
762
763 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500764 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400765 }
766 }
767 }
768
769 @Override
770 @VisibleForTesting
771 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
772 Preconditions.checkNotNull(pkg);
773 Preconditions.checkNotNull(channelId);
774 PackagePreferences r = getPackagePreferences(pkg, uid);
775 if (r == null) {
776 return;
777 }
778 r.channels.remove(channelId);
779 }
780
781 @Override
782 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
783 Preconditions.checkNotNull(pkg);
784 PackagePreferences r = getPackagePreferences(pkg, uid);
785 if (r == null) {
786 return;
787 }
788 int N = r.channels.size() - 1;
789 for (int i = N; i >= 0; i--) {
790 String key = r.channels.keyAt(i);
791 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
792 r.channels.remove(key);
793 }
794 }
795 }
796
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500797 public boolean shouldHideSilentStatusIcons() {
798 return mHideSilentStatusBarIcons;
799 }
800
801 public void setHideSilentStatusIcons(boolean hide) {
802 mHideSilentStatusBarIcons = hide;
803 }
804
Julia Reynolds413ba842019-01-11 10:38:08 -0500805 public void lockChannelsForOEM(String[] appOrChannelList) {
806 if (appOrChannelList == null) {
807 return;
808 }
809 for (String appOrChannel : appOrChannelList) {
810 if (!TextUtils.isEmpty(appOrChannel)) {
811 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
812 if (appSplit != null && appSplit.length > 0) {
813 String appName = appSplit[0];
814 String channelId = appSplit.length == 2 ? appSplit[1] : null;
815
816 synchronized (mPackagePreferences) {
817 for (PackagePreferences r : mPackagePreferences.values()) {
818 if (r.pkg.equals(appName)) {
819 if (channelId == null) {
820 // lock all channels for the app
821 r.oemLockedImportance = true;
822 for (NotificationChannel channel : r.channels.values()) {
823 channel.setImportanceLockedByOEM(true);
824 }
825 } else {
826 NotificationChannel channel = r.channels.get(channelId);
827 if (channel != null) {
828 channel.setImportanceLockedByOEM(true);
829 } else {
830 // if this channel shows up in the future, make sure it'll
831 // be locked immediately
832 r.futureOemLockedChannels.add(channelId);
833 }
834 }
835 }
836 }
837 }
838 }
839 }
840 }
841 }
842
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400843 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
844 int uid, String groupId, boolean includeDeleted) {
845 Preconditions.checkNotNull(pkg);
846 PackagePreferences r = getPackagePreferences(pkg, uid);
847 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
848 return null;
849 }
850 NotificationChannelGroup group = r.groups.get(groupId).clone();
851 group.setChannels(new ArrayList<>());
852 int N = r.channels.size();
853 for (int i = 0; i < N; i++) {
854 final NotificationChannel nc = r.channels.valueAt(i);
855 if (includeDeleted || !nc.isDeleted()) {
856 if (groupId.equals(nc.getGroup())) {
857 group.addChannel(nc);
858 }
859 }
860 }
861 return group;
862 }
863
864 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
865 int uid) {
866 Preconditions.checkNotNull(pkg);
867 PackagePreferences r = getPackagePreferences(pkg, uid);
868 if (r == null) {
869 return null;
870 }
871 return r.groups.get(groupId);
872 }
873
874 @Override
875 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400876 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400877 Preconditions.checkNotNull(pkg);
878 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
879 PackagePreferences r = getPackagePreferences(pkg, uid);
880 if (r == null) {
881 return ParceledListSlice.emptyList();
882 }
883 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
884 int N = r.channels.size();
885 for (int i = 0; i < N; i++) {
886 final NotificationChannel nc = r.channels.valueAt(i);
887 if (includeDeleted || !nc.isDeleted()) {
888 if (nc.getGroup() != null) {
889 if (r.groups.get(nc.getGroup()) != null) {
890 NotificationChannelGroup ncg = groups.get(nc.getGroup());
891 if (ncg == null) {
892 ncg = r.groups.get(nc.getGroup()).clone();
893 ncg.setChannels(new ArrayList<>());
894 groups.put(nc.getGroup(), ncg);
895
896 }
897 ncg.addChannel(nc);
898 }
899 } else {
900 nonGrouped.addChannel(nc);
901 }
902 }
903 }
904 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
905 groups.put(null, nonGrouped);
906 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400907 if (includeEmpty) {
908 for (NotificationChannelGroup group : r.groups.values()) {
909 if (!groups.containsKey(group.getId())) {
910 groups.put(group.getId(), group);
911 }
912 }
913 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400914 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
915 }
916
917 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
918 String groupId) {
919 List<NotificationChannel> deletedChannels = new ArrayList<>();
920 PackagePreferences r = getPackagePreferences(pkg, uid);
921 if (r == null || TextUtils.isEmpty(groupId)) {
922 return deletedChannels;
923 }
924
925 r.groups.remove(groupId);
926
927 int N = r.channels.size();
928 for (int i = 0; i < N; i++) {
929 final NotificationChannel nc = r.channels.valueAt(i);
930 if (groupId.equals(nc.getGroup())) {
931 nc.setDeleted(true);
932 deletedChannels.add(nc);
933 }
934 }
935 return deletedChannels;
936 }
937
938 @Override
939 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
940 int uid) {
941 PackagePreferences r = getPackagePreferences(pkg, uid);
942 if (r == null) {
943 return new ArrayList<>();
944 }
945 return r.groups.values();
946 }
947
948 @Override
949 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
950 boolean includeDeleted) {
951 Preconditions.checkNotNull(pkg);
952 List<NotificationChannel> channels = new ArrayList<>();
953 PackagePreferences r = getPackagePreferences(pkg, uid);
954 if (r == null) {
955 return ParceledListSlice.emptyList();
956 }
957 int N = r.channels.size();
958 for (int i = 0; i < N; i++) {
959 final NotificationChannel nc = r.channels.valueAt(i);
960 if (includeDeleted || !nc.isDeleted()) {
961 channels.add(nc);
962 }
963 }
964 return new ParceledListSlice<>(channels);
965 }
966
967 /**
Beverly0479cde22018-11-09 11:05:34 -0500968 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
969 */
970 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
971 int userId) {
972 List<NotificationChannel> channels = new ArrayList<>();
973 synchronized (mPackagePreferences) {
974 final PackagePreferences r = mPackagePreferences.get(
975 packagePreferencesKey(pkg, userId));
976 // notifications from this package aren't blocked
977 if (r != null && r.importance != IMPORTANCE_NONE) {
978 for (NotificationChannel channel : r.channels.values()) {
979 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
980 channels.add(channel);
981 }
982 }
983 }
984 }
985 return new ParceledListSlice<>(channels);
986 }
987
988 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400989 * True for pre-O apps that only have the default channel, or pre O apps that have no
990 * channels yet. This method will create the default channel for pre-O apps that don't have it.
991 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
992 * upgrades.
993 */
994 public boolean onlyHasDefaultChannel(String pkg, int uid) {
995 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
996 if (r.channels.size() == 1
997 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
998 return true;
999 }
1000 return false;
1001 }
1002
1003 public int getDeletedChannelCount(String pkg, int uid) {
1004 Preconditions.checkNotNull(pkg);
1005 int deletedCount = 0;
1006 PackagePreferences r = getPackagePreferences(pkg, uid);
1007 if (r == null) {
1008 return deletedCount;
1009 }
1010 int N = r.channels.size();
1011 for (int i = 0; i < N; i++) {
1012 final NotificationChannel nc = r.channels.valueAt(i);
1013 if (nc.isDeleted()) {
1014 deletedCount++;
1015 }
1016 }
1017 return deletedCount;
1018 }
1019
1020 public int getBlockedChannelCount(String pkg, int uid) {
1021 Preconditions.checkNotNull(pkg);
1022 int blockedCount = 0;
1023 PackagePreferences r = getPackagePreferences(pkg, uid);
1024 if (r == null) {
1025 return blockedCount;
1026 }
1027 int N = r.channels.size();
1028 for (int i = 0; i < N; i++) {
1029 final NotificationChannel nc = r.channels.valueAt(i);
1030 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1031 blockedCount++;
1032 }
1033 }
1034 return blockedCount;
1035 }
1036
1037 public int getBlockedAppCount(int userId) {
1038 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001039 synchronized (mPackagePreferences) {
1040 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001041 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001042 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001043 if (userId == UserHandle.getUserId(r.uid)
1044 && r.importance == IMPORTANCE_NONE) {
1045 count++;
1046 }
1047 }
1048 }
1049 return count;
1050 }
1051
Beverly0479cde22018-11-09 11:05:34 -05001052 /**
1053 * Returns the number of apps that have at least one notification channel that can bypass DND
1054 * for given particular user
1055 */
1056 public int getAppsBypassingDndCount(int userId) {
1057 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001058 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001059 final int numPackagePreferences = mPackagePreferences.size();
1060 for (int i = 0; i < numPackagePreferences; i++) {
1061 final PackagePreferences r = mPackagePreferences.valueAt(i);
1062 // Package isn't associated with this userId or notifications from this package are
1063 // blocked
1064 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1065 continue;
1066 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001067
Beverly0479cde22018-11-09 11:05:34 -05001068 for (NotificationChannel channel : r.channels.values()) {
1069 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1070 count++;
1071 break;
1072 }
1073 }
1074 }
1075 }
1076 return count;
1077 }
1078
1079 /**
1080 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1081 * updating
1082 * @param userId
1083 */
1084 private void syncChannelsBypassingDnd(int userId) {
1085 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1086 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1087 updateChannelsBypassingDnd(userId);
1088 }
1089
1090 /**
1091 * Updates the user's NotificationPolicy based on whether the given userId
1092 * has channels bypassing DND
1093 * @param userId
1094 */
1095 private void updateChannelsBypassingDnd(int userId) {
1096 synchronized (mPackagePreferences) {
1097 final int numPackagePreferences = mPackagePreferences.size();
1098 for (int i = 0; i < numPackagePreferences; i++) {
1099 final PackagePreferences r = mPackagePreferences.valueAt(i);
1100 // Package isn't associated with this userId or notifications from this package are
1101 // blocked
1102 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1103 continue;
1104 }
1105
1106 for (NotificationChannel channel : r.channels.values()) {
1107 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001108 if (!mAreChannelsBypassingDnd) {
1109 mAreChannelsBypassingDnd = true;
1110 updateZenPolicy(true);
1111 }
1112 return;
1113 }
1114 }
1115 }
1116 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001117 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1118 if (mAreChannelsBypassingDnd) {
1119 mAreChannelsBypassingDnd = false;
1120 updateZenPolicy(false);
1121 }
1122 }
1123
Beverly0479cde22018-11-09 11:05:34 -05001124 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1125 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001126 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1127 return false;
Beverly0479cde22018-11-09 11:05:34 -05001128 }
1129
1130 // Channel is deleted or is blocked
1131 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1132 return false;
1133 }
1134
1135 return true;
1136 }
1137
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001138 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1139 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1140 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1141 policy.priorityCategories, policy.priorityCallSenders,
1142 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1143 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1144 : 0)));
1145 }
1146
1147 public boolean areChannelsBypassingDnd() {
1148 return mAreChannelsBypassingDnd;
1149 }
1150
1151 /**
1152 * Sets importance.
1153 */
1154 @Override
1155 public void setImportance(String pkgName, int uid, int importance) {
1156 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1157 updateConfig();
1158 }
1159
1160 public void setEnabled(String packageName, int uid, boolean enabled) {
1161 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1162 if (wasEnabled == enabled) {
1163 return;
1164 }
1165 setImportance(packageName, uid,
1166 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1167 }
1168
1169 /**
1170 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1171 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1172 * considered for sentiment adjustments (and thus never show a blocking helper).
1173 */
1174 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001175 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1176 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001177 return;
1178 }
1179
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001180 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001181 updateConfig();
1182 }
1183
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001184 /**
1185 * Returns the delegate for a given package, if it's allowed by the package and the user.
1186 */
1187 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1188 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1189
1190 if (prefs == null || prefs.delegate == null) {
1191 return null;
1192 }
1193 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1194 return null;
1195 }
1196 return prefs.delegate.mPkg;
1197 }
1198
1199 /**
1200 * Used by an app to delegate notification posting privileges to another apps.
1201 */
1202 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1203 String delegatePkg, int delegateUid) {
1204 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1205
1206 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1207 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1208 prefs.delegate = delegate;
1209 updateConfig();
1210 }
1211
1212 /**
1213 * Used by an app to turn off its notification delegate.
1214 */
1215 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1216 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1217 if (prefs != null && prefs.delegate != null) {
1218 prefs.delegate.mEnabled = false;
1219 updateConfig();
1220 }
1221 }
1222
1223 /**
1224 * Toggles whether an app can have a notification delegate on behalf of a user.
1225 */
1226 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1227 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1228 if (prefs != null && prefs.delegate != null) {
1229 prefs.delegate.mUserAllowed = userAllowed;
1230 updateConfig();
1231 }
1232 }
1233
1234 /**
1235 * Returns whether the given app is allowed on post notifications on behalf of the other given
1236 * app.
1237 */
1238 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1239 String potentialDelegatePkg, int potentialDelegateUid) {
1240 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1241
1242 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1243 }
1244
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001245 @VisibleForTesting
1246 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1247 if (original.canBypassDnd() != update.canBypassDnd()) {
1248 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1249 }
1250 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1251 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1252 }
1253 if (original.getImportance() != update.getImportance()) {
1254 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1255 }
1256 if (original.shouldShowLights() != update.shouldShowLights()
1257 || original.getLightColor() != update.getLightColor()) {
1258 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1259 }
1260 if (!Objects.equals(original.getSound(), update.getSound())) {
1261 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1262 }
1263 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1264 || original.shouldVibrate() != update.shouldVibrate()) {
1265 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1266 }
1267 if (original.canShowBadge() != update.canShowBadge()) {
1268 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1269 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001270 if (original.isBubbleAllowed() != update.isBubbleAllowed()) {
1271 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001272 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001273 }
1274
1275 public void dump(PrintWriter pw, String prefix,
1276 @NonNull NotificationManagerService.DumpFilter filter) {
1277 pw.print(prefix);
1278 pw.println("per-package config:");
1279
1280 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001281 synchronized (mPackagePreferences) {
1282 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001283 }
1284 pw.println("Restored without uid:");
1285 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1286 }
1287
1288 public void dump(ProtoOutputStream proto,
1289 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001290 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001291 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001292 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001293 }
1294 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1295 mRestoredWithoutUids);
1296 }
1297
1298 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1299 @NonNull NotificationManagerService.DumpFilter filter,
1300 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1301 final int N = PackagePreferencess.size();
1302 for (int i = 0; i < N; i++) {
1303 final PackagePreferences r = PackagePreferencess.valueAt(i);
1304 if (filter.matches(r.pkg)) {
1305 pw.print(prefix);
1306 pw.print(" AppSettings: ");
1307 pw.print(r.pkg);
1308 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001309 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001310 pw.print(')');
1311 if (r.importance != DEFAULT_IMPORTANCE) {
1312 pw.print(" importance=");
1313 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1314 }
1315 if (r.priority != DEFAULT_PRIORITY) {
1316 pw.print(" priority=");
1317 pw.print(Notification.priorityToString(r.priority));
1318 }
1319 if (r.visibility != DEFAULT_VISIBILITY) {
1320 pw.print(" visibility=");
1321 pw.print(Notification.visibilityToString(r.visibility));
1322 }
1323 pw.print(" showBadge=");
1324 pw.print(Boolean.toString(r.showBadge));
1325 pw.println();
1326 for (NotificationChannel channel : r.channels.values()) {
1327 pw.print(prefix);
1328 channel.dump(pw, " ", filter.redact);
1329 }
1330 for (NotificationChannelGroup group : r.groups.values()) {
1331 pw.print(prefix);
1332 pw.print(" ");
1333 pw.print(" ");
1334 pw.println(group);
1335 }
1336 }
1337 }
1338 }
1339
1340 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1341 @NonNull NotificationManagerService.DumpFilter filter,
1342 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1343 final int N = PackagePreferencess.size();
1344 long fToken;
1345 for (int i = 0; i < N; i++) {
1346 final PackagePreferences r = PackagePreferencess.valueAt(i);
1347 if (filter.matches(r.pkg)) {
1348 fToken = proto.start(fieldId);
1349
1350 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1351 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1352 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1353 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1354 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1355 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1356
1357 for (NotificationChannel channel : r.channels.values()) {
1358 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1359 }
1360 for (NotificationChannelGroup group : r.groups.values()) {
1361 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1362 }
1363
1364 proto.end(fToken);
1365 }
1366 }
1367 }
1368
1369 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1370 JSONObject ranking = new JSONObject();
1371 JSONArray PackagePreferencess = new JSONArray();
1372 try {
1373 ranking.put("noUid", mRestoredWithoutUids.size());
1374 } catch (JSONException e) {
1375 // pass
1376 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001377 synchronized (mPackagePreferences) {
1378 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001379 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001380 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001381 if (filter == null || filter.matches(r.pkg)) {
1382 JSONObject PackagePreferences = new JSONObject();
1383 try {
1384 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1385 PackagePreferences.put("packageName", r.pkg);
1386 if (r.importance != DEFAULT_IMPORTANCE) {
1387 PackagePreferences.put("importance",
1388 NotificationListenerService.Ranking.importanceToString(
1389 r.importance));
1390 }
1391 if (r.priority != DEFAULT_PRIORITY) {
1392 PackagePreferences.put("priority",
1393 Notification.priorityToString(r.priority));
1394 }
1395 if (r.visibility != DEFAULT_VISIBILITY) {
1396 PackagePreferences.put("visibility",
1397 Notification.visibilityToString(r.visibility));
1398 }
1399 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1400 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1401 }
1402 JSONArray channels = new JSONArray();
1403 for (NotificationChannel channel : r.channels.values()) {
1404 channels.put(channel.toJson());
1405 }
1406 PackagePreferences.put("channels", channels);
1407 JSONArray groups = new JSONArray();
1408 for (NotificationChannelGroup group : r.groups.values()) {
1409 groups.put(group.toJson());
1410 }
1411 PackagePreferences.put("groups", groups);
1412 } catch (JSONException e) {
1413 // pass
1414 }
1415 PackagePreferencess.put(PackagePreferences);
1416 }
1417 }
1418 }
1419 try {
1420 ranking.put("PackagePreferencess", PackagePreferencess);
1421 } catch (JSONException e) {
1422 // pass
1423 }
1424 return ranking;
1425 }
1426
1427 /**
1428 * Dump only the ban information as structured JSON for the stats collector.
1429 *
1430 * This is intentionally redundant with {#link dumpJson} because the old
1431 * scraper will expect this format.
1432 *
1433 * @param filter
1434 * @return
1435 */
1436 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1437 JSONArray bans = new JSONArray();
1438 Map<Integer, String> packageBans = getPackageBans();
1439 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1440 final int userId = UserHandle.getUserId(ban.getKey());
1441 final String packageName = ban.getValue();
1442 if (filter == null || filter.matches(packageName)) {
1443 JSONObject banJson = new JSONObject();
1444 try {
1445 banJson.put("userId", userId);
1446 banJson.put("packageName", packageName);
1447 } catch (JSONException e) {
1448 e.printStackTrace();
1449 }
1450 bans.put(banJson);
1451 }
1452 }
1453 return bans;
1454 }
1455
1456 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001457 synchronized (mPackagePreferences) {
1458 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001459 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1460 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001461 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001462 if (r.importance == IMPORTANCE_NONE) {
1463 packageBans.put(r.uid, r.pkg);
1464 }
1465 }
1466
1467 return packageBans;
1468 }
1469 }
1470
1471 /**
1472 * Dump only the channel information as structured JSON for the stats collector.
1473 *
1474 * This is intentionally redundant with {#link dumpJson} because the old
1475 * scraper will expect this format.
1476 *
1477 * @param filter
1478 * @return
1479 */
1480 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1481 JSONArray channels = new JSONArray();
1482 Map<String, Integer> packageChannels = getPackageChannels();
1483 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1484 final String packageName = channelCount.getKey();
1485 if (filter == null || filter.matches(packageName)) {
1486 JSONObject channelCountJson = new JSONObject();
1487 try {
1488 channelCountJson.put("packageName", packageName);
1489 channelCountJson.put("channelCount", channelCount.getValue());
1490 } catch (JSONException e) {
1491 e.printStackTrace();
1492 }
1493 channels.put(channelCountJson);
1494 }
1495 }
1496 return channels;
1497 }
1498
1499 private Map<String, Integer> getPackageChannels() {
1500 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001501 synchronized (mPackagePreferences) {
1502 for (int i = 0; i < mPackagePreferences.size(); i++) {
1503 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001504 int channelCount = 0;
1505 for (int j = 0; j < r.channels.size(); j++) {
1506 if (!r.channels.valueAt(j).isDeleted()) {
1507 channelCount++;
1508 }
1509 }
1510 packageChannels.put(r.pkg, channelCount);
1511 }
1512 }
1513 return packageChannels;
1514 }
1515
Beverly0479cde22018-11-09 11:05:34 -05001516 /**
1517 * Called when user switches
1518 */
1519 public void onUserSwitched(int userId) {
1520 syncChannelsBypassingDnd(userId);
1521 }
1522
1523 /**
1524 * Called when user is unlocked
1525 */
1526 public void onUserUnlocked(int userId) {
1527 syncChannelsBypassingDnd(userId);
1528 }
1529
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001530 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001531 synchronized (mPackagePreferences) {
1532 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001533 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001534 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001535 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001536 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001537 }
1538 }
1539 }
1540 }
1541
1542 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001543 synchronized (mPackagePreferences) {
1544 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001545 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001546 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001547 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1548 if (PackagePreferences.channels.containsKey(
1549 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1550 PackagePreferences.channels.get(
1551 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1552 context.getResources().getString(
1553 R.string.default_notification_channel_label));
1554 }
1555 }
1556 }
1557 }
1558 }
1559
1560 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1561 int[] uidList) {
1562 if (pkgList == null || pkgList.length == 0) {
1563 return; // nothing to do
1564 }
1565 boolean updated = false;
1566 if (removingPackage) {
1567 // Remove notification settings for uninstalled package
1568 int size = Math.min(pkgList.length, uidList.length);
1569 for (int i = 0; i < size; i++) {
1570 final String pkg = pkgList[i];
1571 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001572 synchronized (mPackagePreferences) {
1573 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001574 }
1575 mRestoredWithoutUids.remove(pkg);
1576 updated = true;
1577 }
1578 } else {
1579 for (String pkg : pkgList) {
1580 // Package install
1581 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1582 if (r != null) {
1583 try {
1584 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1585 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001586 synchronized (mPackagePreferences) {
1587 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001588 }
1589 updated = true;
1590 } catch (PackageManager.NameNotFoundException e) {
1591 // noop
1592 }
1593 }
1594 // Package upgrade
1595 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001596 synchronized (mPackagePreferences) {
1597 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1598 mPm.getPackageUidAsUser(pkg, changeUserId));
1599 if (fullPackagePreferences != null) {
1600 createDefaultChannelIfNeeded(fullPackagePreferences);
1601 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1602 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001603 }
1604 } catch (PackageManager.NameNotFoundException e) {
1605 }
1606 }
1607 }
1608
1609 if (updated) {
1610 updateConfig();
1611 }
1612 }
1613
1614 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1615 return new LogMaker(
1616 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1617 .ACTION_NOTIFICATION_CHANNEL)
1618 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1619 .setPackageName(pkg)
1620 .addTaggedData(
1621 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1622 .FIELD_NOTIFICATION_CHANNEL_ID,
1623 channel.getId())
1624 .addTaggedData(
1625 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1626 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1627 channel.getImportance());
1628 }
1629
1630 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1631 return new LogMaker(
1632 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1633 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1634 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1635 .addTaggedData(
1636 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1637 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1638 groupId)
1639 .setPackageName(pkg);
1640 }
1641
1642
1643 public void updateBadgingEnabled() {
1644 if (mBadgingEnabled == null) {
1645 mBadgingEnabled = new SparseBooleanArray();
1646 }
1647 boolean changed = false;
1648 // update the cached values
1649 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1650 int userId = mBadgingEnabled.keyAt(index);
1651 final boolean oldValue = mBadgingEnabled.get(userId);
1652 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1653 Settings.Secure.NOTIFICATION_BADGING,
1654 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1655 mBadgingEnabled.put(userId, newValue);
1656 changed |= oldValue != newValue;
1657 }
1658 if (changed) {
1659 updateConfig();
1660 }
1661 }
1662
1663 public boolean badgingEnabled(UserHandle userHandle) {
1664 int userId = userHandle.getIdentifier();
1665 if (userId == UserHandle.USER_ALL) {
1666 return false;
1667 }
1668 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1669 mBadgingEnabled.put(userId,
1670 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1671 Settings.Secure.NOTIFICATION_BADGING,
1672 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1673 }
1674 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1675 }
1676
1677 private void updateConfig() {
1678 mRankingHandler.requestSort();
1679 }
1680
1681 private static String packagePreferencesKey(String pkg, int uid) {
1682 return pkg + "|" + uid;
1683 }
1684
1685 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001686 String pkg;
1687 int uid = UNKNOWN_UID;
1688 int importance = DEFAULT_IMPORTANCE;
1689 int priority = DEFAULT_PRIORITY;
1690 int visibility = DEFAULT_VISIBILITY;
1691 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001692 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001693 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001694 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1695 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001696
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001697 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001698 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1699 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001700
1701 public boolean isValidDelegate(String pkg, int uid) {
1702 return delegate != null && delegate.isAllowed(pkg, uid);
1703 }
1704 }
1705
1706 private static class Delegate {
1707 static final boolean DEFAULT_ENABLED = true;
1708 static final boolean DEFAULT_USER_ALLOWED = true;
1709 String mPkg;
1710 int mUid = UNKNOWN_UID;
1711 boolean mEnabled = DEFAULT_ENABLED;
1712 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1713
1714 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1715 mPkg = pkg;
1716 mUid = uid;
1717 mEnabled = enabled;
1718 mUserAllowed = userAllowed;
1719 }
1720
1721 public boolean isAllowed(String pkg, int uid) {
1722 if (pkg == null || uid == UNKNOWN_UID) {
1723 return false;
1724 }
1725 return pkg.equals(mPkg)
1726 && uid == mUid
1727 && (mUserAllowed && mEnabled);
1728 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001729 }
1730}