blob: 6e34b2d89fa14c4723f97d30af1552fab3d39ae2 [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
146 public void readXml(XmlPullParser parser, boolean forRestore)
147 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)) {
163 mHideSilentStatusBarIcons = XmlUtils.readBooleanAttribute(
164 parser, ATT_HIDE_SILENT, DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS);
165 } else if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400166 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
167 String name = parser.getAttributeValue(null, ATT_NAME);
168 if (!TextUtils.isEmpty(name)) {
169 if (forRestore) {
170 try {
171 //TODO: http://b/22388012
172 uid = mPm.getPackageUidAsUser(name,
173 UserHandle.USER_SYSTEM);
174 } catch (PackageManager.NameNotFoundException e) {
175 // noop
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400176 }
177 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400178
179 PackagePreferences r = getOrCreatePackagePreferences(name, uid,
180 XmlUtils.readIntAttribute(
181 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
182 XmlUtils.readIntAttribute(parser, ATT_PRIORITY,
183 DEFAULT_PRIORITY),
184 XmlUtils.readIntAttribute(
185 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
186 XmlUtils.readBooleanAttribute(
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500187 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE),
188 XmlUtils.readBooleanAttribute(
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800189 parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400190 r.importance = XmlUtils.readIntAttribute(
191 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
192 r.priority = XmlUtils.readIntAttribute(
193 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
194 r.visibility = XmlUtils.readIntAttribute(
195 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
196 r.showBadge = XmlUtils.readBooleanAttribute(
197 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
198 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
199 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
200
201 final int innerDepth = parser.getDepth();
202 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
203 && (type != XmlPullParser.END_TAG
204 || parser.getDepth() > innerDepth)) {
205 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
206 continue;
207 }
208
209 String tagName = parser.getName();
210 // Channel groups
211 if (TAG_GROUP.equals(tagName)) {
212 String id = parser.getAttributeValue(null, ATT_ID);
213 CharSequence groupName = parser.getAttributeValue(null,
214 ATT_NAME);
215 if (!TextUtils.isEmpty(id)) {
216 NotificationChannelGroup group
217 = new NotificationChannelGroup(id, groupName);
218 group.populateFromXml(parser);
219 r.groups.put(id, group);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400220 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400221 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400222 // Channels
223 if (TAG_CHANNEL.equals(tagName)) {
224 String id = parser.getAttributeValue(null, ATT_ID);
225 String channelName = parser.getAttributeValue(null, ATT_NAME);
226 int channelImportance = XmlUtils.readIntAttribute(
227 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
228 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
229 NotificationChannel channel = new NotificationChannel(id,
230 channelName, channelImportance);
231 if (forRestore) {
232 channel.populateFromXmlForRestore(parser, mContext);
233 } else {
234 channel.populateFromXml(parser);
235 }
236 r.channels.put(id, channel);
237 }
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400238 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400239 // Delegate
240 if (TAG_DELEGATE.equals(tagName)) {
241 int delegateId =
242 XmlUtils.readIntAttribute(parser, ATT_UID, UNKNOWN_UID);
243 String delegateName =
244 XmlUtils.readStringAttribute(parser, ATT_NAME);
245 boolean delegateEnabled = XmlUtils.readBooleanAttribute(
246 parser, ATT_ENABLED, Delegate.DEFAULT_ENABLED);
247 boolean userAllowed = XmlUtils.readBooleanAttribute(
248 parser, ATT_USER_ALLOWED,
249 Delegate.DEFAULT_USER_ALLOWED);
250 Delegate d = null;
251 if (delegateId != UNKNOWN_UID && !TextUtils.isEmpty(
252 delegateName)) {
253 d = new Delegate(
254 delegateName, delegateId, delegateEnabled,
255 userAllowed);
256 }
257 r.delegate = d;
258 }
259
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400260 }
261
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400262 try {
263 deleteDefaultChannelIfNeeded(r);
264 } catch (PackageManager.NameNotFoundException e) {
265 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
266 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400267 }
268 }
269 }
270 }
271 }
272 throw new IllegalStateException("Failed to reach END_DOCUMENT");
273 }
274
275 private PackagePreferences getPackagePreferences(String pkg, int uid) {
276 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400277 synchronized (mPackagePreferences) {
278 return mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400279 }
280 }
281
282 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
283 return getOrCreatePackagePreferences(pkg, uid,
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500284 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800285 DEFAULT_ALLOW_BUBBLE);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400286 }
287
288 private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800289 int priority, int visibility, boolean showBadge, boolean allowBubble) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400290 final String key = packagePreferencesKey(pkg, uid);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400291 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400292 PackagePreferences
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400293 r = (uid == UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400294 : mPackagePreferences.get(key);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400295 if (r == null) {
296 r = new PackagePreferences();
297 r.pkg = pkg;
298 r.uid = uid;
299 r.importance = importance;
300 r.priority = priority;
301 r.visibility = visibility;
302 r.showBadge = showBadge;
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800303 r.allowBubble = allowBubble;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400304
305 try {
306 createDefaultChannelIfNeeded(r);
307 } catch (PackageManager.NameNotFoundException e) {
308 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
309 }
310
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400311 if (r.uid == UNKNOWN_UID) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400312 mRestoredWithoutUids.put(pkg, r);
313 } else {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400314 mPackagePreferences.put(key, r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400315 }
316 }
317 return r;
318 }
319 }
320
321 private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
322 PackageManager.NameNotFoundException {
323 final int userId = UserHandle.getUserId(r.uid);
324 final ApplicationInfo applicationInfo =
325 mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
326 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
327 // O apps should not have the default channel.
328 return false;
329 }
330
331 // Otherwise, this app should have the default channel.
332 return true;
333 }
334
335 private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
336 PackageManager.NameNotFoundException {
337 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
338 // Not present
339 return;
340 }
341
342 if (shouldHaveDefaultChannel(r)) {
343 // Keep the default channel until upgraded.
344 return;
345 }
346
347 // Remove Default Channel.
348 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
349 }
350
351 private void createDefaultChannelIfNeeded(PackagePreferences r) throws
352 PackageManager.NameNotFoundException {
353 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
354 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
355 com.android.internal.R.string.default_notification_channel_label));
356 return;
357 }
358
359 if (!shouldHaveDefaultChannel(r)) {
360 // Keep the default channel until upgraded.
361 return;
362 }
363
364 // Create Default Channel
365 NotificationChannel channel;
366 channel = new NotificationChannel(
367 NotificationChannel.DEFAULT_CHANNEL_ID,
368 mContext.getString(R.string.default_notification_channel_label),
369 r.importance);
370 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
371 channel.setLockscreenVisibility(r.visibility);
372 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
373 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
374 }
375 if (r.priority != DEFAULT_PRIORITY) {
376 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
377 }
378 if (r.visibility != DEFAULT_VISIBILITY) {
379 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
380 }
381 r.channels.put(channel.getId(), channel);
382 }
383
384 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
385 out.startTag(null, TAG_RANKING);
386 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500387 if (mHideSilentStatusBarIcons != DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS) {
388 out.startTag(null, TAG_STATUS_ICONS);
389 out.attribute(null, ATT_HIDE_SILENT, String.valueOf(mHideSilentStatusBarIcons));
390 out.endTag(null, TAG_STATUS_ICONS);
391 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400392
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400393 synchronized (mPackagePreferences) {
394 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400395 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -0400396 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400397 //TODO: http://b/22388012
398 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
399 continue;
400 }
401 final boolean hasNonDefaultSettings =
402 r.importance != DEFAULT_IMPORTANCE
403 || r.priority != DEFAULT_PRIORITY
404 || r.visibility != DEFAULT_VISIBILITY
405 || r.showBadge != DEFAULT_SHOW_BADGE
406 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
407 || r.channels.size() > 0
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400408 || r.groups.size() > 0
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500409 || r.delegate != null
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800410 || r.allowBubble != DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400411 if (hasNonDefaultSettings) {
412 out.startTag(null, TAG_PACKAGE);
413 out.attribute(null, ATT_NAME, r.pkg);
414 if (r.importance != DEFAULT_IMPORTANCE) {
415 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
416 }
417 if (r.priority != DEFAULT_PRIORITY) {
418 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
419 }
420 if (r.visibility != DEFAULT_VISIBILITY) {
421 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
422 }
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800423 if (r.allowBubble != DEFAULT_ALLOW_BUBBLE) {
424 out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(r.allowBubble));
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500425 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400426 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
427 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
428 Integer.toString(r.lockedAppFields));
429
430 if (!forBackup) {
431 out.attribute(null, ATT_UID, Integer.toString(r.uid));
432 }
433
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -0400434 if (r.delegate != null) {
435 out.startTag(null, TAG_DELEGATE);
436
437 out.attribute(null, ATT_NAME, r.delegate.mPkg);
438 out.attribute(null, ATT_UID, Integer.toString(r.delegate.mUid));
439 if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
440 out.attribute(null, ATT_ENABLED, Boolean.toString(r.delegate.mEnabled));
441 }
442 if (r.delegate.mUserAllowed != Delegate.DEFAULT_USER_ALLOWED) {
443 out.attribute(null, ATT_USER_ALLOWED,
444 Boolean.toString(r.delegate.mUserAllowed));
445 }
446 out.endTag(null, TAG_DELEGATE);
447 }
448
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400449 for (NotificationChannelGroup group : r.groups.values()) {
450 group.writeXml(out);
451 }
452
453 for (NotificationChannel channel : r.channels.values()) {
454 if (forBackup) {
455 if (!channel.isDeleted()) {
456 channel.writeXmlForBackup(out, mContext);
457 }
458 } else {
459 channel.writeXml(out);
460 }
461 }
462
463 out.endTag(null, TAG_PACKAGE);
464 }
465 }
466 }
467 out.endTag(null, TAG_RANKING);
468 }
469
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800470 /**
471 * Sets whether bubbles are allowed.
472 *
473 * @param pkg the package to allow or not allow bubbles for.
474 * @param uid the uid to allow or not allow bubbles for.
475 * @param allowed whether bubbles are allowed.
476 */
477 public void setBubblesAllowed(String pkg, int uid, boolean allowed) {
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500478 PackagePreferences p = getOrCreatePackagePreferences(pkg, uid);
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800479 p.allowBubble = allowed;
480 p.lockedAppFields = p.lockedAppFields | LockableAppFields.USER_LOCKED_BUBBLE;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500481 }
482
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800483 /**
484 * Whether bubbles are allowed.
485 *
486 * @param pkg the package to check if bubbles are allowed for
487 * @param uid the uid to check if bubbles are allowed for.
488 * @return whether bubbles are allowed.
489 */
Julia Reynolds4509ce72019-01-31 13:12:43 -0500490 @Override
Mady Mellor9db685a2019-01-23 13:23:37 -0800491 public boolean areBubblesAllowed(String pkg, int uid) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -0800492 return getOrCreatePackagePreferences(pkg, uid).allowBubble;
Julia Reynolds33ab8a02018-12-17 16:19:52 -0500493 }
494
495 public int getAppLockedFields(String pkg, int uid) {
496 return getOrCreatePackagePreferences(pkg, uid).lockedAppFields;
497 }
498
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400499 /**
500 * Gets importance.
501 */
502 @Override
503 public int getImportance(String packageName, int uid) {
504 return getOrCreatePackagePreferences(packageName, uid).importance;
505 }
506
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400507 /**
508 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
509 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
510 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
511 */
512 public boolean getIsAppImportanceLocked(String packageName, int uid) {
513 int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
514 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
515 }
516
517 @Override
518 public boolean canShowBadge(String packageName, int uid) {
519 return getOrCreatePackagePreferences(packageName, uid).showBadge;
520 }
521
522 @Override
523 public void setShowBadge(String packageName, int uid, boolean showBadge) {
524 getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
525 updateConfig();
526 }
527
528 @Override
529 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
530 if (groupId == null) {
531 return false;
532 }
533 PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
534 NotificationChannelGroup group = r.groups.get(groupId);
535 if (group == null) {
536 return false;
537 }
538 return group.isBlocked();
539 }
540
541 int getPackagePriority(String pkg, int uid) {
542 return getOrCreatePackagePreferences(pkg, uid).priority;
543 }
544
545 int getPackageVisibility(String pkg, int uid) {
546 return getOrCreatePackagePreferences(pkg, uid).visibility;
547 }
548
549 @Override
550 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
551 boolean fromTargetApp) {
552 Preconditions.checkNotNull(pkg);
553 Preconditions.checkNotNull(group);
554 Preconditions.checkNotNull(group.getId());
555 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
556 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
557 if (r == null) {
558 throw new IllegalArgumentException("Invalid package");
559 }
560 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
561 if (!group.equals(oldGroup)) {
562 // will log for new entries as well as name/description changes
563 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
564 }
565 if (oldGroup != null) {
566 group.setChannels(oldGroup.getChannels());
567
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400568 // apps can't update the blocked status or app overlay permission
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400569 if (fromTargetApp) {
570 group.setBlocked(oldGroup.isBlocked());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400571 group.unlockFields(group.getUserLockedFields());
572 group.lockFields(oldGroup.getUserLockedFields());
573 } else {
574 // but the system can
575 if (group.isBlocked() != oldGroup.isBlocked()) {
576 group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
Beverly0479cde22018-11-09 11:05:34 -0500577 updateChannelsBypassingDnd(mContext.getUserId());
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -0400578 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400579 }
580 }
581 r.groups.put(group.getId(), group);
582 }
583
584 @Override
585 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
586 boolean fromTargetApp, boolean hasDndAccess) {
587 Preconditions.checkNotNull(pkg);
588 Preconditions.checkNotNull(channel);
589 Preconditions.checkNotNull(channel.getId());
590 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
591 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
592 if (r == null) {
593 throw new IllegalArgumentException("Invalid package");
594 }
595 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
596 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
597 }
598 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
599 throw new IllegalArgumentException("Reserved id");
600 }
601 NotificationChannel existing = r.channels.get(channel.getId());
602 // Keep most of the existing settings
603 if (existing != null && fromTargetApp) {
604 if (existing.isDeleted()) {
605 existing.setDeleted(false);
606
607 // log a resurrected channel as if it's new again
608 MetricsLogger.action(getChannelLog(channel, pkg).setType(
609 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
610 }
611
612 existing.setName(channel.getName().toString());
613 existing.setDescription(channel.getDescription());
614 existing.setBlockableSystem(channel.isBlockableSystem());
615 if (existing.getGroup() == null) {
616 existing.setGroup(channel.getGroup());
617 }
618
619 // Apps are allowed to downgrade channel importance if the user has not changed any
620 // fields on this channel yet.
Beverly0479cde22018-11-09 11:05:34 -0500621 final int previousExistingImportance = existing.getImportance();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400622 if (existing.getUserLockedFields() == 0 &&
623 channel.getImportance() < existing.getImportance()) {
624 existing.setImportance(channel.getImportance());
625 }
626
627 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
628 // fields on the channel yet
629 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
630 boolean bypassDnd = channel.canBypassDnd();
631 existing.setBypassDnd(bypassDnd);
632
Beverly0479cde22018-11-09 11:05:34 -0500633 if (bypassDnd != mAreChannelsBypassingDnd
634 || previousExistingImportance != existing.getImportance()) {
635 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400636 }
637 }
638
639 updateConfig();
640 return;
641 }
642 if (channel.getImportance() < IMPORTANCE_NONE
643 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
644 throw new IllegalArgumentException("Invalid importance level");
645 }
646
647 // Reset fields that apps aren't allowed to set.
648 if (fromTargetApp && !hasDndAccess) {
649 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
650 }
651 if (fromTargetApp) {
652 channel.setLockscreenVisibility(r.visibility);
653 }
654 clearLockedFields(channel);
Julia Reynolds413ba842019-01-11 10:38:08 -0500655 channel.setImportanceLockedByOEM(r.oemLockedImportance);
656 if (!channel.isImportanceLockedByOEM()) {
657 if (r.futureOemLockedChannels.remove(channel.getId())) {
658 channel.setImportanceLockedByOEM(true);
659 }
660 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400661 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
662 channel.setLockscreenVisibility(
663 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
664 }
665 if (!r.showBadge) {
666 channel.setShowBadge(false);
667 }
668
669 r.channels.put(channel.getId(), channel);
670 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
Beverly0479cde22018-11-09 11:05:34 -0500671 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400672 }
673 MetricsLogger.action(getChannelLog(channel, pkg).setType(
674 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
675 }
676
677 void clearLockedFields(NotificationChannel channel) {
678 channel.unlockFields(channel.getUserLockedFields());
679 }
680
681 @Override
682 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
683 boolean fromUser) {
684 Preconditions.checkNotNull(updatedChannel);
685 Preconditions.checkNotNull(updatedChannel.getId());
686 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
687 if (r == null) {
688 throw new IllegalArgumentException("Invalid package");
689 }
690 NotificationChannel channel = r.channels.get(updatedChannel.getId());
691 if (channel == null || channel.isDeleted()) {
692 throw new IllegalArgumentException("Channel does not exist");
693 }
694 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
695 updatedChannel.setLockscreenVisibility(
696 NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
697 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400698 if (fromUser) {
699 updatedChannel.lockFields(channel.getUserLockedFields());
700 lockFieldsForUpdate(channel, updatedChannel);
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400701 } else {
702 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400703 }
Julia Reynolds413ba842019-01-11 10:38:08 -0500704 // no importance updates are allowed if OEM blocked it
705 updatedChannel.setImportanceLockedByOEM(channel.isImportanceLockedByOEM());
706 if (updatedChannel.isImportanceLockedByOEM()) {
707 updatedChannel.setImportance(channel.getImportance());
708 }
709
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400710 r.channels.put(updatedChannel.getId(), updatedChannel);
711
Aaron Heuckroth9ae59f82018-07-13 12:23:36 -0400712 if (onlyHasDefaultChannel(pkg, uid)) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400713 // copy settings to app level so they are inherited by new channels
714 // when the app migrates
715 r.importance = updatedChannel.getImportance();
716 r.priority = updatedChannel.canBypassDnd()
717 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
718 r.visibility = updatedChannel.getLockscreenVisibility();
719 r.showBadge = updatedChannel.canShowBadge();
720 }
721
722 if (!channel.equals(updatedChannel)) {
723 // only log if there are real changes
724 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
725 }
726
Beverly0479cde22018-11-09 11:05:34 -0500727 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
728 || channel.getImportance() != updatedChannel.getImportance()) {
729 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400730 }
731 updateConfig();
732 }
733
734 @Override
735 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
736 boolean includeDeleted) {
737 Preconditions.checkNotNull(pkg);
738 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
739 if (r == null) {
740 return null;
741 }
742 if (channelId == null) {
743 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
744 }
745 final NotificationChannel nc = r.channels.get(channelId);
746 if (nc != null && (includeDeleted || !nc.isDeleted())) {
747 return nc;
748 }
749 return null;
750 }
751
752 @Override
753 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
754 PackagePreferences r = getPackagePreferences(pkg, uid);
755 if (r == null) {
756 return;
757 }
758 NotificationChannel channel = r.channels.get(channelId);
759 if (channel != null) {
760 channel.setDeleted(true);
761 LogMaker lm = getChannelLog(channel, pkg);
762 lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
763 MetricsLogger.action(lm);
764
765 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
Beverly0479cde22018-11-09 11:05:34 -0500766 updateChannelsBypassingDnd(mContext.getUserId());
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400767 }
768 }
769 }
770
771 @Override
772 @VisibleForTesting
773 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
774 Preconditions.checkNotNull(pkg);
775 Preconditions.checkNotNull(channelId);
776 PackagePreferences r = getPackagePreferences(pkg, uid);
777 if (r == null) {
778 return;
779 }
780 r.channels.remove(channelId);
781 }
782
783 @Override
784 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
785 Preconditions.checkNotNull(pkg);
786 PackagePreferences r = getPackagePreferences(pkg, uid);
787 if (r == null) {
788 return;
789 }
790 int N = r.channels.size() - 1;
791 for (int i = N; i >= 0; i--) {
792 String key = r.channels.keyAt(i);
793 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
794 r.channels.remove(key);
795 }
796 }
797 }
798
Julia Reynolds12ad7ca2019-01-28 09:29:16 -0500799 public boolean shouldHideSilentStatusIcons() {
800 return mHideSilentStatusBarIcons;
801 }
802
803 public void setHideSilentStatusIcons(boolean hide) {
804 mHideSilentStatusBarIcons = hide;
805 }
806
Julia Reynolds413ba842019-01-11 10:38:08 -0500807 public void lockChannelsForOEM(String[] appOrChannelList) {
808 if (appOrChannelList == null) {
809 return;
810 }
811 for (String appOrChannel : appOrChannelList) {
812 if (!TextUtils.isEmpty(appOrChannel)) {
813 String[] appSplit = appOrChannel.split(NON_BLOCKABLE_CHANNEL_DELIM);
814 if (appSplit != null && appSplit.length > 0) {
815 String appName = appSplit[0];
816 String channelId = appSplit.length == 2 ? appSplit[1] : null;
817
818 synchronized (mPackagePreferences) {
819 for (PackagePreferences r : mPackagePreferences.values()) {
820 if (r.pkg.equals(appName)) {
821 if (channelId == null) {
822 // lock all channels for the app
823 r.oemLockedImportance = true;
824 for (NotificationChannel channel : r.channels.values()) {
825 channel.setImportanceLockedByOEM(true);
826 }
827 } else {
828 NotificationChannel channel = r.channels.get(channelId);
829 if (channel != null) {
830 channel.setImportanceLockedByOEM(true);
831 } else {
832 // if this channel shows up in the future, make sure it'll
833 // be locked immediately
834 r.futureOemLockedChannels.add(channelId);
835 }
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400845 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
846 int uid, String groupId, boolean includeDeleted) {
847 Preconditions.checkNotNull(pkg);
848 PackagePreferences r = getPackagePreferences(pkg, uid);
849 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
850 return null;
851 }
852 NotificationChannelGroup group = r.groups.get(groupId).clone();
853 group.setChannels(new ArrayList<>());
854 int N = r.channels.size();
855 for (int i = 0; i < N; i++) {
856 final NotificationChannel nc = r.channels.valueAt(i);
857 if (includeDeleted || !nc.isDeleted()) {
858 if (groupId.equals(nc.getGroup())) {
859 group.addChannel(nc);
860 }
861 }
862 }
863 return group;
864 }
865
866 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
867 int uid) {
868 Preconditions.checkNotNull(pkg);
869 PackagePreferences r = getPackagePreferences(pkg, uid);
870 if (r == null) {
871 return null;
872 }
873 return r.groups.get(groupId);
874 }
875
876 @Override
877 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400878 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400879 Preconditions.checkNotNull(pkg);
880 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
881 PackagePreferences r = getPackagePreferences(pkg, uid);
882 if (r == null) {
883 return ParceledListSlice.emptyList();
884 }
885 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
886 int N = r.channels.size();
887 for (int i = 0; i < N; i++) {
888 final NotificationChannel nc = r.channels.valueAt(i);
889 if (includeDeleted || !nc.isDeleted()) {
890 if (nc.getGroup() != null) {
891 if (r.groups.get(nc.getGroup()) != null) {
892 NotificationChannelGroup ncg = groups.get(nc.getGroup());
893 if (ncg == null) {
894 ncg = r.groups.get(nc.getGroup()).clone();
895 ncg.setChannels(new ArrayList<>());
896 groups.put(nc.getGroup(), ncg);
897
898 }
899 ncg.addChannel(nc);
900 }
901 } else {
902 nonGrouped.addChannel(nc);
903 }
904 }
905 }
906 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
907 groups.put(null, nonGrouped);
908 }
Julia Reynolds13ed28b2018-09-21 15:20:13 -0400909 if (includeEmpty) {
910 for (NotificationChannelGroup group : r.groups.values()) {
911 if (!groups.containsKey(group.getId())) {
912 groups.put(group.getId(), group);
913 }
914 }
915 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400916 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
917 }
918
919 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
920 String groupId) {
921 List<NotificationChannel> deletedChannels = new ArrayList<>();
922 PackagePreferences r = getPackagePreferences(pkg, uid);
923 if (r == null || TextUtils.isEmpty(groupId)) {
924 return deletedChannels;
925 }
926
927 r.groups.remove(groupId);
928
929 int N = r.channels.size();
930 for (int i = 0; i < N; i++) {
931 final NotificationChannel nc = r.channels.valueAt(i);
932 if (groupId.equals(nc.getGroup())) {
933 nc.setDeleted(true);
934 deletedChannels.add(nc);
935 }
936 }
937 return deletedChannels;
938 }
939
940 @Override
941 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
942 int uid) {
943 PackagePreferences r = getPackagePreferences(pkg, uid);
944 if (r == null) {
945 return new ArrayList<>();
946 }
947 return r.groups.values();
948 }
949
950 @Override
951 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
952 boolean includeDeleted) {
953 Preconditions.checkNotNull(pkg);
954 List<NotificationChannel> channels = new ArrayList<>();
955 PackagePreferences r = getPackagePreferences(pkg, uid);
956 if (r == null) {
957 return ParceledListSlice.emptyList();
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 (includeDeleted || !nc.isDeleted()) {
963 channels.add(nc);
964 }
965 }
966 return new ParceledListSlice<>(channels);
967 }
968
969 /**
Beverly0479cde22018-11-09 11:05:34 -0500970 * Gets all notification channels associated with the given pkg and userId that can bypass dnd
971 */
972 public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
973 int userId) {
974 List<NotificationChannel> channels = new ArrayList<>();
975 synchronized (mPackagePreferences) {
976 final PackagePreferences r = mPackagePreferences.get(
977 packagePreferencesKey(pkg, userId));
978 // notifications from this package aren't blocked
979 if (r != null && r.importance != IMPORTANCE_NONE) {
980 for (NotificationChannel channel : r.channels.values()) {
981 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
982 channels.add(channel);
983 }
984 }
985 }
986 }
987 return new ParceledListSlice<>(channels);
988 }
989
990 /**
Aaron Heuckrothe5bec152018-07-09 16:26:09 -0400991 * True for pre-O apps that only have the default channel, or pre O apps that have no
992 * channels yet. This method will create the default channel for pre-O apps that don't have it.
993 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
994 * upgrades.
995 */
996 public boolean onlyHasDefaultChannel(String pkg, int uid) {
997 PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
998 if (r.channels.size() == 1
999 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1000 return true;
1001 }
1002 return false;
1003 }
1004
1005 public int getDeletedChannelCount(String pkg, int uid) {
1006 Preconditions.checkNotNull(pkg);
1007 int deletedCount = 0;
1008 PackagePreferences r = getPackagePreferences(pkg, uid);
1009 if (r == null) {
1010 return deletedCount;
1011 }
1012 int N = r.channels.size();
1013 for (int i = 0; i < N; i++) {
1014 final NotificationChannel nc = r.channels.valueAt(i);
1015 if (nc.isDeleted()) {
1016 deletedCount++;
1017 }
1018 }
1019 return deletedCount;
1020 }
1021
1022 public int getBlockedChannelCount(String pkg, int uid) {
1023 Preconditions.checkNotNull(pkg);
1024 int blockedCount = 0;
1025 PackagePreferences r = getPackagePreferences(pkg, uid);
1026 if (r == null) {
1027 return blockedCount;
1028 }
1029 int N = r.channels.size();
1030 for (int i = 0; i < N; i++) {
1031 final NotificationChannel nc = r.channels.valueAt(i);
1032 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1033 blockedCount++;
1034 }
1035 }
1036 return blockedCount;
1037 }
1038
1039 public int getBlockedAppCount(int userId) {
1040 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001041 synchronized (mPackagePreferences) {
1042 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001043 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001044 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001045 if (userId == UserHandle.getUserId(r.uid)
1046 && r.importance == IMPORTANCE_NONE) {
1047 count++;
1048 }
1049 }
1050 }
1051 return count;
1052 }
1053
Beverly0479cde22018-11-09 11:05:34 -05001054 /**
1055 * Returns the number of apps that have at least one notification channel that can bypass DND
1056 * for given particular user
1057 */
1058 public int getAppsBypassingDndCount(int userId) {
1059 int count = 0;
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001060 synchronized (mPackagePreferences) {
Beverly0479cde22018-11-09 11:05:34 -05001061 final int numPackagePreferences = mPackagePreferences.size();
1062 for (int i = 0; i < numPackagePreferences; i++) {
1063 final PackagePreferences r = mPackagePreferences.valueAt(i);
1064 // Package isn't associated with this userId or notifications from this package are
1065 // blocked
1066 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1067 continue;
1068 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001069
Beverly0479cde22018-11-09 11:05:34 -05001070 for (NotificationChannel channel : r.channels.values()) {
1071 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
1072 count++;
1073 break;
1074 }
1075 }
1076 }
1077 }
1078 return count;
1079 }
1080
1081 /**
1082 * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
1083 * updating
1084 * @param userId
1085 */
1086 private void syncChannelsBypassingDnd(int userId) {
1087 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
1088 & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
1089 updateChannelsBypassingDnd(userId);
1090 }
1091
1092 /**
1093 * Updates the user's NotificationPolicy based on whether the given userId
1094 * has channels bypassing DND
1095 * @param userId
1096 */
1097 private void updateChannelsBypassingDnd(int userId) {
1098 synchronized (mPackagePreferences) {
1099 final int numPackagePreferences = mPackagePreferences.size();
1100 for (int i = 0; i < numPackagePreferences; i++) {
1101 final PackagePreferences r = mPackagePreferences.valueAt(i);
1102 // Package isn't associated with this userId or notifications from this package are
1103 // blocked
1104 if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
1105 continue;
1106 }
1107
1108 for (NotificationChannel channel : r.channels.values()) {
1109 if (channelIsLive(r, channel) && channel.canBypassDnd()) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001110 if (!mAreChannelsBypassingDnd) {
1111 mAreChannelsBypassingDnd = true;
1112 updateZenPolicy(true);
1113 }
1114 return;
1115 }
1116 }
1117 }
1118 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001119 // If no channels bypass DND, update the zen policy once to disable DND bypass.
1120 if (mAreChannelsBypassingDnd) {
1121 mAreChannelsBypassingDnd = false;
1122 updateZenPolicy(false);
1123 }
1124 }
1125
Beverly0479cde22018-11-09 11:05:34 -05001126 private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
1127 // Channel is in a group that's blocked
Beverly4f7b53d2018-11-20 09:56:31 -05001128 if (isGroupBlocked(pkgPref.pkg, pkgPref.uid, channel.getGroup())) {
1129 return false;
Beverly0479cde22018-11-09 11:05:34 -05001130 }
1131
1132 // Channel is deleted or is blocked
1133 if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
1134 return false;
1135 }
1136
1137 return true;
1138 }
1139
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001140 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1141 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1142 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1143 policy.priorityCategories, policy.priorityCallSenders,
1144 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1145 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1146 : 0)));
1147 }
1148
1149 public boolean areChannelsBypassingDnd() {
1150 return mAreChannelsBypassingDnd;
1151 }
1152
1153 /**
1154 * Sets importance.
1155 */
1156 @Override
1157 public void setImportance(String pkgName, int uid, int importance) {
1158 getOrCreatePackagePreferences(pkgName, uid).importance = importance;
1159 updateConfig();
1160 }
1161
1162 public void setEnabled(String packageName, int uid, boolean enabled) {
1163 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
1164 if (wasEnabled == enabled) {
1165 return;
1166 }
1167 setImportance(packageName, uid,
1168 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
1169 }
1170
1171 /**
1172 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1173 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1174 * considered for sentiment adjustments (and thus never show a blocking helper).
1175 */
1176 public void setAppImportanceLocked(String packageName, int uid) {
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001177 PackagePreferences prefs = getOrCreatePackagePreferences(packageName, uid);
1178 if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001179 return;
1180 }
1181
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001182 prefs.lockedAppFields = prefs.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001183 updateConfig();
1184 }
1185
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001186 /**
1187 * Returns the delegate for a given package, if it's allowed by the package and the user.
1188 */
1189 public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) {
1190 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1191
1192 if (prefs == null || prefs.delegate == null) {
1193 return null;
1194 }
1195 if (!prefs.delegate.mUserAllowed || !prefs.delegate.mEnabled) {
1196 return null;
1197 }
1198 return prefs.delegate.mPkg;
1199 }
1200
1201 /**
1202 * Used by an app to delegate notification posting privileges to another apps.
1203 */
1204 public void setNotificationDelegate(String sourcePkg, int sourceUid,
1205 String delegatePkg, int delegateUid) {
1206 PackagePreferences prefs = getOrCreatePackagePreferences(sourcePkg, sourceUid);
1207
1208 boolean userAllowed = prefs.delegate == null || prefs.delegate.mUserAllowed;
1209 Delegate delegate = new Delegate(delegatePkg, delegateUid, true, userAllowed);
1210 prefs.delegate = delegate;
1211 updateConfig();
1212 }
1213
1214 /**
1215 * Used by an app to turn off its notification delegate.
1216 */
1217 public void revokeNotificationDelegate(String sourcePkg, int sourceUid) {
1218 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1219 if (prefs != null && prefs.delegate != null) {
1220 prefs.delegate.mEnabled = false;
1221 updateConfig();
1222 }
1223 }
1224
1225 /**
1226 * Toggles whether an app can have a notification delegate on behalf of a user.
1227 */
1228 public void toggleNotificationDelegate(String sourcePkg, int sourceUid, boolean userAllowed) {
1229 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1230 if (prefs != null && prefs.delegate != null) {
1231 prefs.delegate.mUserAllowed = userAllowed;
1232 updateConfig();
1233 }
1234 }
1235
1236 /**
1237 * Returns whether the given app is allowed on post notifications on behalf of the other given
1238 * app.
1239 */
1240 public boolean isDelegateAllowed(String sourcePkg, int sourceUid,
1241 String potentialDelegatePkg, int potentialDelegateUid) {
1242 PackagePreferences prefs = getPackagePreferences(sourcePkg, sourceUid);
1243
1244 return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, potentialDelegateUid);
1245 }
1246
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001247 @VisibleForTesting
1248 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
1249 if (original.canBypassDnd() != update.canBypassDnd()) {
1250 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1251 }
1252 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1253 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1254 }
1255 if (original.getImportance() != update.getImportance()) {
1256 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1257 }
1258 if (original.shouldShowLights() != update.shouldShowLights()
1259 || original.getLightColor() != update.getLightColor()) {
1260 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1261 }
1262 if (!Objects.equals(original.getSound(), update.getSound())) {
1263 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1264 }
1265 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1266 || original.shouldVibrate() != update.shouldVibrate()) {
1267 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1268 }
1269 if (original.canShowBadge() != update.canShowBadge()) {
1270 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1271 }
Julia Reynolds4509ce72019-01-31 13:12:43 -05001272 if (original.canBubble() != update.canBubble()) {
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001273 update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
Julia Reynoldsb6bd93d2018-10-24 09:22:38 -04001274 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001275 }
1276
1277 public void dump(PrintWriter pw, String prefix,
1278 @NonNull NotificationManagerService.DumpFilter filter) {
1279 pw.print(prefix);
1280 pw.println("per-package config:");
1281
1282 pw.println("PackagePreferencess:");
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001283 synchronized (mPackagePreferences) {
1284 dumpPackagePreferencess(pw, prefix, filter, mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001285 }
1286 pw.println("Restored without uid:");
1287 dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
1288 }
1289
1290 public void dump(ProtoOutputStream proto,
1291 @NonNull NotificationManagerService.DumpFilter filter) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001292 synchronized (mPackagePreferences) {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001293 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001294 mPackagePreferences);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001295 }
1296 dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1297 mRestoredWithoutUids);
1298 }
1299
1300 private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
1301 @NonNull NotificationManagerService.DumpFilter filter,
1302 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1303 final int N = PackagePreferencess.size();
1304 for (int i = 0; i < N; i++) {
1305 final PackagePreferences r = PackagePreferencess.valueAt(i);
1306 if (filter.matches(r.pkg)) {
1307 pw.print(prefix);
1308 pw.print(" AppSettings: ");
1309 pw.print(r.pkg);
1310 pw.print(" (");
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001311 pw.print(r.uid == UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001312 pw.print(')');
1313 if (r.importance != DEFAULT_IMPORTANCE) {
1314 pw.print(" importance=");
1315 pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
1316 }
1317 if (r.priority != DEFAULT_PRIORITY) {
1318 pw.print(" priority=");
1319 pw.print(Notification.priorityToString(r.priority));
1320 }
1321 if (r.visibility != DEFAULT_VISIBILITY) {
1322 pw.print(" visibility=");
1323 pw.print(Notification.visibilityToString(r.visibility));
1324 }
1325 pw.print(" showBadge=");
1326 pw.print(Boolean.toString(r.showBadge));
1327 pw.println();
1328 for (NotificationChannel channel : r.channels.values()) {
1329 pw.print(prefix);
1330 channel.dump(pw, " ", filter.redact);
1331 }
1332 for (NotificationChannelGroup group : r.groups.values()) {
1333 pw.print(prefix);
1334 pw.print(" ");
1335 pw.print(" ");
1336 pw.println(group);
1337 }
1338 }
1339 }
1340 }
1341
1342 private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
1343 @NonNull NotificationManagerService.DumpFilter filter,
1344 ArrayMap<String, PackagePreferences> PackagePreferencess) {
1345 final int N = PackagePreferencess.size();
1346 long fToken;
1347 for (int i = 0; i < N; i++) {
1348 final PackagePreferences r = PackagePreferencess.valueAt(i);
1349 if (filter.matches(r.pkg)) {
1350 fToken = proto.start(fieldId);
1351
1352 proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
1353 proto.write(RankingHelperProto.RecordProto.UID, r.uid);
1354 proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
1355 proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
1356 proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
1357 proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
1358
1359 for (NotificationChannel channel : r.channels.values()) {
1360 channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
1361 }
1362 for (NotificationChannelGroup group : r.groups.values()) {
1363 group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
1364 }
1365
1366 proto.end(fToken);
1367 }
1368 }
1369 }
1370
1371 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1372 JSONObject ranking = new JSONObject();
1373 JSONArray PackagePreferencess = new JSONArray();
1374 try {
1375 ranking.put("noUid", mRestoredWithoutUids.size());
1376 } catch (JSONException e) {
1377 // pass
1378 }
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001379 synchronized (mPackagePreferences) {
1380 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001381 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001382 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001383 if (filter == null || filter.matches(r.pkg)) {
1384 JSONObject PackagePreferences = new JSONObject();
1385 try {
1386 PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
1387 PackagePreferences.put("packageName", r.pkg);
1388 if (r.importance != DEFAULT_IMPORTANCE) {
1389 PackagePreferences.put("importance",
1390 NotificationListenerService.Ranking.importanceToString(
1391 r.importance));
1392 }
1393 if (r.priority != DEFAULT_PRIORITY) {
1394 PackagePreferences.put("priority",
1395 Notification.priorityToString(r.priority));
1396 }
1397 if (r.visibility != DEFAULT_VISIBILITY) {
1398 PackagePreferences.put("visibility",
1399 Notification.visibilityToString(r.visibility));
1400 }
1401 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1402 PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
1403 }
1404 JSONArray channels = new JSONArray();
1405 for (NotificationChannel channel : r.channels.values()) {
1406 channels.put(channel.toJson());
1407 }
1408 PackagePreferences.put("channels", channels);
1409 JSONArray groups = new JSONArray();
1410 for (NotificationChannelGroup group : r.groups.values()) {
1411 groups.put(group.toJson());
1412 }
1413 PackagePreferences.put("groups", groups);
1414 } catch (JSONException e) {
1415 // pass
1416 }
1417 PackagePreferencess.put(PackagePreferences);
1418 }
1419 }
1420 }
1421 try {
1422 ranking.put("PackagePreferencess", PackagePreferencess);
1423 } catch (JSONException e) {
1424 // pass
1425 }
1426 return ranking;
1427 }
1428
1429 /**
1430 * Dump only the ban information as structured JSON for the stats collector.
1431 *
1432 * This is intentionally redundant with {#link dumpJson} because the old
1433 * scraper will expect this format.
1434 *
1435 * @param filter
1436 * @return
1437 */
1438 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1439 JSONArray bans = new JSONArray();
1440 Map<Integer, String> packageBans = getPackageBans();
1441 for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
1442 final int userId = UserHandle.getUserId(ban.getKey());
1443 final String packageName = ban.getValue();
1444 if (filter == null || filter.matches(packageName)) {
1445 JSONObject banJson = new JSONObject();
1446 try {
1447 banJson.put("userId", userId);
1448 banJson.put("packageName", packageName);
1449 } catch (JSONException e) {
1450 e.printStackTrace();
1451 }
1452 bans.put(banJson);
1453 }
1454 }
1455 return bans;
1456 }
1457
1458 public Map<Integer, String> getPackageBans() {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001459 synchronized (mPackagePreferences) {
1460 final int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001461 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1462 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001463 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001464 if (r.importance == IMPORTANCE_NONE) {
1465 packageBans.put(r.uid, r.pkg);
1466 }
1467 }
1468
1469 return packageBans;
1470 }
1471 }
1472
1473 /**
1474 * Dump only the channel information as structured JSON for the stats collector.
1475 *
1476 * This is intentionally redundant with {#link dumpJson} because the old
1477 * scraper will expect this format.
1478 *
1479 * @param filter
1480 * @return
1481 */
1482 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1483 JSONArray channels = new JSONArray();
1484 Map<String, Integer> packageChannels = getPackageChannels();
1485 for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1486 final String packageName = channelCount.getKey();
1487 if (filter == null || filter.matches(packageName)) {
1488 JSONObject channelCountJson = new JSONObject();
1489 try {
1490 channelCountJson.put("packageName", packageName);
1491 channelCountJson.put("channelCount", channelCount.getValue());
1492 } catch (JSONException e) {
1493 e.printStackTrace();
1494 }
1495 channels.put(channelCountJson);
1496 }
1497 }
1498 return channels;
1499 }
1500
1501 private Map<String, Integer> getPackageChannels() {
1502 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001503 synchronized (mPackagePreferences) {
1504 for (int i = 0; i < mPackagePreferences.size(); i++) {
1505 final PackagePreferences r = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001506 int channelCount = 0;
1507 for (int j = 0; j < r.channels.size(); j++) {
1508 if (!r.channels.valueAt(j).isDeleted()) {
1509 channelCount++;
1510 }
1511 }
1512 packageChannels.put(r.pkg, channelCount);
1513 }
1514 }
1515 return packageChannels;
1516 }
1517
Beverly0479cde22018-11-09 11:05:34 -05001518 /**
1519 * Called when user switches
1520 */
1521 public void onUserSwitched(int userId) {
1522 syncChannelsBypassingDnd(userId);
1523 }
1524
1525 /**
1526 * Called when user is unlocked
1527 */
1528 public void onUserUnlocked(int userId) {
1529 syncChannelsBypassingDnd(userId);
1530 }
1531
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001532 public void onUserRemoved(int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001533 synchronized (mPackagePreferences) {
1534 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001535 for (int i = N - 1; i >= 0; i--) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001536 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001537 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001538 mPackagePreferences.removeAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001539 }
1540 }
1541 }
1542 }
1543
1544 protected void onLocaleChanged(Context context, int userId) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001545 synchronized (mPackagePreferences) {
1546 int N = mPackagePreferences.size();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001547 for (int i = 0; i < N; i++) {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001548 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001549 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
1550 if (PackagePreferences.channels.containsKey(
1551 NotificationChannel.DEFAULT_CHANNEL_ID)) {
1552 PackagePreferences.channels.get(
1553 NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1554 context.getResources().getString(
1555 R.string.default_notification_channel_label));
1556 }
1557 }
1558 }
1559 }
1560 }
1561
1562 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1563 int[] uidList) {
1564 if (pkgList == null || pkgList.length == 0) {
1565 return; // nothing to do
1566 }
1567 boolean updated = false;
1568 if (removingPackage) {
1569 // Remove notification settings for uninstalled package
1570 int size = Math.min(pkgList.length, uidList.length);
1571 for (int i = 0; i < size; i++) {
1572 final String pkg = pkgList[i];
1573 final int uid = uidList[i];
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001574 synchronized (mPackagePreferences) {
1575 mPackagePreferences.remove(packagePreferencesKey(pkg, uid));
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001576 }
1577 mRestoredWithoutUids.remove(pkg);
1578 updated = true;
1579 }
1580 } else {
1581 for (String pkg : pkgList) {
1582 // Package install
1583 final PackagePreferences r = mRestoredWithoutUids.get(pkg);
1584 if (r != null) {
1585 try {
1586 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1587 mRestoredWithoutUids.remove(pkg);
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001588 synchronized (mPackagePreferences) {
1589 mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001590 }
1591 updated = true;
1592 } catch (PackageManager.NameNotFoundException e) {
1593 // noop
1594 }
1595 }
1596 // Package upgrade
1597 try {
Julia Reynoldsb24c62c2018-09-10 10:05:15 -04001598 synchronized (mPackagePreferences) {
1599 PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
1600 mPm.getPackageUidAsUser(pkg, changeUserId));
1601 if (fullPackagePreferences != null) {
1602 createDefaultChannelIfNeeded(fullPackagePreferences);
1603 deleteDefaultChannelIfNeeded(fullPackagePreferences);
1604 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001605 }
1606 } catch (PackageManager.NameNotFoundException e) {
1607 }
1608 }
1609 }
1610
1611 if (updated) {
1612 updateConfig();
1613 }
1614 }
1615
1616 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1617 return new LogMaker(
1618 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1619 .ACTION_NOTIFICATION_CHANNEL)
1620 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1621 .setPackageName(pkg)
1622 .addTaggedData(
1623 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1624 .FIELD_NOTIFICATION_CHANNEL_ID,
1625 channel.getId())
1626 .addTaggedData(
1627 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1628 .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1629 channel.getImportance());
1630 }
1631
1632 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1633 return new LogMaker(
1634 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1635 .ACTION_NOTIFICATION_CHANNEL_GROUP)
1636 .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
1637 .addTaggedData(
1638 com.android.internal.logging.nano.MetricsProto.MetricsEvent
1639 .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1640 groupId)
1641 .setPackageName(pkg);
1642 }
1643
Julia Reynolds4509ce72019-01-31 13:12:43 -05001644 public void updateBubblesEnabled() {
1645 if (mBubblesEnabled == null) {
1646 mBubblesEnabled = new SparseBooleanArray();
1647 }
1648 boolean changed = false;
1649 // update the cached values
1650 for (int index = 0; index < mBubblesEnabled.size(); index++) {
1651 int userId = mBubblesEnabled.keyAt(index);
1652 final boolean oldValue = mBubblesEnabled.get(userId);
1653 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1654 Settings.Secure.NOTIFICATION_BUBBLES,
1655 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0;
1656 mBubblesEnabled.put(userId, newValue);
1657 changed |= oldValue != newValue;
1658 }
1659 if (changed) {
1660 updateConfig();
1661 }
1662 }
1663
1664 public boolean bubblesEnabled(UserHandle userHandle) {
1665 int userId = userHandle.getIdentifier();
1666 if (userId == UserHandle.USER_ALL) {
1667 return false;
1668 }
1669 if (mBubblesEnabled.indexOfKey(userId) < 0) {
1670 mBubblesEnabled.put(userId,
1671 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1672 Settings.Secure.NOTIFICATION_BUBBLES,
1673 DEFAULT_ALLOW_BUBBLE ? 1 : 0, userId) != 0);
1674 }
1675 return mBubblesEnabled.get(userId, DEFAULT_ALLOW_BUBBLE);
1676 }
1677
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001678
1679 public void updateBadgingEnabled() {
1680 if (mBadgingEnabled == null) {
1681 mBadgingEnabled = new SparseBooleanArray();
1682 }
1683 boolean changed = false;
1684 // update the cached values
1685 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1686 int userId = mBadgingEnabled.keyAt(index);
1687 final boolean oldValue = mBadgingEnabled.get(userId);
1688 final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
1689 Settings.Secure.NOTIFICATION_BADGING,
1690 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1691 mBadgingEnabled.put(userId, newValue);
1692 changed |= oldValue != newValue;
1693 }
1694 if (changed) {
1695 updateConfig();
1696 }
1697 }
1698
1699 public boolean badgingEnabled(UserHandle userHandle) {
1700 int userId = userHandle.getIdentifier();
1701 if (userId == UserHandle.USER_ALL) {
1702 return false;
1703 }
1704 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1705 mBadgingEnabled.put(userId,
1706 Settings.Secure.getIntForUser(mContext.getContentResolver(),
1707 Settings.Secure.NOTIFICATION_BADGING,
1708 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1709 }
1710 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1711 }
1712
1713 private void updateConfig() {
1714 mRankingHandler.requestSort();
1715 }
1716
1717 private static String packagePreferencesKey(String pkg, int uid) {
1718 return pkg + "|" + uid;
1719 }
1720
1721 private static class PackagePreferences {
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001722 String pkg;
1723 int uid = UNKNOWN_UID;
1724 int importance = DEFAULT_IMPORTANCE;
1725 int priority = DEFAULT_PRIORITY;
1726 int visibility = DEFAULT_VISIBILITY;
1727 boolean showBadge = DEFAULT_SHOW_BADGE;
Mady Mellorc39b4ae2019-01-09 17:11:37 -08001728 boolean allowBubble = DEFAULT_ALLOW_BUBBLE;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001729 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynolds413ba842019-01-11 10:38:08 -05001730 boolean oemLockedImportance = DEFAULT_OEM_LOCKED_IMPORTANCE;
1731 List<String> futureOemLockedChannels = new ArrayList<>();
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001732
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001733 Delegate delegate = null;
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001734 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
1735 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynoldsa7ba45a2018-08-29 09:07:52 -04001736
1737 public boolean isValidDelegate(String pkg, int uid) {
1738 return delegate != null && delegate.isAllowed(pkg, uid);
1739 }
1740 }
1741
1742 private static class Delegate {
1743 static final boolean DEFAULT_ENABLED = true;
1744 static final boolean DEFAULT_USER_ALLOWED = true;
1745 String mPkg;
1746 int mUid = UNKNOWN_UID;
1747 boolean mEnabled = DEFAULT_ENABLED;
1748 boolean mUserAllowed = DEFAULT_USER_ALLOWED;
1749
1750 Delegate(String pkg, int uid, boolean enabled, boolean userAllowed) {
1751 mPkg = pkg;
1752 mUid = uid;
1753 mEnabled = enabled;
1754 mUserAllowed = userAllowed;
1755 }
1756
1757 public boolean isAllowed(String pkg, int uid) {
1758 if (pkg == null || uid == UNKNOWN_UID) {
1759 return false;
1760 }
1761 return pkg.equals(mPkg)
1762 && uid == mUid
1763 && (mUserAllowed && mEnabled);
1764 }
Aaron Heuckrothe5bec152018-07-09 16:26:09 -04001765 }
1766}