blob: 660309cd578d04224191c5ea73d312d242e6c93c [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;
Julia Reynolds4509ce72019-01-31 13:12:43 -0500130 private SparseBooleanArray mBubblesEnabled;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400131 private boolean mAreChannelsBypassingDnd;
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500132 private boolean mHideSilentStatusBarIcons;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400133
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400134 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
135 ZenModeHelper zenHelper) {
136 mContext = context;
137 mZenModeHelper = zenHelper;
138 mRankingHandler = rankingHandler;
139 mPm = pm;
140
141 updateBadgingEnabled();
Julia Reynolds4509ce72019-01-31 13:12:43 -0500142 updateBubblesEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500143 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400144 }
145
Annie Meng8b646fd2019-02-01 18:46:42 +0000146 public void readXml(XmlPullParser parser, boolean forRestore, int userId)
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400147 throws XmlPullParserException, IOException {
148 int type = parser.getEventType();
149 if (type != XmlPullParser.START_TAG) return;
150 String tag = parser.getName();
151 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400152 synchronized (mPackagePreferences) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500153 // Clobber groups and channels with the xml, but don't delete other data that wasn't
154 // present at the time of serialization.
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400155 mRestoredWithoutUids.clear();
156 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
157 tag = parser.getName();
158 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
159 return;
160 }
161 if (type == XmlPullParser.START_TAG) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500162 if (TAG_STATUS_ICONS.equals(tag)) {
Annie Meng8b646fd2019-02-01 18:46:42 +0000163 if (forRestore && userId != UserHandle.USER_SYSTEM) {
164 continue;
165 }
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500166 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
167 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
168 } else if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400169 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
170 String name = parser.getAttributeValue(null, ATT_NAME);
171 if (!TextUtils.isEmpty(name)) {
172 if (forRestore) {
173 try {
Annie Meng8b646fd2019-02-01 18:46:42 +0000174 uid = mPm.getPackageUidAsUser(name, userId);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400175 } catch (PackageManager.NameNotFoundException e) {
176 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400177 }
178 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400179
180 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
181 XmlUtils.readIntAttribute(
182 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
183 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
184 DEFAULT_PRIORITY),
185 XmlUtils.readIntAttribute(
186 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
187 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500188 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
189 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800190 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400191 r.importance = XmlUtils.readIntAttribute(
192 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
193 r.priority = XmlUtils.readIntAttribute(
194 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
195 r.visibility = XmlUtils.readIntAttribute(
196 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
197 r.showBadge = XmlUtils.readBooleanAttribute(
198 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
199 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
200 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
201
202 final int innerDepth = parser.getDepth();
203 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
204 && (type != XmlPullParser.END_TAG
205 || parser.getDepth() > innerDepth)) {
206 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
207 continue;
208 }
209
210 String tagName = parser.getName();
211 // Channel groups
212 if (TAG_GROUP.equals(tagName)) {
213 String id = parser.getAttributeValue(null, ATT_ID);
214 CharSequence groupName = parser.getAttributeValue(null,
215 ATT_NAME);
216 if (!TextUtils.isEmpty(id)) {
217 NotificationChannelGroup group
218 = new NotificationChannelGroup(id, groupName);
219 group.populateFromXml(parser);
220 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400221 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400222 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400223 // Channels
224 if (TAG_CHANNEL.equals(tagName)) {
225 String id = parser.getAttributeValue(null, ATT_ID);
226 String channelName = parser.getAttributeValue(null, ATT_NAME);
227 int channelImportance = XmlUtils.readIntAttribute(
228 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
229 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
230 NotificationChannel channel = new NotificationChannel(id,
231 channelName, channelImportance);
232 if (forRestore) {
233 channel.populateFromXmlForRestore(parser, mContext);
234 } else {
235 channel.populateFromXml(parser);
236 }
237 r.channels.put(id, channel);
238 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400239 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400240 // Delegate
241 if (TAG_DELEGATE.equals(tagName)) {
242 int delegateId =
243 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
244 String delegateName =
245 XmlUtils.readStringAttribute(parser, ATT_NAME);
246 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
247 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
248 boolean userAllowed = XmlUtils.readBooleanAttribute(
249 parser, ATT_USER_ALLOWED,
250 Delegate.DEFAULT_USER_ALLOWED);
251 Delegate d = null;
252 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
253 delegateName)) {
254 d = new Delegate(
255 delegateName, delegateId, delegateEnabled,
256 userAllowed);
257 }
258 r.delegate = d;
259 }
260
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400261 }
262
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400263 try {
264 deleteDefaultChannelIfNeeded(r);
265 } catch (PackageManager.NameNotFoundException e) {
266 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
267 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400268 }
269 }
270 }
271 }
272 }
273 throw new IllegalStateException("Failed to reach END_DOCUMENT");
274 }
275
276 private PackagePreferences getPackagePreferences(String pkg, int uid) {
277 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400278 synchronized (mPackagePreferences) {
279 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400280 }
281 }
282
283 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
284 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500285 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800286 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400287 }
288
289 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800290 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400291 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400292 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400293 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400294 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400295 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400296 if (r == null) {
297 r = new PackagePreferences();
298 r.pkg = pkg;
299 r.uid = uid;
300 r.importance = importance;
301 r.priority = priority;
302 r.visibility = visibility;
303 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800304 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400305
306 try {
307 createDefaultChannelIfNeeded(r);
308 } catch (PackageManager.NameNotFoundException e) {
309 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
310 }
311
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400312 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400313 mRestoredWithoutUids.put(pkg, r);
314 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400315 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400316 }
317 }
318 return r;
319 }
320 }
321
322 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
323 PackageManager.NameNotFoundException {
324 final int userId = UserHandle.getUserId(r.uid);
325 final ApplicationInfo applicationInfo =
326 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
327 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
328 // O apps should not have the default channel.
329 return false;
330 }
331
332 // Otherwise, this app should have the default channel.
333 return true;
334 }
335
336 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
337 PackageManager.NameNotFoundException {
338 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
339 // Not present
340 return;
341 }
342
343 if (shouldHaveDefaultChannel(r)) {
344 // Keep the default channel until upgraded.
345 return;
346 }
347
348 // Remove Default Channel.
349 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
350 }
351
352 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
353 PackageManager.NameNotFoundException {
354 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
355 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
356 com.android.internal.R.string.default_notification_channel_label));
357 return;
358 }
359
360 if (!shouldHaveDefaultChannel(r)) {
361 // Keep the default channel until upgraded.
362 return;
363 }
364
365 // Create Default Channel
366 NotificationChannel channel;
367 channel = new NotificationChannel(
368 NotificationChannel.DEFAULT_CHANNEL_ID,
369 mContext.getString(R.string.default_notification_channel_label),
370 r.importance);
371 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
372 channel.setLockscreenVisibility(r.visibility);
373 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
374 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
375 }
376 if (r.priority != DEFAULT_PRIORITY) {
377 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
378 }
379 if (r.visibility != DEFAULT_VISIBILITY) {
380 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
381 }
382 r.channels.put(channel.getId(), channel);
383 }
384
Annie Meng8b646fd2019-02-01 18:46:42 +0000385 public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400386 out.startTag(null, TAG_RANKING);
387 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
Annie Meng8b646fd2019-02-01 18:46:42 +0000388 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS
389 && (!forBackup || userId == UserHandle.USER_SYSTEM)) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500390 out.startTag(null, TAG_STATUS_ICONS);
391 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
392 out.endTag(null, TAG_STATUS_ICONS);
393 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400394
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400395 synchronized (mPackagePreferences) {
396 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400397 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400398 final PackagePreferences r = mPackagePreferences.valueAt(i);
Annie Meng8b646fd2019-02-01 18:46:42 +0000399 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400400 continue;
401 }
402 final boolean hasNonDefaultSettings =
403 r.importance != DEFAULT_IMPORTANCE
404 || r.priority != DEFAULT_PRIORITY
405 || r.visibility != DEFAULT_VISIBILITY
406 || r.showBadge != DEFAULT_SHOW_BADGE
407 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
408 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400409 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500410 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800411 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400412 if (hasNonDefaultSettings) {
413 out.startTag(null, TAG_PACKAGE);
414 out.attribute(null, ATT_NAME, r.pkg);
415 if (r.importance != DEFAULT_IMPORTANCE) {
416 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
417 }
418 if (r.priority != DEFAULT_PRIORITY) {
419 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
420 }
421 if (r.visibility != DEFAULT_VISIBILITY) {
422 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
423 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800424 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
425 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500426 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400427 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
428 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
429 Integer.toString(r.lockedAppFields));
430
431 if (!forBackup) {
432 out.attribute(null, ATT_UID, Integer.toString(r.uid));
433 }
434
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400435 if (r.delegate != null) {
436 out.startTag(null, TAG_DELEGATE);
437
438 out.attribute(null, ATT_NAME, r.delegate.mPkg);
439 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
440 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
441 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
442 }
443 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
444 out.attribute(null, ATT_USER_ALLOWED,
445 Boolean.toString(r.delegate.mUserAllowed));
446 }
447 out.endTag(null, TAG_DELEGATE);
448 }
449
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400450 for (NotificationChannelGroup group : r.groups.values()) {
451 group.writeXml(out);
452 }
453
454 for (NotificationChannel channel : r.channels.values()) {
455 if (forBackup) {
456 if (!channel.isDeleted()) {
457 channel.writeXmlForBackup(out, mContext);
458 }
459 } else {
460 channel.writeXml(out);
461 }
462 }
463
464 out.endTag(null, TAG_PACKAGE);
465 }
466 }
467 }
468 out.endTag(null, TAG_RANKING);
469 }
470
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800471 /**
472 * Sets whether bubbles are allowed.
473 *
474 * @param pkg the package to allow or not allow bubbles for.
475 * @param uid the uid to allow or not allow bubbles for.
476 * @param allowed whether bubbles are allowed.
477 */
478 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500479 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800480 p.allowBubble = allowed;
481 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500482 }
483
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800484 /**
485 * Whether bubbles are allowed.
486 *
487 * @param pkg the package to check if bubbles are allowed for
488 * @param uid the uid to check if bubbles are allowed for.
489 * @return whether bubbles are allowed.
490 */
Julia Reynolds4509ce72019-01-31 13:12:43 -0500491 @Override
Mady Mellor9db685a2019-01-23 13:23:37 -0800492 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800493 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500494 }
495
496 public int getAppLockedFields(String pkg, int uid) {
497 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
498 }
499
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400500 /**
501 * Gets importance.
502 */
503 @Override
504 public int getImportance(String packageName, int uid) {
505 return getOrCreatePackagePreferences(packageName, uid).importance;
506 }
507
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400508 /**
509 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
510 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
511 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
512 */
513 public boolean getIsAppImportanceLocked(String packageName, int uid) {
514 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
515 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
516 }
517
518 @Override
519 public boolean canShowBadge(String packageName, int uid) {
520 return getOrCreatePackagePreferences(packageName, uid).showBadge;
521 }
522
523 @Override
524 public void setShowBadge(String packageName, int uid, boolean showBadge) {
525 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
526 updateConfig();
527 }
528
529 @Override
530 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
531 if (groupId == null) {
532 return false;
533 }
534 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
535 NotificationChannelGroup group = r.groups.get(groupId);
536 if (group == null) {
537 return false;
538 }
539 return group.isBlocked();
540 }
541
542 int getPackagePriority(String pkg, int uid) {
543 return getOrCreatePackagePreferences(pkg, uid).priority;
544 }
545
546 int getPackageVisibility(String pkg, int uid) {
547 return getOrCreatePackagePreferences(pkg, uid).visibility;
548 }
549
550 @Override
551 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
552 boolean fromTargetApp) {
553 Preconditions.checkNotNull(pkg);
554 Preconditions.checkNotNull(group);
555 Preconditions.checkNotNull(group.getId());
556 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
557 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
558 if (r == null) {
559 throw new IllegalArgumentException("Invalid package");
560 }
561 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
562 if (!group.equals(oldGroup)) {
563 // will log for new entries as well as name/description changes
564 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
565 }
566 if (oldGroup != null) {
567 group.setChannels(oldGroup.getChannels());
568
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400569 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400570 if (fromTargetApp) {
571 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400572 group.unlockFields(group.getUserLockedFields());
573 group.lockFields(oldGroup.getUserLockedFields());
574 } else {
575 // but the system can
576 if (group.isBlocked() != oldGroup.isBlocked()) {
577 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500578 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400579 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400580 }
581 }
582 r.groups.put(group.getId(), group);
583 }
584
585 @Override
586 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
587 boolean fromTargetApp, boolean hasDndAccess) {
588 Preconditions.checkNotNull(pkg);
589 Preconditions.checkNotNull(channel);
590 Preconditions.checkNotNull(channel.getId());
591 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
592 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
593 if (r == null) {
594 throw new IllegalArgumentException("Invalid package");
595 }
596 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
597 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
598 }
599 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
600 throw new IllegalArgumentException("Reserved id");
601 }
602 NotificationChannel existing = r.channels.get(channel.getId());
603 // Keep most of the existing settings
604 if (existing != null && fromTargetApp) {
605 if (existing.isDeleted()) {
606 existing.setDeleted(false);
607
608 // log a resurrected channel as if it's new again
609 MetricsLogger.action(getChannelLog(channel, pkg).setType(
610 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
611 }
612
613 existing.setName(channel.getName().toString());
614 existing.setDescription(channel.getDescription());
615 existing.setBlockableSystem(channel.isBlockableSystem());
616 if (existing.getGroup() == null) {
617 existing.setGroup(channel.getGroup());
618 }
619
620 // Apps are allowed to downgrade channel importance if the user has not changed any
621 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500622 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400623 if (existing.getUserLockedFields() == 0 &&
624 channel.getImportance() < existing.getImportance()) {
625 existing.setImportance(channel.getImportance());
626 }
627
628 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
629 // fields on the channel yet
630 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
631 boolean bypassDnd = channel.canBypassDnd();
632 existing.setBypassDnd(bypassDnd);
633
Beverly0479cde22018-11-09 11:05:34 -0500634 if (bypassDnd != mAreChannelsBypassingDnd
635 || previousExistingImportance != existing.getImportance()) {
636 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400637 }
638 }
639
640 updateConfig();
641 return;
642 }
643 if (channel.getImportance() < IMPORTANCE_NONE
644 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
645 throw new IllegalArgumentException("Invalid importance level");
646 }
647
648 // Reset fields that apps aren't allowed to set.
649 if (fromTargetApp && !hasDndAccess) {
650 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
651 }
652 if (fromTargetApp) {
653 channel.setLockscreenVisibility(r.visibility);
654 }
655 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500656 channel.setImportanceLockedByOEM(r.oemLockedImportance);
657 if (!channel.isImportanceLockedByOEM()) {
658 if (r.futureOemLockedChannels.remove(channel.getId())) {
659 channel.setImportanceLockedByOEM(true);
660 }
661 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400662 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
663 channel.setLockscreenVisibility(
664 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
665 }
666 if (!r.showBadge) {
667 channel.setShowBadge(false);
668 }
669
670 r.channels.put(channel.getId(), channel);
671 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500672 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400673 }
674 MetricsLogger.action(getChannelLog(channel, pkg).setType(
675 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
676 }
677
678 void clearLockedFields(NotificationChannel channel) {
679 channel.unlockFields(channel.getUserLockedFields());
680 }
681
682 @Override
683 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
684 boolean fromUser) {
685 Preconditions.checkNotNull(updatedChannel);
686 Preconditions.checkNotNull(updatedChannel.getId());
687 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
688 if (r == null) {
689 throw new IllegalArgumentException("Invalid package");
690 }
691 NotificationChannel channel = r.channels.get(updatedChannel.getId());
692 if (channel == null || channel.isDeleted()) {
693 throw new IllegalArgumentException("Channel does not exist");
694 }
695 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
696 updatedChannel.setLockscreenVisibility(
697 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
698 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400699 if (fromUser) {
700 updatedChannel.lockFields(channel.getUserLockedFields());
701 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400702 } else {
703 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400704 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500705 // no importance updates are allowed if OEM blocked it
706 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
707 if (updatedChannel.isImportanceLockedByOEM()) {
708 updatedChannel.setImportance(channel.getImportance());
709 }
710
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400711 r.channels.put(updatedChannel.getId(), updatedChannel);
712
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400713 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400714 // copy settings to app level so they are inherited by new channels
715 // when the app migrates
716 r.importance = updatedChannel.getImportance();
717 r.priority = updatedChannel.canBypassDnd()
718 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
719 r.visibility = updatedChannel.getLockscreenVisibility();
720 r.showBadge = updatedChannel.canShowBadge();
721 }
722
723 if (!channel.equals(updatedChannel)) {
724 // only log if there are real changes
Jan Althaus52ebc152019-02-21 16:24:19 +0100725 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
726 .setSubtype(fromUser ? 1 : 0));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400727 }
728
Beverly0479cde22018-11-09 11:05:34 -0500729 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
730 || channel.getImportance() != updatedChannel.getImportance()) {
731 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400732 }
733 updateConfig();
734 }
735
736 @Override
737 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
738 boolean includeDeleted) {
739 Preconditions.checkNotNull(pkg);
740 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
741 if (r == null) {
742 return null;
743 }
744 if (channelId == null) {
745 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
746 }
747 final NotificationChannel nc = r.channels.get(channelId);
748 if (nc != null && (includeDeleted || !nc.isDeleted())) {
749 return nc;
750 }
751 return null;
752 }
753
754 @Override
755 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
756 PackagePreferences r = getPackagePreferences(pkg, uid);
757 if (r == null) {
758 return;
759 }
760 NotificationChannel channel = r.channels.get(channelId);
761 if (channel != null) {
762 channel.setDeleted(true);
763 LogMaker lm = getChannelLog(channel, pkg);
764 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
765 MetricsLogger.action(lm);
766
767 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500768 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400769 }
770 }
771 }
772
773 @Override
774 @VisibleForTesting
775 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
776 Preconditions.checkNotNull(pkg);
777 Preconditions.checkNotNull(channelId);
778 PackagePreferences r = getPackagePreferences(pkg, uid);
779 if (r == null) {
780 return;
781 }
782 r.channels.remove(channelId);
783 }
784
785 @Override
786 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
787 Preconditions.checkNotNull(pkg);
788 PackagePreferences r = getPackagePreferences(pkg, uid);
789 if (r == null) {
790 return;
791 }
792 int N = r.channels.size() - 1;
793 for (int i = N; i >= 0; i--) {
794 String key = r.channels.keyAt(i);
795 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
796 r.channels.remove(key);
797 }
798 }
799 }
800
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500801 public boolean shouldHideSilentStatusIcons() {
802 return mHideSilentStatusBarIcons;
803 }
804
805 public void setHideSilentStatusIcons(boolean hide) {
806 mHideSilentStatusBarIcons = hide;
807 }
808
Julia Reynolds413ba842019-01-11 10:38:08 -0500809 public void lockChannelsForOEM(String[] appOrChannelList) {
810 if (appOrChannelList == null) {
811 return;
812 }
813 for (String appOrChannel : appOrChannelList) {
814 if (!TextUtils.isEmpty(appOrChannel)) {
815 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
816 if (appSplit != null && appSplit.length > 0) {
817 String appName = appSplit[0];
818 String channelId = appSplit.length == 2 ? appSplit[1] : null;
819
820 synchronized (mPackagePreferences) {
821 for (PackagePreferences r : mPackagePreferences.values()) {
822 if (r.pkg.equals(appName)) {
823 if (channelId == null) {
824 // lock all channels for the app
825 r.oemLockedImportance = true;
826 for (NotificationChannel channel : r.channels.values()) {
827 channel.setImportanceLockedByOEM(true);
828 }
829 } else {
830 NotificationChannel channel = r.channels.get(channelId);
831 if (channel != null) {
832 channel.setImportanceLockedByOEM(true);
833 } else {
834 // if this channel shows up in the future, make sure it'll
835 // be locked immediately
836 r.futureOemLockedChannels.add(channelId);
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844 }
845 }
846
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400847 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
848 int uid, String groupId, boolean includeDeleted) {
849 Preconditions.checkNotNull(pkg);
850 PackagePreferences r = getPackagePreferences(pkg, uid);
851 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
852 return null;
853 }
854 NotificationChannelGroup group = r.groups.get(groupId).clone();
855 group.setChannels(new ArrayList<>());
856 int N = r.channels.size();
857 for (int i = 0; i < N; i++) {
858 final NotificationChannel nc = r.channels.valueAt(i);
859 if (includeDeleted || !nc.isDeleted()) {
860 if (groupId.equals(nc.getGroup())) {
861 group.addChannel(nc);
862 }
863 }
864 }
865 return group;
866 }
867
868 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
869 int uid) {
870 Preconditions.checkNotNull(pkg);
871 PackagePreferences r = getPackagePreferences(pkg, uid);
872 if (r == null) {
873 return null;
874 }
875 return r.groups.get(groupId);
876 }
877
878 @Override
879 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400880 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400881 Preconditions.checkNotNull(pkg);
882 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
883 PackagePreferences r = getPackagePreferences(pkg, uid);
884 if (r == null) {
885 return ParceledListSlice.emptyList();
886 }
887 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
888 int N = r.channels.size();
889 for (int i = 0; i < N; i++) {
890 final NotificationChannel nc = r.channels.valueAt(i);
891 if (includeDeleted || !nc.isDeleted()) {
892 if (nc.getGroup() != null) {
893 if (r.groups.get(nc.getGroup()) != null) {
894 NotificationChannelGroup ncg = groups.get(nc.getGroup());
895 if (ncg == null) {
896 ncg = r.groups.get(nc.getGroup()).clone();
897 ncg.setChannels(new ArrayList<>());
898 groups.put(nc.getGroup(), ncg);
899
900 }
901 ncg.addChannel(nc);
902 }
903 } else {
904 nonGrouped.addChannel(nc);
905 }
906 }
907 }
908 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
909 groups.put(null, nonGrouped);
910 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400911 if (includeEmpty) {
912 for (NotificationChannelGroup group : r.groups.values()) {
913 if (!groups.containsKey(group.getId())) {
914 groups.put(group.getId(), group);
915 }
916 }
917 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400918 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
919 }
920
921 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
922 String groupId) {
923 List<NotificationChannel> deletedChannels = new ArrayList<>();
924 PackagePreferences r = getPackagePreferences(pkg, uid);
925 if (r == null || TextUtils.isEmpty(groupId)) {
926 return deletedChannels;
927 }
928
929 r.groups.remove(groupId);
930
931 int N = r.channels.size();
932 for (int i = 0; i < N; i++) {
933 final NotificationChannel nc = r.channels.valueAt(i);
934 if (groupId.equals(nc.getGroup())) {
935 nc.setDeleted(true);
936 deletedChannels.add(nc);
937 }
938 }
939 return deletedChannels;
940 }
941
942 @Override
943 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
944 int uid) {
945 PackagePreferences r = getPackagePreferences(pkg, uid);
946 if (r == null) {
947 return new ArrayList<>();
948 }
949 return r.groups.values();
950 }
951
952 @Override
953 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
954 boolean includeDeleted) {
955 Preconditions.checkNotNull(pkg);
956 List<NotificationChannel> channels = new ArrayList<>();
957 PackagePreferences r = getPackagePreferences(pkg, uid);
958 if (r == null) {
959 return ParceledListSlice.emptyList();
960 }
961 int N = r.channels.size();
962 for (int i = 0; i < N; i++) {
963 final NotificationChannel nc = r.channels.valueAt(i);
964 if (includeDeleted || !nc.isDeleted()) {
965 channels.add(nc);
966 }
967 }
968 return new ParceledListSlice<>(channels);
969 }
970
971 /**
Beverly0479cde22018-11-09 11:05:34 -0500972 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
973 */
974 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
975 int userId) {
976 List<NotificationChannel> channels = new ArrayList<>();
977 synchronized (mPackagePreferences) {
978 final PackagePreferences r = mPackagePreferences.get(
979 packagePreferencesKey(pkg, userId));
980 // notifications from this package aren't blocked
981 if (r != null && r.importance != IMPORTANCE_NONE) {
982 for (NotificationChannel channel : r.channels.values()) {
983 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
984 channels.add(channel);
985 }
986 }
987 }
988 }
989 return new ParceledListSlice<>(channels);
990 }
991
992 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400993 * True for pre-O apps that only have the default channel, or pre O apps that have no
994 * channels yet. This method will create the default channel for pre-O apps that don't have it.
995 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
996 * upgrades.
997 */
998 public boolean onlyHasDefaultChannel(String pkg, int uid) {
999 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
1000 if (r.channels.size() == 1
1001 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1002 return true;
1003 }
1004 return false;
1005 }
1006
1007 public int getDeletedChannelCount(String pkg, int uid) {
1008 Preconditions.checkNotNull(pkg);
1009 int deletedCount = 0;
1010 PackagePreferences r = getPackagePreferences(pkg, uid);
1011 if (r == null) {
1012 return deletedCount;
1013 }
1014 int N = r.channels.size();
1015 for (int i = 0; i < N; i++) {
1016 final NotificationChannel nc = r.channels.valueAt(i);
1017 if (nc.isDeleted()) {
1018 deletedCount++;
1019 }
1020 }
1021 return deletedCount;
1022 }
1023
1024 public int getBlockedChannelCount(String pkg, int uid) {
1025 Preconditions.checkNotNull(pkg);
1026 int blockedCount = 0;
1027 PackagePreferences r = getPackagePreferences(pkg, uid);
1028 if (r == null) {
1029 return blockedCount;
1030 }
1031 int N = r.channels.size();
1032 for (int i = 0; i < N; i++) {
1033 final NotificationChannel nc = r.channels.valueAt(i);
1034 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1035 blockedCount++;
1036 }
1037 }
1038 return blockedCount;
1039 }
1040
1041 public int getBlockedAppCount(int userId) {
1042 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001043 synchronized (mPackagePreferences) {
1044 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001045 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001046 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001047 if (userId == UserHandle.getUserId(r.uid)
1048 && r.importance == IMPORTANCE_NONE) {
1049 count++;
1050 }
1051 }
1052 }
1053 return count;
1054 }
1055
Beverly0479cde22018-11-09 11:05:34 -05001056 /**
1057 * Returns the number of apps that have at least one notification channel that can bypass DND
1058 * for given particular user
1059 */
1060 public int getAppsBypassingDndCount(int userId) {
1061 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001062 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001063 final int numPackagePreferences = mPackagePreferences.size();
1064 for (int i = 0; i < numPackagePreferences; i++) {
1065 final PackagePreferences r = mPackagePreferences.valueAt(i);
1066 // Package isn't associated with this userId or notifications from this package are
1067 // blocked
1068 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1069 continue;
1070 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001071
Beverly0479cde22018-11-09 11:05:34 -05001072 for (NotificationChannel channel : r.channels.values()) {
1073 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1074 count++;
1075 break;
1076 }
1077 }
1078 }
1079 }
1080 return count;
1081 }
1082
1083 /**
1084 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1085 * updating
1086 * @param userId
1087 */
1088 private void syncChannelsBypassingDnd(int userId) {
1089 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1090 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1091 updateChannelsBypassingDnd(userId);
1092 }
1093
1094 /**
1095 * Updates the user's NotificationPolicy based on whether the given userId
1096 * has channels bypassing DND
1097 * @param userId
1098 */
1099 private void updateChannelsBypassingDnd(int userId) {
1100 synchronized (mPackagePreferences) {
1101 final int numPackagePreferences = mPackagePreferences.size();
1102 for (int i = 0; i < numPackagePreferences; i++) {
1103 final PackagePreferences r = mPackagePreferences.valueAt(i);
1104 // Package isn't associated with this userId or notifications from this package are
1105 // blocked
1106 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1107 continue;
1108 }
1109
1110 for (NotificationChannel channel : r.channels.values()) {
1111 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001112 if (!mAreChannelsBypassingDnd) {
1113 mAreChannelsBypassingDnd = true;
1114 updateZenPolicy(true);
1115 }
1116 return;
1117 }
1118 }
1119 }
1120 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001121 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1122 if (mAreChannelsBypassingDnd) {
1123 mAreChannelsBypassingDnd = false;
1124 updateZenPolicy(false);
1125 }
1126 }
1127
Beverly0479cde22018-11-09 11:05:34 -05001128 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1129 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001130 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1131 return false;
Beverly0479cde22018-11-09 11:05:34 -05001132 }
1133
1134 // Channel is deleted or is blocked
1135 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1136 return false;
1137 }
1138
1139 return true;
1140 }
1141
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001142 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1143 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1144 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1145 policy.priorityCategories, policy.priorityCallSenders,
1146 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1147 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1148 : 0)));
1149 }
1150
1151 public boolean areChannelsBypassingDnd() {
1152 return mAreChannelsBypassingDnd;
1153 }
1154
1155 /**
1156 * Sets importance.
1157 */
1158 @Override
1159 public void setImportance(String pkgName, int uid, int importance) {
1160 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1161 updateConfig();
1162 }
1163
1164 public void setEnabled(String packageName, int uid, boolean enabled) {
1165 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1166 if (wasEnabled == enabled) {
1167 return;
1168 }
1169 setImportance(packageName, uid,
1170 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1171 }
1172
1173 /**
1174 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1175 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1176 * considered for sentiment adjustments (and thus never show a blocking helper).
1177 */
1178 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001179 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1180 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001181 return;
1182 }
1183
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001184 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001185 updateConfig();
1186 }
1187
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001188 /**
1189 * Returns the delegate for a given package, if it's allowed by the package and the user.
1190 */
1191 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1192 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1193
1194 if (prefs == null || prefs.delegate == null) {
1195 return null;
1196 }
1197 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1198 return null;
1199 }
1200 return prefs.delegate.mPkg;
1201 }
1202
1203 /**
1204 * Used by an app to delegate notification posting privileges to another apps.
1205 */
1206 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1207 String delegatePkg, int delegateUid) {
1208 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1209
1210 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1211 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1212 prefs.delegate = delegate;
1213 updateConfig();
1214 }
1215
1216 /**
1217 * Used by an app to turn off its notification delegate.
1218 */
1219 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1220 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1221 if (prefs != null && prefs.delegate != null) {
1222 prefs.delegate.mEnabled = false;
1223 updateConfig();
1224 }
1225 }
1226
1227 /**
1228 * Toggles whether an app can have a notification delegate on behalf of a user.
1229 */
1230 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1231 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1232 if (prefs != null && prefs.delegate != null) {
1233 prefs.delegate.mUserAllowed = userAllowed;
1234 updateConfig();
1235 }
1236 }
1237
1238 /**
1239 * Returns whether the given app is allowed on post notifications on behalf of the other given
1240 * app.
1241 */
1242 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1243 String potentialDelegatePkg, int potentialDelegateUid) {
1244 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1245
1246 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1247 }
1248
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001249 @VisibleForTesting
1250 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1251 if (original.canBypassDnd() != update.canBypassDnd()) {
1252 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1253 }
1254 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1255 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1256 }
1257 if (original.getImportance() != update.getImportance()) {
1258 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1259 }
1260 if (original.shouldShowLights() != update.shouldShowLights()
1261 || original.getLightColor() != update.getLightColor()) {
1262 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1263 }
1264 if (!Objects.equals(original.getSound(), update.getSound())) {
1265 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1266 }
1267 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1268 || original.shouldVibrate() != update.shouldVibrate()) {
1269 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1270 }
1271 if (original.canShowBadge() != update.canShowBadge()) {
1272 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1273 }
Julia Reynolds4509ce72019-01-31 13:12:43 -05001274 if (original.canBubble() != update.canBubble()) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001275 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001276 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001277 }
1278
1279 public void dump(PrintWriter pw, String prefix,
1280 @NonNull NotificationManagerService.DumpFilter filter) {
1281 pw.print(prefix);
1282 pw.println("per-package config:");
1283
1284 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001285 synchronized (mPackagePreferences) {
1286 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001287 }
1288 pw.println("Restored without uid:");
1289 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1290 }
1291
1292 public void dump(ProtoOutputStream proto,
1293 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001294 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001295 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001296 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001297 }
1298 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1299 mRestoredWithoutUids);
1300 }
1301
1302 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1303 @NonNull NotificationManagerService.DumpFilter filter,
1304 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1305 final int N = PackagePreferencess.size();
1306 for (int i = 0; i < N; i++) {
1307 final PackagePreferences r = PackagePreferencess.valueAt(i);
1308 if (filter.matches(r.pkg)) {
1309 pw.print(prefix);
1310 pw.print(" AppSettings: ");
1311 pw.print(r.pkg);
1312 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001313 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001314 pw.print(')');
1315 if (r.importance != DEFAULT_IMPORTANCE) {
1316 pw.print(" importance=");
1317 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1318 }
1319 if (r.priority != DEFAULT_PRIORITY) {
1320 pw.print(" priority=");
1321 pw.print(Notification.priorityToString(r.priority));
1322 }
1323 if (r.visibility != DEFAULT_VISIBILITY) {
1324 pw.print(" visibility=");
1325 pw.print(Notification.visibilityToString(r.visibility));
1326 }
1327 pw.print(" showBadge=");
1328 pw.print(Boolean.toString(r.showBadge));
1329 pw.println();
1330 for (NotificationChannel channel : r.channels.values()) {
1331 pw.print(prefix);
1332 channel.dump(pw, " ", filter.redact);
1333 }
1334 for (NotificationChannelGroup group : r.groups.values()) {
1335 pw.print(prefix);
1336 pw.print(" ");
1337 pw.print(" ");
1338 pw.println(group);
1339 }
1340 }
1341 }
1342 }
1343
1344 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1345 @NonNull NotificationManagerService.DumpFilter filter,
1346 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1347 final int N = PackagePreferencess.size();
1348 long fToken;
1349 for (int i = 0; i < N; i++) {
1350 final PackagePreferences r = PackagePreferencess.valueAt(i);
1351 if (filter.matches(r.pkg)) {
1352 fToken = proto.start(fieldId);
1353
1354 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1355 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1356 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1357 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1358 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1359 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1360
1361 for (NotificationChannel channel : r.channels.values()) {
1362 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1363 }
1364 for (NotificationChannelGroup group : r.groups.values()) {
1365 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1366 }
1367
1368 proto.end(fToken);
1369 }
1370 }
1371 }
1372
1373 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1374 JSONObject ranking = new JSONObject();
1375 JSONArray PackagePreferencess = new JSONArray();
1376 try {
1377 ranking.put("noUid", mRestoredWithoutUids.size());
1378 } catch (JSONException e) {
1379 // pass
1380 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001381 synchronized (mPackagePreferences) {
1382 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001383 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001384 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001385 if (filter == null || filter.matches(r.pkg)) {
1386 JSONObject PackagePreferences = new JSONObject();
1387 try {
1388 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1389 PackagePreferences.put("packageName", r.pkg);
1390 if (r.importance != DEFAULT_IMPORTANCE) {
1391 PackagePreferences.put("importance",
1392 NotificationListenerService.Ranking.importanceToString(
1393 r.importance));
1394 }
1395 if (r.priority != DEFAULT_PRIORITY) {
1396 PackagePreferences.put("priority",
1397 Notification.priorityToString(r.priority));
1398 }
1399 if (r.visibility != DEFAULT_VISIBILITY) {
1400 PackagePreferences.put("visibility",
1401 Notification.visibilityToString(r.visibility));
1402 }
1403 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1404 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1405 }
1406 JSONArray channels = new JSONArray();
1407 for (NotificationChannel channel : r.channels.values()) {
1408 channels.put(channel.toJson());
1409 }
1410 PackagePreferences.put("channels", channels);
1411 JSONArray groups = new JSONArray();
1412 for (NotificationChannelGroup group : r.groups.values()) {
1413 groups.put(group.toJson());
1414 }
1415 PackagePreferences.put("groups", groups);
1416 } catch (JSONException e) {
1417 // pass
1418 }
1419 PackagePreferencess.put(PackagePreferences);
1420 }
1421 }
1422 }
1423 try {
1424 ranking.put("PackagePreferencess", PackagePreferencess);
1425 } catch (JSONException e) {
1426 // pass
1427 }
1428 return ranking;
1429 }
1430
1431 /**
1432 * Dump only the ban information as structured JSON for the stats collector.
1433 *
1434 * This is intentionally redundant with {#link dumpJson} because the old
1435 * scraper will expect this format.
1436 *
1437 * @param filter
1438 * @return
1439 */
1440 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1441 JSONArray bans = new JSONArray();
1442 Map<Integer, String> packageBans = getPackageBans();
1443 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1444 final int userId = UserHandle.getUserId(ban.getKey());
1445 final String packageName = ban.getValue();
1446 if (filter == null || filter.matches(packageName)) {
1447 JSONObject banJson = new JSONObject();
1448 try {
1449 banJson.put("userId", userId);
1450 banJson.put("packageName", packageName);
1451 } catch (JSONException e) {
1452 e.printStackTrace();
1453 }
1454 bans.put(banJson);
1455 }
1456 }
1457 return bans;
1458 }
1459
1460 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001461 synchronized (mPackagePreferences) {
1462 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001463 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1464 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001465 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001466 if (r.importance == IMPORTANCE_NONE) {
1467 packageBans.put(r.uid, r.pkg);
1468 }
1469 }
1470
1471 return packageBans;
1472 }
1473 }
1474
1475 /**
1476 * Dump only the channel information as structured JSON for the stats collector.
1477 *
1478 * This is intentionally redundant with {#link dumpJson} because the old
1479 * scraper will expect this format.
1480 *
1481 * @param filter
1482 * @return
1483 */
1484 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1485 JSONArray channels = new JSONArray();
1486 Map<String, Integer> packageChannels = getPackageChannels();
1487 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1488 final String packageName = channelCount.getKey();
1489 if (filter == null || filter.matches(packageName)) {
1490 JSONObject channelCountJson = new JSONObject();
1491 try {
1492 channelCountJson.put("packageName", packageName);
1493 channelCountJson.put("channelCount", channelCount.getValue());
1494 } catch (JSONException e) {
1495 e.printStackTrace();
1496 }
1497 channels.put(channelCountJson);
1498 }
1499 }
1500 return channels;
1501 }
1502
1503 private Map<String, Integer> getPackageChannels() {
1504 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001505 synchronized (mPackagePreferences) {
1506 for (int i = 0; i < mPackagePreferences.size(); i++) {
1507 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001508 int channelCount = 0;
1509 for (int j = 0; j < r.channels.size(); j++) {
1510 if (!r.channels.valueAt(j).isDeleted()) {
1511 channelCount++;
1512 }
1513 }
1514 packageChannels.put(r.pkg, channelCount);
1515 }
1516 }
1517 return packageChannels;
1518 }
1519
Beverly0479cde22018-11-09 11:05:34 -05001520 /**
1521 * Called when user switches
1522 */
1523 public void onUserSwitched(int userId) {
1524 syncChannelsBypassingDnd(userId);
1525 }
1526
1527 /**
1528 * Called when user is unlocked
1529 */
1530 public void onUserUnlocked(int userId) {
1531 syncChannelsBypassingDnd(userId);
1532 }
1533
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001534 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001535 synchronized (mPackagePreferences) {
1536 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001537 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001538 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001539 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001540 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001541 }
1542 }
1543 }
1544 }
1545
1546 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001547 synchronized (mPackagePreferences) {
1548 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001549 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001550 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001551 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1552 if (PackagePreferences.channels.containsKey(
1553 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1554 PackagePreferences.channels.get(
1555 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1556 context.getResources().getString(
1557 R.string.default_notification_channel_label));
1558 }
1559 }
1560 }
1561 }
1562 }
1563
1564 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1565 int[] uidList) {
1566 if (pkgList == null || pkgList.length == 0) {
1567 return; // nothing to do
1568 }
1569 boolean updated = false;
1570 if (removingPackage) {
1571 // Remove notification settings for uninstalled package
1572 int size = Math.min(pkgList.length, uidList.length);
1573 for (int i = 0; i < size; i++) {
1574 final String pkg = pkgList[i];
1575 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001576 synchronized (mPackagePreferences) {
1577 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001578 }
1579 mRestoredWithoutUids.remove(pkg);
1580 updated = true;
1581 }
1582 } else {
1583 for (String pkg : pkgList) {
1584 // Package install
1585 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1586 if (r != null) {
1587 try {
1588 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1589 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001590 synchronized (mPackagePreferences) {
1591 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001592 }
1593 updated = true;
1594 } catch (PackageManager.NameNotFoundException e) {
1595 // noop
1596 }
1597 }
1598 // Package upgrade
1599 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001600 synchronized (mPackagePreferences) {
1601 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1602 mPm.getPackageUidAsUser(pkg, changeUserId));
1603 if (fullPackagePreferences != null) {
1604 createDefaultChannelIfNeeded(fullPackagePreferences);
1605 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1606 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001607 }
1608 } catch (PackageManager.NameNotFoundException e) {
1609 }
1610 }
1611 }
1612
1613 if (updated) {
1614 updateConfig();
1615 }
1616 }
1617
1618 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1619 return new LogMaker(
1620 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1621 .ACTION_NOTIFICATION_CHANNEL)
1622 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1623 .setPackageName(pkg)
1624 .addTaggedData(
1625 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1626 .FIELD_NOTIFICATION_CHANNEL_ID,
1627 channel.getId())
1628 .addTaggedData(
1629 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1630 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1631 channel.getImportance());
1632 }
1633
1634 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1635 return new LogMaker(
1636 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1637 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1638 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1639 .addTaggedData(
1640 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1641 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1642 groupId)
1643 .setPackageName(pkg);
1644 }
1645
Julia Reynolds4509ce72019-01-31 13:12:43 -05001646 public void updateBubblesEnabled() {
1647 if (mBubblesEnabled == null) {
1648 mBubblesEnabled = new SparseBooleanArray();
1649 }
1650 boolean changed = false;
1651 // update the cached values
1652 for (int index = 0; index < mBubblesEnabled.size(); index++) {
1653 int userId = mBubblesEnabled.keyAt(index);
1654 final boolean oldValue = mBubblesEnabled.get(userId);
1655 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1656 Settings.Secure.NOTIFICATION_BUBBLES,
1657 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
1658 mBubblesEnabled.put(userId, newValue);
1659 changed |= oldValue != newValue;
1660 }
1661 if (changed) {
1662 updateConfig();
1663 }
1664 }
1665
1666 public boolean bubblesEnabled(UserHandle userHandle) {
1667 int userId = userHandle.getIdentifier();
1668 if (userId == UserHandle.USER_ALL) {
1669 return false;
1670 }
1671 if (mBubblesEnabled.indexOfKey(userId) < 0) {
1672 mBubblesEnabled.put(userId,
1673 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1674 Settings.Secure.NOTIFICATION_BUBBLES,
1675 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
1676 }
1677 return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
1678 }
1679
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001680
1681 public void updateBadgingEnabled() {
1682 if (mBadgingEnabled == null) {
1683 mBadgingEnabled = new SparseBooleanArray();
1684 }
1685 boolean changed = false;
1686 // update the cached values
1687 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1688 int userId = mBadgingEnabled.keyAt(index);
1689 final boolean oldValue = mBadgingEnabled.get(userId);
1690 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1691 Settings.Secure.NOTIFICATION_BADGING,
1692 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1693 mBadgingEnabled.put(userId, newValue);
1694 changed |= oldValue != newValue;
1695 }
1696 if (changed) {
1697 updateConfig();
1698 }
1699 }
1700
1701 public boolean badgingEnabled(UserHandle userHandle) {
1702 int userId = userHandle.getIdentifier();
1703 if (userId == UserHandle.USER_ALL) {
1704 return false;
1705 }
1706 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1707 mBadgingEnabled.put(userId,
1708 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1709 Settings.Secure.NOTIFICATION_BADGING,
1710 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1711 }
1712 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1713 }
1714
1715 private void updateConfig() {
1716 mRankingHandler.requestSort();
1717 }
1718
1719 private static String packagePreferencesKey(String pkg, int uid) {
1720 return pkg + "|" + uid;
1721 }
1722
1723 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001724 String pkg;
1725 int uid = UNKNOWN_UID;
1726 int importance = DEFAULT_IMPORTANCE;
1727 int priority = DEFAULT_PRIORITY;
1728 int visibility = DEFAULT_VISIBILITY;
1729 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001730 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001731 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001732 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1733 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001734
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001735 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001736 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1737 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001738
1739 public boolean isValidDelegate(String pkg, int uid) {
1740 return delegate != null && delegate.isAllowed(pkg, uid);
1741 }
1742 }
1743
1744 private static class Delegate {
1745 static final boolean DEFAULT_ENABLED = true;
1746 static final boolean DEFAULT_USER_ALLOWED = true;
1747 String mPkg;
1748 int mUid = UNKNOWN_UID;
1749 boolean mEnabled = DEFAULT_ENABLED;
1750 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1751
1752 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1753 mPkg = pkg;
1754 mUid = uid;
1755 mEnabled = enabled;
1756 mUserAllowed = userAllowed;
1757 }
1758
1759 public boolean isAllowed(String pkg, int uid) {
1760 if (pkg == null || uid == UNKNOWN_UID) {
1761 return false;
1762 }
1763 return pkg.equals(mPkg)
1764 && uid == mUid
1765 && (mUserAllowed && mEnabled);
1766 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001767 }
1768}