blob: f34b2cb5cf292389b469b7b74c8f3924eee7fd0b [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;
Julia Reynolds0c245002019-03-27 16:10:11 -040040import android.util.ArraySet;
41import android.util.Pair;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040042import android.util.Slog;
43import android.util.SparseBooleanArray;
44import android.util.proto.ProtoOutputStream;
45
46import com.android.internal.R;
47import com.android.internal.annotations.VisibleForTesting;
48import com.android.internal.logging.MetricsLogger;
49import com.android.internal.util.Preconditions;
50import com.android.internal.util.XmlUtils;
51
52import org.json.JSONArray;
53import org.json.JSONException;
54import org.json.JSONObject;
55import org.xmlpull.v1.XmlPullParser;
56import org.xmlpull.v1.XmlPullParserException;
57import org.xmlpull.v1.XmlSerializer;
58
59import java.io.IOException;
60import java.io.PrintWriter;
61import java.util.ArrayList;
62import java.util.Arrays;
63import java.util.Collection;
64import java.util.List;
65import java.util.Map;
66import java.util.Objects;
67import java.util.concurrent.ConcurrentHashMap;
68
69public class PreferencesHelper implements RankingConfig {
70 private static final String TAG = "NotificationPrefHelper";
71 private static final int XML_VERSION = 1;
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040072 private static final int UNKNOWN_UID = UserHandle.USER_NULL;
Julia Reynolds413ba842019-01-11 10:38:08 -050073 private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040074
75 @VisibleForTesting
76 static final String TAG_RANKING = "ranking";
77 private static final String TAG_PACKAGE = "package";
78 private static final String TAG_CHANNEL = "channel";
79 private static final String TAG_GROUP = "channelGroup";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040080 private static final String TAG_DELEGATE = "delegate";
Julia Reynolds12ad7ca2019-01-28 09:29:16 -050081 private static final String TAG_STATUS_ICONS = "status_icons";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040082
83 private static final String ATT_VERSION = "version";
84 private static final String ATT_NAME = "name";
85 private static final String ATT_UID = "uid";
86 private static final String ATT_ID = "id";
Mady Mellorc39b4ae2019-01-09 17:11:37 -080087 private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040088 private static final String ATT_PRIORITY = "priority";
89 private static final String ATT_VISIBILITY = "visibility";
90 private static final String ATT_IMPORTANCE = "importance";
91 private static final String ATT_SHOW_BADGE = "show_badge";
92 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -040093 private static final String ATT_ENABLED = "enabled";
94 private static final String ATT_USER_ALLOWED = "allowed";
Julia Reynolds12ad7ca2019-01-28 09:29:16 -050095 private static final String ATT_HIDE_SILENT = "hide_silent";
Aaron Heuckrothe5bec152018-07-09 16:26:09 -040096
97 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
98 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
99 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500100 @VisibleForTesting
Julia Reynolds2594b472019-04-03 13:30:16 -0400101 static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = true;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400102 private static final boolean DEFAULT_SHOW_BADGE = true;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800103 private static final boolean DEFAULT_ALLOW_BUBBLE = true;
Julia Reynolds413ba842019-01-11 10:38:08 -0500104 private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE = false;
Julia Reynolds0c245002019-03-27 16:10:11 -0400105 private static final boolean DEFAULT_APP_LOCKED_IMPORTANCE = false;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800106
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400107 /**
108 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
109 * fields.
110 */
111 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
112
113 /**
114 * All user-lockable fields for a given application.
115 */
116 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
117 public @interface LockableAppFields {
118 int USER_LOCKED_IMPORTANCE = 0x00000001;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800119 int USER_LOCKED_BUBBLE = 0x00000002;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400120 }
121
122 // pkg|uid => PackagePreferences
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400123 private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400124 // pkg => PackagePreferences
125 private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
126
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400127 private final Context mContext;
128 private final PackageManager mPm;
129 private final RankingHandler mRankingHandler;
130 private final ZenModeHelper mZenModeHelper;
131
132 private SparseBooleanArray mBadgingEnabled;
Julia Reynolds4509ce72019-01-31 13:12:43 -0500133 private SparseBooleanArray mBubblesEnabled;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400134 private boolean mAreChannelsBypassingDnd;
Julia Reynolds2594b472019-04-03 13:30:16 -0400135 private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400136
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400137 public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
138 ZenModeHelper zenHelper) {
139 mContext = context;
140 mZenModeHelper = zenHelper;
141 mRankingHandler = rankingHandler;
142 mPm = pm;
143
144 updateBadgingEnabled();
Julia Reynolds4509ce72019-01-31 13:12:43 -0500145 updateBubblesEnabled();
Beverly0479cde22018-11-09 11:05:34 -0500146 syncChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400147 }
148
Annie Meng8b646fd2019-02-01 18:46:42 +0000149 public void readXml(XmlPullParser parser, boolean forRestore, int userId)
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400150 throws XmlPullParserException, IOException {
151 int type = parser.getEventType();
152 if (type != XmlPullParser.START_TAG) return;
153 String tag = parser.getName();
154 if (!TAG_RANKING.equals(tag)) return;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400155 synchronized (mPackagePreferences) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500156 // Clobber groups and channels with the xml, but don't delete other data that wasn't
157 // present at the time of serialization.
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400158 mRestoredWithoutUids.clear();
159 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
160 tag = parser.getName();
161 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
162 return;
163 }
164 if (type == XmlPullParser.START_TAG) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500165 if (TAG_STATUS_ICONS.equals(tag)) {
Annie Meng8b646fd2019-02-01 18:46:42 +0000166 if (forRestore && userId != UserHandle.USER_SYSTEM) {
167 continue;
168 }
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500169 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
170 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
171 } else if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400172 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
173 String name = parser.getAttributeValue(null, ATT_NAME);
174 if (!TextUtils.isEmpty(name)) {
175 if (forRestore) {
176 try {
Annie Meng8b646fd2019-02-01 18:46:42 +0000177 uid = mPm.getPackageUidAsUser(name, userId);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400178 } catch (PackageManager.NameNotFoundException e) {
179 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400180 }
181 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400182
183 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
184 XmlUtils.readIntAttribute(
185 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
186 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
187 DEFAULT_PRIORITY),
188 XmlUtils.readIntAttribute(
189 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
190 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500191 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
192 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800193 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400194 r.importance = XmlUtils.readIntAttribute(
195 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
196 r.priority = XmlUtils.readIntAttribute(
197 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
198 r.visibility = XmlUtils.readIntAttribute(
199 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
200 r.showBadge = XmlUtils.readBooleanAttribute(
201 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
202 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
203 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
204
205 final int innerDepth = parser.getDepth();
206 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
207 && (type != XmlPullParser.END_TAG
208 || parser.getDepth() > innerDepth)) {
209 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
210 continue;
211 }
212
213 String tagName = parser.getName();
214 // Channel groups
215 if (TAG_GROUP.equals(tagName)) {
216 String id = parser.getAttributeValue(null, ATT_ID);
217 CharSequence groupName = parser.getAttributeValue(null,
218 ATT_NAME);
219 if (!TextUtils.isEmpty(id)) {
220 NotificationChannelGroup group
221 = new NotificationChannelGroup(id, groupName);
222 group.populateFromXml(parser);
223 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400224 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400225 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400226 // Channels
227 if (TAG_CHANNEL.equals(tagName)) {
228 String id = parser.getAttributeValue(null, ATT_ID);
229 String channelName = parser.getAttributeValue(null, ATT_NAME);
230 int channelImportance = XmlUtils.readIntAttribute(
231 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
232 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
233 NotificationChannel channel = new NotificationChannel(id,
234 channelName, channelImportance);
235 if (forRestore) {
236 channel.populateFromXmlForRestore(parser, mContext);
237 } else {
238 channel.populateFromXml(parser);
239 }
240 r.channels.put(id, channel);
241 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400242 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400243 // Delegate
244 if (TAG_DELEGATE.equals(tagName)) {
245 int delegateId =
246 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
247 String delegateName =
248 XmlUtils.readStringAttribute(parser, ATT_NAME);
249 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
250 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
251 boolean userAllowed = XmlUtils.readBooleanAttribute(
252 parser, ATT_USER_ALLOWED,
253 Delegate.DEFAULT_USER_ALLOWED);
254 Delegate d = null;
255 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
256 delegateName)) {
257 d = new Delegate(
258 delegateName, delegateId, delegateEnabled,
259 userAllowed);
260 }
261 r.delegate = d;
262 }
263
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400264 }
265
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400266 try {
267 deleteDefaultChannelIfNeeded(r);
268 } catch (PackageManager.NameNotFoundException e) {
269 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
270 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400271 }
272 }
273 }
274 }
275 }
276 throw new IllegalStateException("Failed to reach END_DOCUMENT");
277 }
278
279 private PackagePreferences getPackagePreferences(String pkg, int uid) {
280 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400281 synchronized (mPackagePreferences) {
282 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400283 }
284 }
285
286 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
287 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500288 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800289 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400290 }
291
292 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800293 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400294 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400295 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400296 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400297 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400298 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400299 if (r == null) {
300 r = new PackagePreferences();
301 r.pkg = pkg;
302 r.uid = uid;
303 r.importance = importance;
304 r.priority = priority;
305 r.visibility = visibility;
306 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800307 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400308
309 try {
310 createDefaultChannelIfNeeded(r);
311 } catch (PackageManager.NameNotFoundException e) {
312 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
313 }
314
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400315 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400316 mRestoredWithoutUids.put(pkg, r);
317 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400318 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400319 }
320 }
321 return r;
322 }
323 }
324
325 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
326 PackageManager.NameNotFoundException {
327 final int userId = UserHandle.getUserId(r.uid);
328 final ApplicationInfo applicationInfo =
329 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
330 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
331 // O apps should not have the default channel.
332 return false;
333 }
334
335 // Otherwise, this app should have the default channel.
336 return true;
337 }
338
339 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
340 PackageManager.NameNotFoundException {
341 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
342 // Not present
343 return;
344 }
345
346 if (shouldHaveDefaultChannel(r)) {
347 // Keep the default channel until upgraded.
348 return;
349 }
350
351 // Remove Default Channel.
352 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
353 }
354
355 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
356 PackageManager.NameNotFoundException {
357 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
358 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
359 com.android.internal.R.string.default_notification_channel_label));
360 return;
361 }
362
363 if (!shouldHaveDefaultChannel(r)) {
364 // Keep the default channel until upgraded.
365 return;
366 }
367
368 // Create Default Channel
369 NotificationChannel channel;
370 channel = new NotificationChannel(
371 NotificationChannel.DEFAULT_CHANNEL_ID,
372 mContext.getString(R.string.default_notification_channel_label),
373 r.importance);
374 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
375 channel.setLockscreenVisibility(r.visibility);
376 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
377 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
378 }
379 if (r.priority != DEFAULT_PRIORITY) {
380 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
381 }
382 if (r.visibility != DEFAULT_VISIBILITY) {
383 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
384 }
385 r.channels.put(channel.getId(), channel);
386 }
387
Annie Meng8b646fd2019-02-01 18:46:42 +0000388 public void writeXml(XmlSerializer out, boolean forBackup, int userId) throws IOException {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400389 out.startTag(null, TAG_RANKING);
390 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
Annie Meng8b646fd2019-02-01 18:46:42 +0000391 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS
392 && (!forBackup || userId == UserHandle.USER_SYSTEM)) {
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500393 out.startTag(null, TAG_STATUS_ICONS);
394 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
395 out.endTag(null, TAG_STATUS_ICONS);
396 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400397
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400398 synchronized (mPackagePreferences) {
399 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400400 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400401 final PackagePreferences r = mPackagePreferences.valueAt(i);
Annie Meng8b646fd2019-02-01 18:46:42 +0000402 if (forBackup && UserHandle.getUserId(r.uid) != userId) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400403 continue;
404 }
405 final boolean hasNonDefaultSettings =
406 r.importance != DEFAULT_IMPORTANCE
407 || r.priority != DEFAULT_PRIORITY
408 || r.visibility != DEFAULT_VISIBILITY
409 || r.showBadge != DEFAULT_SHOW_BADGE
410 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
411 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400412 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500413 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800414 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400415 if (hasNonDefaultSettings) {
416 out.startTag(null, TAG_PACKAGE);
417 out.attribute(null, ATT_NAME, r.pkg);
418 if (r.importance != DEFAULT_IMPORTANCE) {
419 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
420 }
421 if (r.priority != DEFAULT_PRIORITY) {
422 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
423 }
424 if (r.visibility != DEFAULT_VISIBILITY) {
425 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
426 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800427 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
428 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500429 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400430 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
431 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
432 Integer.toString(r.lockedAppFields));
433
434 if (!forBackup) {
435 out.attribute(null, ATT_UID, Integer.toString(r.uid));
436 }
437
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400438 if (r.delegate != null) {
439 out.startTag(null, TAG_DELEGATE);
440
441 out.attribute(null, ATT_NAME, r.delegate.mPkg);
442 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
443 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
444 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
445 }
446 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
447 out.attribute(null, ATT_USER_ALLOWED,
448 Boolean.toString(r.delegate.mUserAllowed));
449 }
450 out.endTag(null, TAG_DELEGATE);
451 }
452
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400453 for (NotificationChannelGroup group : r.groups.values()) {
454 group.writeXml(out);
455 }
456
457 for (NotificationChannel channel : r.channels.values()) {
458 if (forBackup) {
459 if (!channel.isDeleted()) {
460 channel.writeXmlForBackup(out, mContext);
461 }
462 } else {
463 channel.writeXml(out);
464 }
465 }
466
467 out.endTag(null, TAG_PACKAGE);
468 }
469 }
470 }
471 out.endTag(null, TAG_RANKING);
472 }
473
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800474 /**
475 * Sets whether bubbles are allowed.
476 *
477 * @param pkg the package to allow or not allow bubbles for.
478 * @param uid the uid to allow or not allow bubbles for.
479 * @param allowed whether bubbles are allowed.
480 */
481 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500482 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800483 p.allowBubble = allowed;
484 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500485 }
486
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800487 /**
488 * Whether bubbles are allowed.
489 *
490 * @param pkg the package to check if bubbles are allowed for
491 * @param uid the uid to check if bubbles are allowed for.
492 * @return whether bubbles are allowed.
493 */
Julia Reynolds4509ce72019-01-31 13:12:43 -0500494 @Override
Mady Mellor9db685a2019-01-23 13:23:37 -0800495 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800496 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500497 }
498
499 public int getAppLockedFields(String pkg, int uid) {
500 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
501 }
502
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400503 /**
504 * Gets importance.
505 */
506 @Override
507 public int getImportance(String packageName, int uid) {
508 return getOrCreatePackagePreferences(packageName, uid).importance;
509 }
510
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400511 /**
512 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
513 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
514 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
515 */
516 public boolean getIsAppImportanceLocked(String packageName, int uid) {
517 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
518 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
519 }
520
521 @Override
522 public boolean canShowBadge(String packageName, int uid) {
523 return getOrCreatePackagePreferences(packageName, uid).showBadge;
524 }
525
526 @Override
527 public void setShowBadge(String packageName, int uid, boolean showBadge) {
528 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
529 updateConfig();
530 }
531
532 @Override
533 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
534 if (groupId == null) {
535 return false;
536 }
537 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
538 NotificationChannelGroup group = r.groups.get(groupId);
539 if (group == null) {
540 return false;
541 }
542 return group.isBlocked();
543 }
544
545 int getPackagePriority(String pkg, int uid) {
546 return getOrCreatePackagePreferences(pkg, uid).priority;
547 }
548
549 int getPackageVisibility(String pkg, int uid) {
550 return getOrCreatePackagePreferences(pkg, uid).visibility;
551 }
552
553 @Override
554 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
555 boolean fromTargetApp) {
556 Preconditions.checkNotNull(pkg);
557 Preconditions.checkNotNull(group);
558 Preconditions.checkNotNull(group.getId());
559 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
560 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
561 if (r == null) {
562 throw new IllegalArgumentException("Invalid package");
563 }
564 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
565 if (!group.equals(oldGroup)) {
566 // will log for new entries as well as name/description changes
567 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
568 }
569 if (oldGroup != null) {
570 group.setChannels(oldGroup.getChannels());
571
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400572 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400573 if (fromTargetApp) {
574 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400575 group.unlockFields(group.getUserLockedFields());
576 group.lockFields(oldGroup.getUserLockedFields());
577 } else {
578 // but the system can
579 if (group.isBlocked() != oldGroup.isBlocked()) {
580 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500581 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400582 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400583 }
584 }
585 r.groups.put(group.getId(), group);
586 }
587
588 @Override
589 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
590 boolean fromTargetApp, boolean hasDndAccess) {
591 Preconditions.checkNotNull(pkg);
592 Preconditions.checkNotNull(channel);
593 Preconditions.checkNotNull(channel.getId());
594 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
595 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
596 if (r == null) {
597 throw new IllegalArgumentException("Invalid package");
598 }
599 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
600 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
601 }
602 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
603 throw new IllegalArgumentException("Reserved id");
604 }
605 NotificationChannel existing = r.channels.get(channel.getId());
606 // Keep most of the existing settings
607 if (existing != null && fromTargetApp) {
608 if (existing.isDeleted()) {
609 existing.setDeleted(false);
610
611 // log a resurrected channel as if it's new again
612 MetricsLogger.action(getChannelLog(channel, pkg).setType(
613 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
614 }
615
616 existing.setName(channel.getName().toString());
617 existing.setDescription(channel.getDescription());
618 existing.setBlockableSystem(channel.isBlockableSystem());
619 if (existing.getGroup() == null) {
620 existing.setGroup(channel.getGroup());
621 }
622
623 // Apps are allowed to downgrade channel importance if the user has not changed any
624 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500625 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400626 if (existing.getUserLockedFields() == 0 &&
627 channel.getImportance() < existing.getImportance()) {
628 existing.setImportance(channel.getImportance());
629 }
630
631 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
632 // fields on the channel yet
633 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
634 boolean bypassDnd = channel.canBypassDnd();
635 existing.setBypassDnd(bypassDnd);
636
Beverly0479cde22018-11-09 11:05:34 -0500637 if (bypassDnd != mAreChannelsBypassingDnd
638 || previousExistingImportance != existing.getImportance()) {
639 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400640 }
641 }
642
643 updateConfig();
644 return;
645 }
646 if (channel.getImportance() < IMPORTANCE_NONE
647 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
648 throw new IllegalArgumentException("Invalid importance level");
649 }
650
651 // Reset fields that apps aren't allowed to set.
652 if (fromTargetApp && !hasDndAccess) {
653 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
654 }
655 if (fromTargetApp) {
656 channel.setLockscreenVisibility(r.visibility);
657 }
658 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500659 channel.setImportanceLockedByOEM(r.oemLockedImportance);
660 if (!channel.isImportanceLockedByOEM()) {
661 if (r.futureOemLockedChannels.remove(channel.getId())) {
662 channel.setImportanceLockedByOEM(true);
663 }
664 }
Julia Reynolds0c245002019-03-27 16:10:11 -0400665 channel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400666 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
667 channel.setLockscreenVisibility(
668 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
669 }
670 if (!r.showBadge) {
671 channel.setShowBadge(false);
672 }
673
674 r.channels.put(channel.getId(), channel);
675 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500676 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400677 }
678 MetricsLogger.action(getChannelLog(channel, pkg).setType(
679 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
680 }
681
682 void clearLockedFields(NotificationChannel channel) {
683 channel.unlockFields(channel.getUserLockedFields());
684 }
685
686 @Override
687 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
688 boolean fromUser) {
689 Preconditions.checkNotNull(updatedChannel);
690 Preconditions.checkNotNull(updatedChannel.getId());
691 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
692 if (r == null) {
693 throw new IllegalArgumentException("Invalid package");
694 }
695 NotificationChannel channel = r.channels.get(updatedChannel.getId());
696 if (channel == null || channel.isDeleted()) {
697 throw new IllegalArgumentException("Channel does not exist");
698 }
699 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
700 updatedChannel.setLockscreenVisibility(
701 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
702 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400703 if (fromUser) {
704 updatedChannel.lockFields(channel.getUserLockedFields());
705 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400706 } else {
707 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400708 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500709 // no importance updates are allowed if OEM blocked it
710 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
711 if (updatedChannel.isImportanceLockedByOEM()) {
712 updatedChannel.setImportance(channel.getImportance());
713 }
Julia Reynolds0c245002019-03-27 16:10:11 -0400714 updatedChannel.setImportanceLockedByCriticalDeviceFunction(r.defaultAppLockedImportance);
715 if (updatedChannel.isImportanceLockedByCriticalDeviceFunction()) {
716 updatedChannel.setImportance(channel.getImportance());
717 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500718
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400719 r.channels.put(updatedChannel.getId(), updatedChannel);
720
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400721 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400722 // copy settings to app level so they are inherited by new channels
723 // when the app migrates
724 r.importance = updatedChannel.getImportance();
725 r.priority = updatedChannel.canBypassDnd()
726 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
727 r.visibility = updatedChannel.getLockscreenVisibility();
728 r.showBadge = updatedChannel.canShowBadge();
729 }
730
731 if (!channel.equals(updatedChannel)) {
732 // only log if there are real changes
Jan Althaus52ebc152019-02-21 16:24:19 +0100733 MetricsLogger.action(getChannelLog(updatedChannel, pkg)
734 .setSubtype(fromUser ? 1 : 0));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400735 }
736
Beverly0479cde22018-11-09 11:05:34 -0500737 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
738 || channel.getImportance() != updatedChannel.getImportance()) {
739 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400740 }
741 updateConfig();
742 }
743
744 @Override
745 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
746 boolean includeDeleted) {
747 Preconditions.checkNotNull(pkg);
748 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
749 if (r == null) {
750 return null;
751 }
752 if (channelId == null) {
753 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
754 }
755 final NotificationChannel nc = r.channels.get(channelId);
756 if (nc != null && (includeDeleted || !nc.isDeleted())) {
757 return nc;
758 }
759 return null;
760 }
761
762 @Override
763 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
764 PackagePreferences r = getPackagePreferences(pkg, uid);
765 if (r == null) {
766 return;
767 }
768 NotificationChannel channel = r.channels.get(channelId);
769 if (channel != null) {
770 channel.setDeleted(true);
771 LogMaker lm = getChannelLog(channel, pkg);
772 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
773 MetricsLogger.action(lm);
774
775 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500776 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400777 }
778 }
779 }
780
781 @Override
782 @VisibleForTesting
783 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
784 Preconditions.checkNotNull(pkg);
785 Preconditions.checkNotNull(channelId);
786 PackagePreferences r = getPackagePreferences(pkg, uid);
787 if (r == null) {
788 return;
789 }
790 r.channels.remove(channelId);
791 }
792
793 @Override
794 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
795 Preconditions.checkNotNull(pkg);
796 PackagePreferences r = getPackagePreferences(pkg, uid);
797 if (r == null) {
798 return;
799 }
800 int N = r.channels.size() - 1;
801 for (int i = N; i >= 0; i--) {
802 String key = r.channels.keyAt(i);
803 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
804 r.channels.remove(key);
805 }
806 }
807 }
808
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500809 public boolean shouldHideSilentStatusIcons() {
810 return mHideSilentStatusBarIcons;
811 }
812
813 public void setHideSilentStatusIcons(boolean hide) {
814 mHideSilentStatusBarIcons = hide;
815 }
816
Julia Reynolds413ba842019-01-11 10:38:08 -0500817 public void lockChannelsForOEM(String[] appOrChannelList) {
818 if (appOrChannelList == null) {
819 return;
820 }
821 for (String appOrChannel : appOrChannelList) {
822 if (!TextUtils.isEmpty(appOrChannel)) {
823 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
824 if (appSplit != null && appSplit.length > 0) {
825 String appName = appSplit[0];
826 String channelId = appSplit.length == 2 ? appSplit[1] : null;
827
828 synchronized (mPackagePreferences) {
829 for (PackagePreferences r : mPackagePreferences.values()) {
830 if (r.pkg.equals(appName)) {
831 if (channelId == null) {
832 // lock all channels for the app
833 r.oemLockedImportance = true;
834 for (NotificationChannel channel : r.channels.values()) {
835 channel.setImportanceLockedByOEM(true);
836 }
837 } else {
838 NotificationChannel channel = r.channels.get(channelId);
839 if (channel != null) {
840 channel.setImportanceLockedByOEM(true);
841 } else {
842 // if this channel shows up in the future, make sure it'll
843 // be locked immediately
844 r.futureOemLockedChannels.add(channelId);
845 }
846 }
847 }
848 }
849 }
850 }
851 }
852 }
853 }
854
Julia Reynolds0c245002019-03-27 16:10:11 -0400855 public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<String> toAdd) {
856 synchronized (mPackagePreferences) {
857 for (PackagePreferences p : mPackagePreferences.values()) {
858 if (userId == UserHandle.getUserId(p.uid)) {
859 if (toRemove != null && toRemove.contains(p.pkg)) {
860 p.defaultAppLockedImportance = false;
861 for (NotificationChannel channel : p.channels.values()) {
862 channel.setImportanceLockedByCriticalDeviceFunction(false);
863 }
864 } else if (toAdd != null && toAdd.contains(p.pkg)) {
865 p.defaultAppLockedImportance = true;
866 for (NotificationChannel channel : p.channels.values()) {
867 channel.setImportanceLockedByCriticalDeviceFunction(true);
868 }
869 }
870 }
871 }
872 }
873 }
874
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400875 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
876 int uid, String groupId, boolean includeDeleted) {
877 Preconditions.checkNotNull(pkg);
878 PackagePreferences r = getPackagePreferences(pkg, uid);
879 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
880 return null;
881 }
882 NotificationChannelGroup group = r.groups.get(groupId).clone();
883 group.setChannels(new ArrayList<>());
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 (groupId.equals(nc.getGroup())) {
889 group.addChannel(nc);
890 }
891 }
892 }
893 return group;
894 }
895
896 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
897 int uid) {
898 Preconditions.checkNotNull(pkg);
899 PackagePreferences r = getPackagePreferences(pkg, uid);
900 if (r == null) {
901 return null;
902 }
903 return r.groups.get(groupId);
904 }
905
906 @Override
907 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400908 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400909 Preconditions.checkNotNull(pkg);
910 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
911 PackagePreferences r = getPackagePreferences(pkg, uid);
912 if (r == null) {
913 return ParceledListSlice.emptyList();
914 }
915 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
916 int N = r.channels.size();
917 for (int i = 0; i < N; i++) {
918 final NotificationChannel nc = r.channels.valueAt(i);
919 if (includeDeleted || !nc.isDeleted()) {
920 if (nc.getGroup() != null) {
921 if (r.groups.get(nc.getGroup()) != null) {
922 NotificationChannelGroup ncg = groups.get(nc.getGroup());
923 if (ncg == null) {
924 ncg = r.groups.get(nc.getGroup()).clone();
925 ncg.setChannels(new ArrayList<>());
926 groups.put(nc.getGroup(), ncg);
927
928 }
929 ncg.addChannel(nc);
930 }
931 } else {
932 nonGrouped.addChannel(nc);
933 }
934 }
935 }
936 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
937 groups.put(null, nonGrouped);
938 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400939 if (includeEmpty) {
940 for (NotificationChannelGroup group : r.groups.values()) {
941 if (!groups.containsKey(group.getId())) {
942 groups.put(group.getId(), group);
943 }
944 }
945 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400946 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
947 }
948
949 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
950 String groupId) {
951 List<NotificationChannel> deletedChannels = new ArrayList<>();
952 PackagePreferences r = getPackagePreferences(pkg, uid);
953 if (r == null || TextUtils.isEmpty(groupId)) {
954 return deletedChannels;
955 }
956
957 r.groups.remove(groupId);
958
959 int N = r.channels.size();
960 for (int i = 0; i < N; i++) {
961 final NotificationChannel nc = r.channels.valueAt(i);
962 if (groupId.equals(nc.getGroup())) {
963 nc.setDeleted(true);
964 deletedChannels.add(nc);
965 }
966 }
967 return deletedChannels;
968 }
969
970 @Override
971 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
972 int uid) {
973 PackagePreferences r = getPackagePreferences(pkg, uid);
974 if (r == null) {
975 return new ArrayList<>();
976 }
977 return r.groups.values();
978 }
979
980 @Override
981 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
982 boolean includeDeleted) {
983 Preconditions.checkNotNull(pkg);
984 List<NotificationChannel> channels = new ArrayList<>();
985 PackagePreferences r = getPackagePreferences(pkg, uid);
986 if (r == null) {
987 return ParceledListSlice.emptyList();
988 }
989 int N = r.channels.size();
990 for (int i = 0; i < N; i++) {
991 final NotificationChannel nc = r.channels.valueAt(i);
992 if (includeDeleted || !nc.isDeleted()) {
993 channels.add(nc);
994 }
995 }
996 return new ParceledListSlice<>(channels);
997 }
998
999 /**
Beverly0479cde22018-11-09 11:05:34 -05001000 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
1001 */
1002 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
1003 int userId) {
1004 List<NotificationChannel> channels = new ArrayList<>();
1005 synchronized (mPackagePreferences) {
1006 final PackagePreferences r = mPackagePreferences.get(
1007 packagePreferencesKey(pkg, userId));
1008 // notifications from this package aren't blocked
1009 if (r != null && r.importance != IMPORTANCE_NONE) {
1010 for (NotificationChannel channel : r.channels.values()) {
1011 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1012 channels.add(channel);
1013 }
1014 }
1015 }
1016 }
1017 return new ParceledListSlice<>(channels);
1018 }
1019
1020 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001021 * True for pre-O apps that only have the default channel, or pre O apps that have no
1022 * channels yet. This method will create the default channel for pre-O apps that don't have it.
1023 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
1024 * upgrades.
1025 */
1026 public boolean onlyHasDefaultChannel(String pkg, int uid) {
1027 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
1028 if (r.channels.size() == 1
1029 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1030 return true;
1031 }
1032 return false;
1033 }
1034
1035 public int getDeletedChannelCount(String pkg, int uid) {
1036 Preconditions.checkNotNull(pkg);
1037 int deletedCount = 0;
1038 PackagePreferences r = getPackagePreferences(pkg, uid);
1039 if (r == null) {
1040 return deletedCount;
1041 }
1042 int N = r.channels.size();
1043 for (int i = 0; i < N; i++) {
1044 final NotificationChannel nc = r.channels.valueAt(i);
1045 if (nc.isDeleted()) {
1046 deletedCount++;
1047 }
1048 }
1049 return deletedCount;
1050 }
1051
1052 public int getBlockedChannelCount(String pkg, int uid) {
1053 Preconditions.checkNotNull(pkg);
1054 int blockedCount = 0;
1055 PackagePreferences r = getPackagePreferences(pkg, uid);
1056 if (r == null) {
1057 return blockedCount;
1058 }
1059 int N = r.channels.size();
1060 for (int i = 0; i < N; i++) {
1061 final NotificationChannel nc = r.channels.valueAt(i);
1062 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1063 blockedCount++;
1064 }
1065 }
1066 return blockedCount;
1067 }
1068
1069 public int getBlockedAppCount(int userId) {
1070 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001071 synchronized (mPackagePreferences) {
1072 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001073 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001074 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001075 if (userId == UserHandle.getUserId(r.uid)
1076 && r.importance == IMPORTANCE_NONE) {
1077 count++;
1078 }
1079 }
1080 }
1081 return count;
1082 }
1083
Beverly0479cde22018-11-09 11:05:34 -05001084 /**
1085 * Returns the number of apps that have at least one notification channel that can bypass DND
1086 * for given particular user
1087 */
1088 public int getAppsBypassingDndCount(int userId) {
1089 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001090 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001091 final int numPackagePreferences = mPackagePreferences.size();
1092 for (int i = 0; i < numPackagePreferences; i++) {
1093 final PackagePreferences r = mPackagePreferences.valueAt(i);
1094 // Package isn't associated with this userId or notifications from this package are
1095 // blocked
1096 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1097 continue;
1098 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001099
Beverly0479cde22018-11-09 11:05:34 -05001100 for (NotificationChannel channel : r.channels.values()) {
1101 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1102 count++;
1103 break;
1104 }
1105 }
1106 }
1107 }
1108 return count;
1109 }
1110
1111 /**
1112 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1113 * updating
1114 * @param userId
1115 */
1116 private void syncChannelsBypassingDnd(int userId) {
1117 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1118 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1119 updateChannelsBypassingDnd(userId);
1120 }
1121
1122 /**
1123 * Updates the user's NotificationPolicy based on whether the given userId
1124 * has channels bypassing DND
1125 * @param userId
1126 */
1127 private void updateChannelsBypassingDnd(int userId) {
1128 synchronized (mPackagePreferences) {
1129 final int numPackagePreferences = mPackagePreferences.size();
1130 for (int i = 0; i < numPackagePreferences; i++) {
1131 final PackagePreferences r = mPackagePreferences.valueAt(i);
1132 // Package isn't associated with this userId or notifications from this package are
1133 // blocked
1134 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1135 continue;
1136 }
1137
1138 for (NotificationChannel channel : r.channels.values()) {
1139 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001140 if (!mAreChannelsBypassingDnd) {
1141 mAreChannelsBypassingDnd = true;
1142 updateZenPolicy(true);
1143 }
1144 return;
1145 }
1146 }
1147 }
1148 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001149 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1150 if (mAreChannelsBypassingDnd) {
1151 mAreChannelsBypassingDnd = false;
1152 updateZenPolicy(false);
1153 }
1154 }
1155
Beverly0479cde22018-11-09 11:05:34 -05001156 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1157 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001158 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1159 return false;
Beverly0479cde22018-11-09 11:05:34 -05001160 }
1161
1162 // Channel is deleted or is blocked
1163 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1164 return false;
1165 }
1166
1167 return true;
1168 }
1169
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001170 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1171 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1172 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1173 policy.priorityCategories, policy.priorityCallSenders,
1174 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1175 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1176 : 0)));
1177 }
1178
1179 public boolean areChannelsBypassingDnd() {
1180 return mAreChannelsBypassingDnd;
1181 }
1182
1183 /**
1184 * Sets importance.
1185 */
1186 @Override
1187 public void setImportance(String pkgName, int uid, int importance) {
1188 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1189 updateConfig();
1190 }
1191
1192 public void setEnabled(String packageName, int uid, boolean enabled) {
1193 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1194 if (wasEnabled == enabled) {
1195 return;
1196 }
1197 setImportance(packageName, uid,
1198 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1199 }
1200
1201 /**
1202 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1203 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1204 * considered for sentiment adjustments (and thus never show a blocking helper).
1205 */
1206 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001207 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1208 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001209 return;
1210 }
1211
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001212 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001213 updateConfig();
1214 }
1215
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001216 /**
1217 * Returns the delegate for a given package, if it's allowed by the package and the user.
1218 */
1219 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1220 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1221
1222 if (prefs == null || prefs.delegate == null) {
1223 return null;
1224 }
1225 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1226 return null;
1227 }
1228 return prefs.delegate.mPkg;
1229 }
1230
1231 /**
1232 * Used by an app to delegate notification posting privileges to another apps.
1233 */
1234 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1235 String delegatePkg, int delegateUid) {
1236 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1237
1238 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1239 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1240 prefs.delegate = delegate;
1241 updateConfig();
1242 }
1243
1244 /**
1245 * Used by an app to turn off its notification delegate.
1246 */
1247 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1248 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1249 if (prefs != null && prefs.delegate != null) {
1250 prefs.delegate.mEnabled = false;
1251 updateConfig();
1252 }
1253 }
1254
1255 /**
1256 * Toggles whether an app can have a notification delegate on behalf of a user.
1257 */
1258 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1259 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1260 if (prefs != null && prefs.delegate != null) {
1261 prefs.delegate.mUserAllowed = userAllowed;
1262 updateConfig();
1263 }
1264 }
1265
1266 /**
1267 * Returns whether the given app is allowed on post notifications on behalf of the other given
1268 * app.
1269 */
1270 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1271 String potentialDelegatePkg, int potentialDelegateUid) {
1272 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1273
1274 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1275 }
1276
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001277 @VisibleForTesting
1278 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1279 if (original.canBypassDnd() != update.canBypassDnd()) {
1280 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1281 }
1282 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1283 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1284 }
1285 if (original.getImportance() != update.getImportance()) {
1286 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1287 }
1288 if (original.shouldShowLights() != update.shouldShowLights()
1289 || original.getLightColor() != update.getLightColor()) {
1290 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1291 }
1292 if (!Objects.equals(original.getSound(), update.getSound())) {
1293 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1294 }
1295 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1296 || original.shouldVibrate() != update.shouldVibrate()) {
1297 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1298 }
1299 if (original.canShowBadge() != update.canShowBadge()) {
1300 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1301 }
Julia Reynolds4509ce72019-01-31 13:12:43 -05001302 if (original.canBubble() != update.canBubble()) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001303 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001304 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001305 }
1306
1307 public void dump(PrintWriter pw, String prefix,
1308 @NonNull NotificationManagerService.DumpFilter filter) {
1309 pw.print(prefix);
1310 pw.println("per-package config:");
1311
1312 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001313 synchronized (mPackagePreferences) {
1314 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001315 }
1316 pw.println("Restored without uid:");
1317 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1318 }
1319
1320 public void dump(ProtoOutputStream proto,
1321 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001322 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001323 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001324 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001325 }
1326 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1327 mRestoredWithoutUids);
1328 }
1329
1330 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1331 @NonNull NotificationManagerService.DumpFilter filter,
1332 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1333 final int N = PackagePreferencess.size();
1334 for (int i = 0; i < N; i++) {
1335 final PackagePreferences r = PackagePreferencess.valueAt(i);
1336 if (filter.matches(r.pkg)) {
1337 pw.print(prefix);
1338 pw.print(" AppSettings: ");
1339 pw.print(r.pkg);
1340 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001341 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001342 pw.print(')');
1343 if (r.importance != DEFAULT_IMPORTANCE) {
1344 pw.print(" importance=");
1345 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1346 }
1347 if (r.priority != DEFAULT_PRIORITY) {
1348 pw.print(" priority=");
1349 pw.print(Notification.priorityToString(r.priority));
1350 }
1351 if (r.visibility != DEFAULT_VISIBILITY) {
1352 pw.print(" visibility=");
1353 pw.print(Notification.visibilityToString(r.visibility));
1354 }
1355 pw.print(" showBadge=");
1356 pw.print(Boolean.toString(r.showBadge));
1357 pw.println();
1358 for (NotificationChannel channel : r.channels.values()) {
1359 pw.print(prefix);
1360 channel.dump(pw, " ", filter.redact);
1361 }
1362 for (NotificationChannelGroup group : r.groups.values()) {
1363 pw.print(prefix);
1364 pw.print(" ");
1365 pw.print(" ");
1366 pw.println(group);
1367 }
1368 }
1369 }
1370 }
1371
1372 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1373 @NonNull NotificationManagerService.DumpFilter filter,
1374 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1375 final int N = PackagePreferencess.size();
1376 long fToken;
1377 for (int i = 0; i < N; i++) {
1378 final PackagePreferences r = PackagePreferencess.valueAt(i);
1379 if (filter.matches(r.pkg)) {
1380 fToken = proto.start(fieldId);
1381
1382 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1383 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1384 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1385 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1386 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1387 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1388
1389 for (NotificationChannel channel : r.channels.values()) {
1390 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1391 }
1392 for (NotificationChannelGroup group : r.groups.values()) {
1393 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1394 }
1395
1396 proto.end(fToken);
1397 }
1398 }
1399 }
1400
1401 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1402 JSONObject ranking = new JSONObject();
1403 JSONArray PackagePreferencess = new JSONArray();
1404 try {
1405 ranking.put("noUid", mRestoredWithoutUids.size());
1406 } catch (JSONException e) {
1407 // pass
1408 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001409 synchronized (mPackagePreferences) {
1410 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001411 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001412 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001413 if (filter == null || filter.matches(r.pkg)) {
1414 JSONObject PackagePreferences = new JSONObject();
1415 try {
1416 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1417 PackagePreferences.put("packageName", r.pkg);
1418 if (r.importance != DEFAULT_IMPORTANCE) {
1419 PackagePreferences.put("importance",
1420 NotificationListenerService.Ranking.importanceToString(
1421 r.importance));
1422 }
1423 if (r.priority != DEFAULT_PRIORITY) {
1424 PackagePreferences.put("priority",
1425 Notification.priorityToString(r.priority));
1426 }
1427 if (r.visibility != DEFAULT_VISIBILITY) {
1428 PackagePreferences.put("visibility",
1429 Notification.visibilityToString(r.visibility));
1430 }
1431 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1432 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1433 }
1434 JSONArray channels = new JSONArray();
1435 for (NotificationChannel channel : r.channels.values()) {
1436 channels.put(channel.toJson());
1437 }
1438 PackagePreferences.put("channels", channels);
1439 JSONArray groups = new JSONArray();
1440 for (NotificationChannelGroup group : r.groups.values()) {
1441 groups.put(group.toJson());
1442 }
1443 PackagePreferences.put("groups", groups);
1444 } catch (JSONException e) {
1445 // pass
1446 }
1447 PackagePreferencess.put(PackagePreferences);
1448 }
1449 }
1450 }
1451 try {
1452 ranking.put("PackagePreferencess", PackagePreferencess);
1453 } catch (JSONException e) {
1454 // pass
1455 }
1456 return ranking;
1457 }
1458
1459 /**
1460 * Dump only the ban information as structured JSON for the stats collector.
1461 *
1462 * This is intentionally redundant with {#link dumpJson} because the old
1463 * scraper will expect this format.
1464 *
1465 * @param filter
1466 * @return
1467 */
1468 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1469 JSONArray bans = new JSONArray();
1470 Map<Integer, String> packageBans = getPackageBans();
1471 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1472 final int userId = UserHandle.getUserId(ban.getKey());
1473 final String packageName = ban.getValue();
1474 if (filter == null || filter.matches(packageName)) {
1475 JSONObject banJson = new JSONObject();
1476 try {
1477 banJson.put("userId", userId);
1478 banJson.put("packageName", packageName);
1479 } catch (JSONException e) {
1480 e.printStackTrace();
1481 }
1482 bans.put(banJson);
1483 }
1484 }
1485 return bans;
1486 }
1487
1488 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001489 synchronized (mPackagePreferences) {
1490 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001491 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1492 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001493 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001494 if (r.importance == IMPORTANCE_NONE) {
1495 packageBans.put(r.uid, r.pkg);
1496 }
1497 }
1498
1499 return packageBans;
1500 }
1501 }
1502
1503 /**
1504 * Dump only the channel information as structured JSON for the stats collector.
1505 *
1506 * This is intentionally redundant with {#link dumpJson} because the old
1507 * scraper will expect this format.
1508 *
1509 * @param filter
1510 * @return
1511 */
1512 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1513 JSONArray channels = new JSONArray();
1514 Map<String, Integer> packageChannels = getPackageChannels();
1515 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1516 final String packageName = channelCount.getKey();
1517 if (filter == null || filter.matches(packageName)) {
1518 JSONObject channelCountJson = new JSONObject();
1519 try {
1520 channelCountJson.put("packageName", packageName);
1521 channelCountJson.put("channelCount", channelCount.getValue());
1522 } catch (JSONException e) {
1523 e.printStackTrace();
1524 }
1525 channels.put(channelCountJson);
1526 }
1527 }
1528 return channels;
1529 }
1530
1531 private Map<String, Integer> getPackageChannels() {
1532 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001533 synchronized (mPackagePreferences) {
1534 for (int i = 0; i < mPackagePreferences.size(); i++) {
1535 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001536 int channelCount = 0;
1537 for (int j = 0; j < r.channels.size(); j++) {
1538 if (!r.channels.valueAt(j).isDeleted()) {
1539 channelCount++;
1540 }
1541 }
1542 packageChannels.put(r.pkg, channelCount);
1543 }
1544 }
1545 return packageChannels;
1546 }
1547
Beverly0479cde22018-11-09 11:05:34 -05001548 /**
1549 * Called when user switches
1550 */
1551 public void onUserSwitched(int userId) {
1552 syncChannelsBypassingDnd(userId);
1553 }
1554
1555 /**
1556 * Called when user is unlocked
1557 */
1558 public void onUserUnlocked(int userId) {
1559 syncChannelsBypassingDnd(userId);
1560 }
1561
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001562 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001563 synchronized (mPackagePreferences) {
1564 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001565 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001566 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001567 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001568 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001569 }
1570 }
1571 }
1572 }
1573
1574 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001575 synchronized (mPackagePreferences) {
1576 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001577 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001578 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001579 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1580 if (PackagePreferences.channels.containsKey(
1581 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1582 PackagePreferences.channels.get(
1583 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1584 context.getResources().getString(
1585 R.string.default_notification_channel_label));
1586 }
1587 }
1588 }
1589 }
1590 }
1591
1592 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1593 int[] uidList) {
1594 if (pkgList == null || pkgList.length == 0) {
1595 return; // nothing to do
1596 }
1597 boolean updated = false;
1598 if (removingPackage) {
1599 // Remove notification settings for uninstalled package
1600 int size = Math.min(pkgList.length, uidList.length);
1601 for (int i = 0; i < size; i++) {
1602 final String pkg = pkgList[i];
1603 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001604 synchronized (mPackagePreferences) {
1605 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001606 }
1607 mRestoredWithoutUids.remove(pkg);
1608 updated = true;
1609 }
1610 } else {
1611 for (String pkg : pkgList) {
1612 // Package install
1613 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1614 if (r != null) {
1615 try {
1616 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1617 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001618 synchronized (mPackagePreferences) {
1619 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001620 }
1621 updated = true;
1622 } catch (PackageManager.NameNotFoundException e) {
1623 // noop
1624 }
1625 }
1626 // Package upgrade
1627 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001628 synchronized (mPackagePreferences) {
1629 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1630 mPm.getPackageUidAsUser(pkg, changeUserId));
1631 if (fullPackagePreferences != null) {
1632 createDefaultChannelIfNeeded(fullPackagePreferences);
1633 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1634 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001635 }
1636 } catch (PackageManager.NameNotFoundException e) {
1637 }
1638 }
1639 }
1640
1641 if (updated) {
1642 updateConfig();
1643 }
1644 }
1645
1646 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1647 return new LogMaker(
1648 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1649 .ACTION_NOTIFICATION_CHANNEL)
1650 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1651 .setPackageName(pkg)
1652 .addTaggedData(
1653 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1654 .FIELD_NOTIFICATION_CHANNEL_ID,
1655 channel.getId())
1656 .addTaggedData(
1657 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1658 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1659 channel.getImportance());
1660 }
1661
1662 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1663 return new LogMaker(
1664 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1665 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1666 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1667 .addTaggedData(
1668 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1669 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1670 groupId)
1671 .setPackageName(pkg);
1672 }
1673
Julia Reynolds4509ce72019-01-31 13:12:43 -05001674 public void updateBubblesEnabled() {
1675 if (mBubblesEnabled == null) {
1676 mBubblesEnabled = new SparseBooleanArray();
1677 }
1678 boolean changed = false;
1679 // update the cached values
1680 for (int index = 0; index < mBubblesEnabled.size(); index++) {
1681 int userId = mBubblesEnabled.keyAt(index);
1682 final boolean oldValue = mBubblesEnabled.get(userId);
1683 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1684 Settings.Secure.NOTIFICATION_BUBBLES,
1685 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
1686 mBubblesEnabled.put(userId, newValue);
1687 changed |= oldValue != newValue;
1688 }
1689 if (changed) {
1690 updateConfig();
1691 }
1692 }
1693
1694 public boolean bubblesEnabled(UserHandle userHandle) {
1695 int userId = userHandle.getIdentifier();
1696 if (userId == UserHandle.USER_ALL) {
1697 return false;
1698 }
1699 if (mBubblesEnabled.indexOfKey(userId) < 0) {
1700 mBubblesEnabled.put(userId,
1701 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1702 Settings.Secure.NOTIFICATION_BUBBLES,
1703 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
1704 }
1705 return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
1706 }
1707
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001708
1709 public void updateBadgingEnabled() {
1710 if (mBadgingEnabled == null) {
1711 mBadgingEnabled = new SparseBooleanArray();
1712 }
1713 boolean changed = false;
1714 // update the cached values
1715 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1716 int userId = mBadgingEnabled.keyAt(index);
1717 final boolean oldValue = mBadgingEnabled.get(userId);
1718 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1719 Settings.Secure.NOTIFICATION_BADGING,
1720 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1721 mBadgingEnabled.put(userId, newValue);
1722 changed |= oldValue != newValue;
1723 }
1724 if (changed) {
1725 updateConfig();
1726 }
1727 }
1728
1729 public boolean badgingEnabled(UserHandle userHandle) {
1730 int userId = userHandle.getIdentifier();
1731 if (userId == UserHandle.USER_ALL) {
1732 return false;
1733 }
1734 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1735 mBadgingEnabled.put(userId,
1736 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1737 Settings.Secure.NOTIFICATION_BADGING,
1738 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1739 }
1740 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1741 }
1742
1743 private void updateConfig() {
1744 mRankingHandler.requestSort();
1745 }
1746
1747 private static String packagePreferencesKey(String pkg, int uid) {
1748 return pkg + "|" + uid;
1749 }
1750
1751 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001752 String pkg;
1753 int uid = UNKNOWN_UID;
1754 int importance = DEFAULT_IMPORTANCE;
1755 int priority = DEFAULT_PRIORITY;
1756 int visibility = DEFAULT_VISIBILITY;
1757 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001758 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001759 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds0c245002019-03-27 16:10:11 -04001760 // these fields are loaded on boot from a different source of truth and so are not
1761 // written to notification policy xml
Julia Reynolds413ba842019-01-11 10:38:08 -05001762 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1763 List<String> futureOemLockedChannels = new ArrayList<>();
Julia Reynolds0c245002019-03-27 16:10:11 -04001764 boolean defaultAppLockedImportance = DEFAULT_APP_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001765
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001766 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001767 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1768 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001769
1770 public boolean isValidDelegate(String pkg, int uid) {
1771 return delegate != null && delegate.isAllowed(pkg, uid);
1772 }
1773 }
1774
1775 private static class Delegate {
1776 static final boolean DEFAULT_ENABLED = true;
1777 static final boolean DEFAULT_USER_ALLOWED = true;
1778 String mPkg;
1779 int mUid = UNKNOWN_UID;
1780 boolean mEnabled = DEFAULT_ENABLED;
1781 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1782
1783 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1784 mPkg = pkg;
1785 mUid = uid;
1786 mEnabled = enabled;
1787 mUserAllowed = userAllowed;
1788 }
1789
1790 public boolean isAllowed(String pkg, int uid) {
1791 if (pkg == null || uid == UNKNOWN_UID) {
1792 return false;
1793 }
1794 return pkg.equals(mPkg)
1795 && uid == mUid
1796 && (mUserAllowed && mEnabled);
1797 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001798 }
1799}