blob: da6e9c02c96a58d653874e200de4aed76aa79e09 [file] [log] [blame]
Chris Wren54bbef42014-07-09 18:37:56 -04001/**
2 * Copyright (c) 2014, 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 */
16package com.android.server.notification;
17
Julia Reynoldsf2e499d2018-03-30 10:36:42 -040018import static android.app.NotificationManager.IMPORTANCE_NONE;
19
Rohan Shah590e1b22018-04-10 23:48:47 -040020import android.annotation.IntDef;
Kweku Adams887f09c2017-11-13 17:12:20 -080021import android.annotation.NonNull;
Chris Wren54bbef42014-07-09 18:37:56 -040022import android.app.Notification;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040023import android.app.NotificationChannel;
Julia Reynolds59e152e2017-01-25 17:42:53 -050024import android.app.NotificationChannelGroup;
Julia Reynolds85769912016-10-25 09:08:57 -040025import android.app.NotificationManager;
Chris Wren54bbef42014-07-09 18:37:56 -040026import android.content.Context;
Julia Reynolds85769912016-10-25 09:08:57 -040027import android.content.pm.ApplicationInfo;
John Spurlock35ef0a62015-05-28 11:24:10 -040028import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040030import android.content.pm.ParceledListSlice;
Julia Reynoldsd373d782017-03-03 13:32:57 -050031import android.metrics.LogMaker;
Julia Reynolds85769912016-10-25 09:08:57 -040032import android.os.Build;
Chris Wren54bbef42014-07-09 18:37:56 -040033import android.os.UserHandle;
Chris Wren89aa2262017-05-05 18:05:56 -040034import android.provider.Settings.Secure;
Julia Reynolds5d25ee72015-11-20 15:38:20 -050035import android.service.notification.NotificationListenerService.Ranking;
Kweku Adams62b42242017-09-25 12:54:02 -070036import android.service.notification.RankingHelperProto;
37import android.service.notification.RankingHelperProto.RecordProto;
Chris Wren54bbef42014-07-09 18:37:56 -040038import android.text.TextUtils;
39import android.util.ArrayMap;
Chris Wren54bbef42014-07-09 18:37:56 -040040import android.util.Slog;
Chris Wren89aa2262017-05-05 18:05:56 -040041import android.util.SparseBooleanArray;
Kweku Adams62b42242017-09-25 12:54:02 -070042import android.util.proto.ProtoOutputStream;
John Spurlock1d881a12015-03-18 19:21:54 -040043
Beverly86d076f2018-04-17 14:44:52 -040044import com.android.internal.R;
45import com.android.internal.annotations.VisibleForTesting;
46import com.android.internal.logging.MetricsLogger;
47import com.android.internal.logging.nano.MetricsProto;
48import com.android.internal.util.Preconditions;
49import com.android.internal.util.XmlUtils;
50
Chris Wrenacf424a2016-03-15 12:48:55 -040051import org.json.JSONArray;
52import org.json.JSONException;
53import org.json.JSONObject;
Chris Wren54bbef42014-07-09 18:37:56 -040054import org.xmlpull.v1.XmlPullParser;
55import org.xmlpull.v1.XmlPullParserException;
56import org.xmlpull.v1.XmlSerializer;
57
58import java.io.IOException;
59import java.io.PrintWriter;
60import java.util.ArrayList;
Julia Reynoldse0b25742017-05-08 12:55:24 -040061import java.util.Arrays;
Julia Reynoldsf02562a2017-01-26 13:33:56 -050062import java.util.Collection;
Chris Wren54bbef42014-07-09 18:37:56 -040063import java.util.Collections;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040064import java.util.List;
Chris Wrenacf424a2016-03-15 12:48:55 -040065import java.util.Map;
66import java.util.Map.Entry;
Julia Reynoldse0b25742017-05-08 12:55:24 -040067import java.util.Objects;
Beverly86d076f2018-04-17 14:44:52 -040068import java.util.concurrent.ConcurrentHashMap;
Chris Wren54bbef42014-07-09 18:37:56 -040069
70public class RankingHelper implements RankingConfig {
71 private static final String TAG = "RankingHelper";
Chris Wren54bbef42014-07-09 18:37:56 -040072
73 private static final int XML_VERSION = 1;
74
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040075 static final String TAG_RANKING = "ranking";
Chris Wren54bbef42014-07-09 18:37:56 -040076 private static final String TAG_PACKAGE = "package";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040077 private static final String TAG_CHANNEL = "channel";
Julia Reynolds59e152e2017-01-25 17:42:53 -050078 private static final String TAG_GROUP = "channelGroup";
Chris Wren54bbef42014-07-09 18:37:56 -040079
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040080 private static final String ATT_VERSION = "version";
Chris Wren54bbef42014-07-09 18:37:56 -040081 private static final String ATT_NAME = "name";
82 private static final String ATT_UID = "uid";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040083 private static final String ATT_ID = "id";
Chris Wren54bbef42014-07-09 18:37:56 -040084 private static final String ATT_PRIORITY = "priority";
Chris Wren3ad4e3a2014-09-02 17:23:51 -040085 private static final String ATT_VISIBILITY = "visibility";
Julia Reynolds5d25ee72015-11-20 15:38:20 -050086 private static final String ATT_IMPORTANCE = "importance";
Julia Reynolds924eed12017-01-19 09:52:07 -050087 private static final String ATT_SHOW_BADGE = "show_badge";
Rohan Shah590e1b22018-04-10 23:48:47 -040088 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Chris Wren54bbef42014-07-09 18:37:56 -040089
John Spurlock1d881a12015-03-18 19:21:54 -040090 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
Julia Reynolds85769912016-10-25 09:08:57 -040091 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
92 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds924eed12017-01-19 09:52:07 -050093 private static final boolean DEFAULT_SHOW_BADGE = true;
Rohan Shah590e1b22018-04-10 23:48:47 -040094 /**
95 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
96 * fields.
97 */
98 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
99
100 /**
101 * All user-lockable fields for a given application.
102 */
103 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
104 public @interface LockableAppFields {
105 int USER_LOCKED_IMPORTANCE = 0x00000001;
106 }
John Spurlock1d881a12015-03-18 19:21:54 -0400107
Chris Wren54bbef42014-07-09 18:37:56 -0400108 private final NotificationSignalExtractor[] mSignalExtractors;
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500109 private final NotificationComparator mPreliminaryComparator;
Christoph Studercd4adf82014-08-19 17:50:49 +0200110 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
Chris Wren54bbef42014-07-09 18:37:56 -0400111
John Spurlock1d881a12015-03-18 19:21:54 -0400112 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
113 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
John Spurlock35ef0a62015-05-28 11:24:10 -0400114 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
Chris Wren54bbef42014-07-09 18:37:56 -0400115
116 private final Context mContext;
Chris Wren51017d02015-12-15 15:34:46 -0500117 private final RankingHandler mRankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400118 private final PackageManager mPm;
Chris Wren89aa2262017-05-05 18:05:56 -0400119 private SparseBooleanArray mBadgingEnabled;
Chris Wren54bbef42014-07-09 18:37:56 -0400120
Beverly86d076f2018-04-17 14:44:52 -0400121 private boolean mAreChannelsBypassingDnd;
122 private ZenModeHelper mZenModeHelper;
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400123
Julia Reynolds85769912016-10-25 09:08:57 -0400124 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500125 ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
Chris Wren54bbef42014-07-09 18:37:56 -0400126 mContext = context;
127 mRankingHandler = rankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400128 mPm = pm;
Beverly86d076f2018-04-17 14:44:52 -0400129 mZenModeHelper= zenHelper;
Chris Wren54bbef42014-07-09 18:37:56 -0400130
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500131 mPreliminaryComparator = new NotificationComparator(mContext);
132
Chris Wren89aa2262017-05-05 18:05:56 -0400133 updateBadgingEnabled();
134
Chris Wren54bbef42014-07-09 18:37:56 -0400135 final int N = extractorNames.length;
136 mSignalExtractors = new NotificationSignalExtractor[N];
137 for (int i = 0; i < N; i++) {
138 try {
139 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
140 NotificationSignalExtractor extractor =
141 (NotificationSignalExtractor) extractorClass.newInstance();
Chris Wren5eab2b72015-06-16 13:56:22 -0400142 extractor.initialize(mContext, usageStats);
Chris Wren54bbef42014-07-09 18:37:56 -0400143 extractor.setConfig(this);
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500144 extractor.setZenHelper(zenHelper);
Chris Wren54bbef42014-07-09 18:37:56 -0400145 mSignalExtractors[i] = extractor;
146 } catch (ClassNotFoundException e) {
147 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
148 } catch (InstantiationException e) {
149 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
150 } catch (IllegalAccessException e) {
151 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
152 }
153 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400154
Beverlyc36640f2018-05-21 16:14:15 -0400155 mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
156 NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
Beverly86d076f2018-04-17 14:44:52 -0400157 updateChannelsBypassingDnd();
Chris Wren54bbef42014-07-09 18:37:56 -0400158 }
159
John Spurlock1d881a12015-03-18 19:21:54 -0400160 @SuppressWarnings("unchecked")
John Spurlock2b122f42014-08-27 16:29:47 -0400161 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
162 final int N = mSignalExtractors.length;
163 for (int i = 0; i < N; i++) {
164 final NotificationSignalExtractor extractor = mSignalExtractors[i];
165 if (extractorClass.equals(extractor.getClass())) {
166 return (T) extractor;
167 }
168 }
169 return null;
170 }
171
Chris Wren54bbef42014-07-09 18:37:56 -0400172 public void extractSignals(NotificationRecord r) {
173 final int N = mSignalExtractors.length;
174 for (int i = 0; i < N; i++) {
175 NotificationSignalExtractor extractor = mSignalExtractors[i];
176 try {
177 RankingReconsideration recon = extractor.process(r);
178 if (recon != null) {
Chris Wren51017d02015-12-15 15:34:46 -0500179 mRankingHandler.requestReconsideration(recon);
Chris Wren54bbef42014-07-09 18:37:56 -0400180 }
181 } catch (Throwable t) {
182 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
183 }
184 }
185 }
186
John Spurlock35ef0a62015-05-28 11:24:10 -0400187 public void readXml(XmlPullParser parser, boolean forRestore)
188 throws XmlPullParserException, IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400189 int type = parser.getEventType();
190 if (type != XmlPullParser.START_TAG) return;
191 String tag = parser.getName();
192 if (!TAG_RANKING.equals(tag)) return;
Geoffrey Pitsch6eccf002017-04-05 12:33:59 -0400193 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
194 // at the time of serialization.
John Spurlock35ef0a62015-05-28 11:24:10 -0400195 mRestoredWithoutUids.clear();
Chris Wren54bbef42014-07-09 18:37:56 -0400196 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
197 tag = parser.getName();
198 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
199 return;
200 }
201 if (type == XmlPullParser.START_TAG) {
202 if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400203 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
Chris Wren54bbef42014-07-09 18:37:56 -0400204 String name = parser.getAttributeValue(null, ATT_NAME);
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400205 if (!TextUtils.isEmpty(name)) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400206 if (forRestore) {
207 try {
Xiaohui Chenddbe4ca2015-08-13 16:20:56 -0700208 //TODO: http://b/22388012
Julia Reynolds85769912016-10-25 09:08:57 -0400209 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
John Spurlock35ef0a62015-05-28 11:24:10 -0400210 } catch (NameNotFoundException e) {
211 // noop
212 }
213 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400214
Julia Reynolds85769912016-10-25 09:08:57 -0400215 Record r = getOrCreateRecord(name, uid,
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400216 XmlUtils.readIntAttribute(
217 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
218 XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
219 XmlUtils.readIntAttribute(
220 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
221 XmlUtils.readBooleanAttribute(
222 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
223 r.importance = XmlUtils.readIntAttribute(
224 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
225 r.priority = XmlUtils.readIntAttribute(
226 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
227 r.visibility = XmlUtils.readIntAttribute(
228 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
229 r.showBadge = XmlUtils.readBooleanAttribute(
230 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
Rohan Shah590e1b22018-04-10 23:48:47 -0400231 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
232 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
Julia Reynolds85769912016-10-25 09:08:57 -0400233
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400234 final int innerDepth = parser.getDepth();
235 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
236 && (type != XmlPullParser.END_TAG
237 || parser.getDepth() > innerDepth)) {
238 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
239 continue;
240 }
241
242 String tagName = parser.getName();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500243 // Channel groups
244 if (TAG_GROUP.equals(tagName)) {
245 String id = parser.getAttributeValue(null, ATT_ID);
246 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
247 if (!TextUtils.isEmpty(id)) {
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400248 NotificationChannelGroup group
249 = new NotificationChannelGroup(id, groupName);
Julia Reynolds005c8b92017-08-24 10:35:53 -0400250 group.populateFromXml(parser);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500251 r.groups.put(id, group);
252 }
253 }
254 // Channels
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400255 if (TAG_CHANNEL.equals(tagName)) {
256 String id = parser.getAttributeValue(null, ATT_ID);
Julia Reynolds2c891c92017-03-17 14:23:47 -0400257 String channelName = parser.getAttributeValue(null, ATT_NAME);
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400258 int channelImportance = XmlUtils.readIntAttribute(
Julia Reynoldsb852e562017-06-06 16:14:18 -0400259 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400260 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
261 NotificationChannel channel = new NotificationChannel(id,
262 channelName, channelImportance);
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100263 if (forRestore) {
264 channel.populateFromXmlForRestore(parser, mContext);
265 } else {
266 channel.populateFromXml(parser);
267 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400268 r.channels.put(id, channel);
269 }
270 }
271 }
Julia Reynolds85769912016-10-25 09:08:57 -0400272
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500273 try {
274 deleteDefaultChannelIfNeeded(r);
275 } catch (NameNotFoundException e) {
276 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
277 }
Chris Wren54bbef42014-07-09 18:37:56 -0400278 }
279 }
280 }
281 }
282 throw new IllegalStateException("Failed to reach END_DOCUMENT");
283 }
284
John Spurlock1d881a12015-03-18 19:21:54 -0400285 private static String recordKey(String pkg, int uid) {
286 return pkg + "|" + uid;
287 }
288
Julia Reynolds85769912016-10-25 09:08:57 -0400289 private Record getRecord(String pkg, int uid) {
John Spurlock1d881a12015-03-18 19:21:54 -0400290 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400291 synchronized (mRecords) {
292 return mRecords.get(key);
293 }
Julia Reynolds85769912016-10-25 09:08:57 -0400294 }
295
296 private Record getOrCreateRecord(String pkg, int uid) {
297 return getOrCreateRecord(pkg, uid,
Julia Reynolds924eed12017-01-19 09:52:07 -0500298 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400299 }
300
301 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
Julia Reynolds924eed12017-01-19 09:52:07 -0500302 int visibility, boolean showBadge) {
Julia Reynolds85769912016-10-25 09:08:57 -0400303 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400304 synchronized (mRecords) {
305 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
306 key);
307 if (r == null) {
308 r = new Record();
309 r.pkg = pkg;
310 r.uid = uid;
311 r.importance = importance;
312 r.priority = priority;
313 r.visibility = visibility;
314 r.showBadge = showBadge;
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500315
Julia Reynolds44011962017-04-14 12:29:04 -0400316 try {
317 createDefaultChannelIfNeeded(r);
318 } catch (NameNotFoundException e) {
319 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
320 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500321
Julia Reynolds44011962017-04-14 12:29:04 -0400322 if (r.uid == Record.UNKNOWN_UID) {
323 mRestoredWithoutUids.put(pkg, r);
324 } else {
325 mRecords.put(key, r);
326 }
Julia Reynolds85769912016-10-25 09:08:57 -0400327 }
Julia Reynolds44011962017-04-14 12:29:04 -0400328 return r;
John Spurlock1d881a12015-03-18 19:21:54 -0400329 }
John Spurlock1d881a12015-03-18 19:21:54 -0400330 }
331
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500332 private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
333 final int userId = UserHandle.getUserId(r.uid);
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000334 final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
Dan Sandlere103b782017-05-17 16:07:56 -0700335 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400336 // O apps should not have the default channel.
337 return false;
Julia Reynolds85769912016-10-25 09:08:57 -0400338 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500339
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400340 // Otherwise, this app should have the default channel.
341 return true;
Julia Reynolds85769912016-10-25 09:08:57 -0400342 }
343
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500344 private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
Julia Reynolds85769912016-10-25 09:08:57 -0400345 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500346 // Not present
347 return;
Julia Reynolds85769912016-10-25 09:08:57 -0400348 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500349
350 if (shouldHaveDefaultChannel(r)) {
351 // Keep the default channel until upgraded.
352 return;
353 }
354
355 // Remove Default Channel.
356 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
357 }
358
359 private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
360 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Julia Reynolds17717f52017-05-09 11:46:06 -0400361 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
362 mContext.getString(R.string.default_notification_channel_label));
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500363 return;
364 }
365
366 if (!shouldHaveDefaultChannel(r)) {
367 // Keep the default channel until upgraded.
368 return;
369 }
370
371 // Create Default Channel
372 NotificationChannel channel;
373 channel = new NotificationChannel(
374 NotificationChannel.DEFAULT_CHANNEL_ID,
375 mContext.getString(R.string.default_notification_channel_label),
376 r.importance);
377 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
378 channel.setLockscreenVisibility(r.visibility);
379 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
380 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
381 }
382 if (r.priority != DEFAULT_PRIORITY) {
383 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
384 }
385 if (r.visibility != DEFAULT_VISIBILITY) {
386 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
387 }
388 r.channels.put(channel.getId(), channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400389 }
390
John Spurlock35ef0a62015-05-28 11:24:10 -0400391 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400392 out.startTag(null, TAG_RANKING);
393 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
394
Julia Reynolds44011962017-04-14 12:29:04 -0400395 synchronized (mRecords) {
396 final int N = mRecords.size();
397 for (int i = 0; i < N; i++) {
398 final Record r = mRecords.valueAt(i);
399 //TODO: http://b/22388012
400 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
401 continue;
Julia Reynoldsef37f282016-02-12 09:11:27 -0500402 }
Rohan Shah590e1b22018-04-10 23:48:47 -0400403 final boolean hasNonDefaultSettings =
404 r.importance != DEFAULT_IMPORTANCE
405 || r.priority != DEFAULT_PRIORITY
406 || r.visibility != DEFAULT_VISIBILITY
407 || r.showBadge != DEFAULT_SHOW_BADGE
408 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
409 || r.channels.size() > 0
410 || r.groups.size() > 0;
Julia Reynolds44011962017-04-14 12:29:04 -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));
Julia Reynoldsd0a5b162017-03-18 13:42:09 -0400416 }
Julia Reynolds44011962017-04-14 12:29:04 -0400417 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 }
423 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
Rohan Shah590e1b22018-04-10 23:48:47 -0400424 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
425 Integer.toString(r.lockedAppFields));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400426
Julia Reynolds44011962017-04-14 12:29:04 -0400427 if (!forBackup) {
428 out.attribute(null, ATT_UID, Integer.toString(r.uid));
429 }
430
431 for (NotificationChannelGroup group : r.groups.values()) {
432 group.writeXml(out);
433 }
434
435 for (NotificationChannel channel : r.channels.values()) {
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100436 if (forBackup) {
437 if (!channel.isDeleted()) {
438 channel.writeXmlForBackup(out, mContext);
439 }
440 } else {
Julia Reynolds44011962017-04-14 12:29:04 -0400441 channel.writeXml(out);
442 }
443 }
444
445 out.endTag(null, TAG_PACKAGE);
446 }
Julia Reynoldsef37f282016-02-12 09:11:27 -0500447 }
Chris Wren54bbef42014-07-09 18:37:56 -0400448 }
449 out.endTag(null, TAG_RANKING);
450 }
451
452 private void updateConfig() {
453 final int N = mSignalExtractors.length;
454 for (int i = 0; i < N; i++) {
455 mSignalExtractors[i].setConfig(this);
456 }
Julia Reynoldseb3dca72017-07-11 10:39:58 -0400457 mRankingHandler.requestSort();
Chris Wren54bbef42014-07-09 18:37:56 -0400458 }
459
460 public void sort(ArrayList<NotificationRecord> notificationList) {
Chris Wren1031c972014-07-23 13:11:45 +0000461 final int N = notificationList.size();
Christoph Studercd4adf82014-08-19 17:50:49 +0200462 // clear global sort keys
Chris Wren1031c972014-07-23 13:11:45 +0000463 for (int i = N - 1; i >= 0; i--) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200464 notificationList.get(i).setGlobalSortKey(null);
Chris Wren1031c972014-07-23 13:11:45 +0000465 }
466
Christoph Studer85374052014-10-08 11:19:55 -0700467 // rank each record individually
468 Collections.sort(notificationList, mPreliminaryComparator);
Christoph Studercd4adf82014-08-19 17:50:49 +0200469
470 synchronized (mProxyByGroupTmp) {
471 // record individual ranking result and nominate proxies for each group
472 for (int i = N - 1; i >= 0; i--) {
473 final NotificationRecord record = notificationList.get(i);
474 record.setAuthoritativeRank(i);
475 final String groupKey = record.getGroupKey();
Julia Reynolds3fb989b2017-02-28 16:06:55 -0500476 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
Julia Reynolds51710712017-07-19 13:48:07 -0400477 if (existingProxy == null) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200478 mProxyByGroupTmp.put(groupKey, record);
479 }
480 }
481 // assign global sort key:
482 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
483 for (int i = 0; i < N; i++) {
484 final NotificationRecord record = notificationList.get(i);
485 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
486 String groupSortKey = record.getNotification().getSortKey();
487
488 // We need to make sure the developer provided group sort key (gsk) is handled
489 // correctly:
490 // gsk="" < gsk=non-null-string < gsk=null
491 //
492 // We enforce this by using different prefixes for these three cases.
493 String groupSortKeyPortion;
494 if (groupSortKey == null) {
495 groupSortKeyPortion = "nsk";
496 } else if (groupSortKey.equals("")) {
497 groupSortKeyPortion = "esk";
498 } else {
499 groupSortKeyPortion = "gsk=" + groupSortKey;
500 }
501
502 boolean isGroupSummary = record.getNotification().isGroupSummary();
503 record.setGlobalSortKey(
504 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
Selim Cinek55a3e732017-05-25 18:30:10 -0700505 record.isRecentlyIntrusive()
506 && record.getImportance() > NotificationManager.IMPORTANCE_MIN
507 ? '0' : '1',
Christoph Studercd4adf82014-08-19 17:50:49 +0200508 groupProxy.getAuthoritativeRank(),
509 isGroupSummary ? '0' : '1',
510 groupSortKeyPortion,
511 record.getAuthoritativeRank()));
512 }
513 mProxyByGroupTmp.clear();
Chris Wren1031c972014-07-23 13:11:45 +0000514 }
Christoph Studercd4adf82014-08-19 17:50:49 +0200515
Chris Wren1031c972014-07-23 13:11:45 +0000516 // Do a second ranking pass, using group proxies
517 Collections.sort(notificationList, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400518 }
519
520 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
Chris Wren1031c972014-07-23 13:11:45 +0000521 return Collections.binarySearch(notificationList, target, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400522 }
523
Julia Reynoldsef37f282016-02-12 09:11:27 -0500524 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -0500525 * Gets importance.
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500526 */
527 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -0500528 public int getImportance(String packageName, int uid) {
529 return getOrCreateRecord(packageName, uid).importance;
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500530 }
531
Rohan Shah590e1b22018-04-10 23:48:47 -0400532
533 /**
534 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
535 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
536 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
537 */
538 public boolean getIsAppImportanceLocked(String packageName, int uid) {
539 int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
540 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
541 }
542
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400543 @Override
Julia Reynolds924eed12017-01-19 09:52:07 -0500544 public boolean canShowBadge(String packageName, int uid) {
545 return getOrCreateRecord(packageName, uid).showBadge;
546 }
547
548 @Override
549 public void setShowBadge(String packageName, int uid, boolean showBadge) {
550 getOrCreateRecord(packageName, uid).showBadge = showBadge;
551 updateConfig();
552 }
553
Julia Reynolds005c8b92017-08-24 10:35:53 -0400554 @Override
555 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
556 if (groupId == null) {
557 return false;
558 }
559 Record r = getOrCreateRecord(packageName, uid);
560 NotificationChannelGroup group = r.groups.get(groupId);
561 if (group == null) {
562 return false;
563 }
564 return group.isBlocked();
565 }
566
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400567 int getPackagePriority(String pkg, int uid) {
568 return getOrCreateRecord(pkg, uid).priority;
569 }
570
571 int getPackageVisibility(String pkg, int uid) {
572 return getOrCreateRecord(pkg, uid).visibility;
573 }
574
Julia Reynolds924eed12017-01-19 09:52:07 -0500575 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500576 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
577 boolean fromTargetApp) {
578 Preconditions.checkNotNull(pkg);
579 Preconditions.checkNotNull(group);
580 Preconditions.checkNotNull(group.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400581 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500582 Record r = getOrCreateRecord(pkg, uid);
583 if (r == null) {
584 throw new IllegalArgumentException("Invalid package");
585 }
Dan Sandler0171f572017-06-14 14:03:18 -0400586 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
587 if (!group.equals(oldGroup)) {
Julia Reynolds005c8b92017-08-24 10:35:53 -0400588 // will log for new entries as well as name/description changes
Dan Sandler0171f572017-06-14 14:03:18 -0400589 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
590 }
Julia Reynolds005c8b92017-08-24 10:35:53 -0400591 if (oldGroup != null) {
592 group.setChannels(oldGroup.getChannels());
593
594 if (fromTargetApp) {
595 group.setBlocked(oldGroup.isBlocked());
596 }
597 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500598 r.groups.put(group.getId(), group);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500599 }
600
601 @Override
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500602 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
Julia Reynolds1fe10942018-03-28 12:46:51 -0400603 boolean fromTargetApp, boolean hasDndAccess) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500604 Preconditions.checkNotNull(pkg);
605 Preconditions.checkNotNull(channel);
606 Preconditions.checkNotNull(channel.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400607 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400608 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500609 if (r == null) {
610 throw new IllegalArgumentException("Invalid package");
611 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500612 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
613 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
614 }
Julia Reynolds03fa85d2017-03-06 15:14:50 -0500615 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
616 throw new IllegalArgumentException("Reserved id");
617 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500618 NotificationChannel existing = r.channels.get(channel.getId());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400619 // Keep most of the existing settings
Julia Reynolds3d91f112017-03-03 08:59:53 -0500620 if (existing != null && fromTargetApp) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500621 if (existing.isDeleted()) {
622 existing.setDeleted(false);
Dan Sandler0171f572017-06-14 14:03:18 -0400623
624 // log a resurrected channel as if it's new again
625 MetricsLogger.action(getChannelLog(channel, pkg).setType(
626 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500627 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500628
Julia Reynolds2c891c92017-03-17 14:23:47 -0400629 existing.setName(channel.getName().toString());
630 existing.setDescription(channel.getDescription());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400631 existing.setBlockableSystem(channel.isBlockableSystem());
Julia Reynolds005c8b92017-08-24 10:35:53 -0400632 if (existing.getGroup() == null) {
633 existing.setGroup(channel.getGroup());
634 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500635
Geoffrey Pitsch76a3aa02017-07-26 15:07:34 -0400636 // Apps are allowed to downgrade channel importance if the user has not changed any
637 // fields on this channel yet.
638 if (existing.getUserLockedFields() == 0 &&
639 channel.getImportance() < existing.getImportance()) {
640 existing.setImportance(channel.getImportance());
641 }
642
Julia Reynolds1fe10942018-03-28 12:46:51 -0400643 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
644 // fields on the channel yet
Julia Reynolds7f6681d2018-05-01 12:28:56 -0400645 if (existing.getUserLockedFields() == 0 && hasDndAccess) {
Beverly86d076f2018-04-17 14:44:52 -0400646 boolean bypassDnd = channel.canBypassDnd();
647 existing.setBypassDnd(bypassDnd);
648
649 if (bypassDnd != mAreChannelsBypassingDnd) {
650 updateChannelsBypassingDnd();
651 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400652 }
653
Julia Reynoldsd373d782017-03-03 13:32:57 -0500654 updateConfig();
Geoffrey Pitsch03533712017-01-05 10:30:07 -0500655 return;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400656 }
Julia Reynoldsf2e499d2018-03-30 10:36:42 -0400657 if (channel.getImportance() < IMPORTANCE_NONE
Julia Reynolds85769912016-10-25 09:08:57 -0400658 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
659 throw new IllegalArgumentException("Invalid importance level");
660 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400661
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500662 // Reset fields that apps aren't allowed to set.
Julia Reynolds7f6681d2018-05-01 12:28:56 -0400663 if (fromTargetApp && !hasDndAccess) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500664 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400665 }
666 if (fromTargetApp) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500667 channel.setLockscreenVisibility(r.visibility);
668 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500669 clearLockedFields(channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400670 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
671 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
672 }
Julia Reynolds924eed12017-01-19 09:52:07 -0500673 if (!r.showBadge) {
674 channel.setShowBadge(false);
675 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400676
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400677 r.channels.put(channel.getId(), channel);
Beverly86d076f2018-04-17 14:44:52 -0400678 if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
679 updateChannelsBypassingDnd();
680 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500681 MetricsLogger.action(getChannelLog(channel, pkg).setType(
682 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400683 }
684
Julia Reynoldse0b25742017-05-08 12:55:24 -0400685 void clearLockedFields(NotificationChannel channel) {
686 channel.unlockFields(channel.getUserLockedFields());
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500687 }
688
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400689 @Override
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400690 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
691 boolean fromUser) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500692 Preconditions.checkNotNull(updatedChannel);
693 Preconditions.checkNotNull(updatedChannel.getId());
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400694 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500695 if (r == null) {
696 throw new IllegalArgumentException("Invalid package");
697 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400698 NotificationChannel channel = r.channels.get(updatedChannel.getId());
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500699 if (channel == null || channel.isDeleted()) {
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400700 throw new IllegalArgumentException("Channel does not exist");
701 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400702 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
703 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
704 }
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500705 if (!fromUser) {
706 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
707 }
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400708 if (fromUser) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500709 updatedChannel.lockFields(channel.getUserLockedFields());
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400710 lockFieldsForUpdate(channel, updatedChannel);
711 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400712 r.channels.put(updatedChannel.getId(), updatedChannel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500713
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400714 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
715 // copy settings to app level so they are inherited by new channels
716 // when the app migrates
717 r.importance = updatedChannel.getImportance();
718 r.priority = updatedChannel.canBypassDnd()
719 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
720 r.visibility = updatedChannel.getLockscreenVisibility();
721 r.showBadge = updatedChannel.canShowBadge();
722 }
723
Dan Sandler0171f572017-06-14 14:03:18 -0400724 if (!channel.equals(updatedChannel)) {
725 // only log if there are real changes
726 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
727 }
Beverly86d076f2018-04-17 14:44:52 -0400728
729 if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
730 updateChannelsBypassingDnd();
731 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400732 updateConfig();
733 }
734
735 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500736 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
737 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500738 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400739 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500740 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500741 return null;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500742 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400743 if (channelId == null) {
744 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
745 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500746 final NotificationChannel nc = r.channels.get(channelId);
747 if (nc != null && (includeDeleted || !nc.isDeleted())) {
748 return nc;
749 }
750 return null;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400751 }
752
753 @Override
754 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
Julia Reynolds85769912016-10-25 09:08:57 -0400755 Record r = getRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500756 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500757 return;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500758 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500759 NotificationChannel channel = r.channels.get(channelId);
760 if (channel != null) {
761 channel.setDeleted(true);
Julia Reynolds8e0eb372017-03-21 15:04:50 -0400762 LogMaker lm = getChannelLog(channel, pkg);
763 lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
764 MetricsLogger.action(lm);
Beverly86d076f2018-04-17 14:44:52 -0400765
766 if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
767 updateChannelsBypassingDnd();
768 }
Julia Reynolds85769912016-10-25 09:08:57 -0400769 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400770 }
771
772 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500773 @VisibleForTesting
774 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
775 Preconditions.checkNotNull(pkg);
776 Preconditions.checkNotNull(channelId);
777 Record r = getRecord(pkg, uid);
778 if (r == null) {
779 return;
780 }
781 r.channels.remove(channelId);
782 }
783
784 @Override
785 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
786 Preconditions.checkNotNull(pkg);
787 Record r = getRecord(pkg, uid);
788 if (r == null) {
789 return;
790 }
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000791 int N = r.channels.size() - 1;
792 for (int i = N; i >= 0; i--) {
793 String key = r.channels.keyAt(i);
794 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
795 r.channels.remove(key);
796 }
797 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500798 }
799
Julia Reynolds005c8b92017-08-24 10:35:53 -0400800 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
801 int uid, String groupId, boolean includeDeleted) {
802 Preconditions.checkNotNull(pkg);
803 Record r = getRecord(pkg, uid);
804 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
805 return null;
806 }
807 NotificationChannelGroup group = r.groups.get(groupId).clone();
808 group.setChannels(new ArrayList<>());
809 int N = r.channels.size();
810 for (int i = 0; i < N; i++) {
811 final NotificationChannel nc = r.channels.valueAt(i);
812 if (includeDeleted || !nc.isDeleted()) {
813 if (groupId.equals(nc.getGroup())) {
814 group.addChannel(nc);
815 }
816 }
817 }
818 return group;
819 }
820
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500821 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
822 int uid) {
823 Preconditions.checkNotNull(pkg);
824 Record r = getRecord(pkg, uid);
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500825 if (r == null) {
826 return null;
827 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500828 return r.groups.get(groupId);
829 }
830
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500831 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500832 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds173a4822018-09-21 15:20:13 -0400833 int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty) {
Julia Reynolds59e152e2017-01-25 17:42:53 -0500834 Preconditions.checkNotNull(pkg);
Julia Reynolds74856c42017-02-08 14:47:23 -0500835 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500836 Record r = getRecord(pkg, uid);
837 if (r == null) {
838 return ParceledListSlice.emptyList();
839 }
840 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
841 int N = r.channels.size();
842 for (int i = 0; i < N; i++) {
843 final NotificationChannel nc = r.channels.valueAt(i);
844 if (includeDeleted || !nc.isDeleted()) {
845 if (nc.getGroup() != null) {
Julia Reynolds9bfba592017-03-15 14:03:55 -0400846 if (r.groups.get(nc.getGroup()) != null) {
847 NotificationChannelGroup ncg = groups.get(nc.getGroup());
848 if (ncg == null) {
849 ncg = r.groups.get(nc.getGroup()).clone();
Julia Reynolds005c8b92017-08-24 10:35:53 -0400850 ncg.setChannels(new ArrayList<>());
Julia Reynolds9bfba592017-03-15 14:03:55 -0400851 groups.put(nc.getGroup(), ncg);
852
853 }
854 ncg.addChannel(nc);
Julia Reynolds74856c42017-02-08 14:47:23 -0500855 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500856 } else {
857 nonGrouped.addChannel(nc);
858 }
859 }
860 }
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500861 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
Julia Reynolds74856c42017-02-08 14:47:23 -0500862 groups.put(null, nonGrouped);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500863 }
Julia Reynolds173a4822018-09-21 15:20:13 -0400864 if (includeEmpty) {
865 for (NotificationChannelGroup group : r.groups.values()) {
866 if (!groups.containsKey(group.getId())) {
867 groups.put(group.getId(), group);
868 }
869 }
870 }
Julia Reynolds74856c42017-02-08 14:47:23 -0500871 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500872 }
873
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400874 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
Julia Reynolds9bfba592017-03-15 14:03:55 -0400875 String groupId) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400876 List<NotificationChannel> deletedChannels = new ArrayList<>();
Julia Reynolds9bfba592017-03-15 14:03:55 -0400877 Record r = getRecord(pkg, uid);
878 if (r == null || TextUtils.isEmpty(groupId)) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400879 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400880 }
881
882 r.groups.remove(groupId);
883
884 int N = r.channels.size();
885 for (int i = 0; i < N; i++) {
886 final NotificationChannel nc = r.channels.valueAt(i);
887 if (groupId.equals(nc.getGroup())) {
888 nc.setDeleted(true);
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400889 deletedChannels.add(nc);
Julia Reynolds9bfba592017-03-15 14:03:55 -0400890 }
891 }
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400892 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400893 }
894
Julia Reynolds59e152e2017-01-25 17:42:53 -0500895 @Override
Julia Reynoldsf02562a2017-01-26 13:33:56 -0500896 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
897 int uid) {
898 Record r = getRecord(pkg, uid);
899 if (r == null) {
900 return new ArrayList<>();
901 }
902 return r.groups.values();
903 }
904
905 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500906 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
907 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500908 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400909 List<NotificationChannel> channels = new ArrayList<>();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500910 Record r = getRecord(pkg, uid);
911 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500912 return ParceledListSlice.emptyList();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500913 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400914 int N = r.channels.size();
915 for (int i = 0; i < N; i++) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500916 final NotificationChannel nc = r.channels.valueAt(i);
917 if (includeDeleted || !nc.isDeleted()) {
918 channels.add(nc);
919 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400920 }
Julia Reynolds85769912016-10-25 09:08:57 -0400921 return new ParceledListSlice<>(channels);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400922 }
923
Julia Reynolds17717f52017-05-09 11:46:06 -0400924 /**
925 * True for pre-O apps that only have the default channel, or pre O apps that have no
926 * channels yet. This method will create the default channel for pre-O apps that don't have it.
927 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
928 * upgrades.
929 */
930 public boolean onlyHasDefaultChannel(String pkg, int uid) {
931 Record r = getOrCreateRecord(pkg, uid);
932 if (r.channels.size() == 1
933 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
934 return true;
935 }
936 return false;
937 }
938
Julia Reynolds41103f42017-03-15 11:36:35 -0400939 public int getDeletedChannelCount(String pkg, int uid) {
940 Preconditions.checkNotNull(pkg);
941 int deletedCount = 0;
942 Record r = getRecord(pkg, uid);
943 if (r == null) {
944 return deletedCount;
945 }
946 int N = r.channels.size();
947 for (int i = 0; i < N; i++) {
948 final NotificationChannel nc = r.channels.valueAt(i);
949 if (nc.isDeleted()) {
950 deletedCount++;
951 }
952 }
953 return deletedCount;
954 }
955
Julia Reynoldsf2e499d2018-03-30 10:36:42 -0400956 public int getBlockedChannelCount(String pkg, int uid) {
957 Preconditions.checkNotNull(pkg);
958 int blockedCount = 0;
959 Record r = getRecord(pkg, uid);
960 if (r == null) {
961 return blockedCount;
962 }
963 int N = r.channels.size();
964 for (int i = 0; i < N; i++) {
965 final NotificationChannel nc = r.channels.valueAt(i);
966 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
967 blockedCount++;
968 }
969 }
970 return blockedCount;
971 }
972
Julia Reynoldse273f082018-04-12 13:48:49 -0400973 public int getBlockedAppCount(int userId) {
974 int count = 0;
975 synchronized (mRecords) {
976 final int N = mRecords.size();
977 for (int i = 0; i < N; i++) {
978 final Record r = mRecords.valueAt(i);
979 if (userId == UserHandle.getUserId(r.uid)
980 && r.importance == IMPORTANCE_NONE) {
981 count++;
982 }
983 }
984 }
985 return count;
986 }
987
Beverly86d076f2018-04-17 14:44:52 -0400988 public void updateChannelsBypassingDnd() {
989 synchronized (mRecords) {
990 final int numRecords = mRecords.size();
991 for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
992 final Record r = mRecords.valueAt(recordIndex);
993 final int numChannels = r.channels.size();
994
995 for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
996 NotificationChannel channel = r.channels.valueAt(channelIndex);
997 if (!channel.isDeleted() && channel.canBypassDnd()) {
998 if (!mAreChannelsBypassingDnd) {
999 mAreChannelsBypassingDnd = true;
1000 updateZenPolicy(true);
1001 }
1002 return;
1003 }
1004 }
1005 }
1006 }
1007
1008 if (mAreChannelsBypassingDnd) {
1009 mAreChannelsBypassingDnd = false;
1010 updateZenPolicy(false);
1011 }
1012 }
1013
1014 public void updateZenPolicy(boolean areChannelsBypassingDnd) {
1015 NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
1016 mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
1017 policy.priorityCategories, policy.priorityCallSenders,
1018 policy.priorityMessageSenders, policy.suppressedVisualEffects,
1019 (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
1020 : 0)));
1021 }
1022
1023 public boolean areChannelsBypassingDnd() {
1024 return mAreChannelsBypassingDnd;
1025 }
1026
Julia Reynolds81afbcd2016-02-09 14:54:08 -05001027 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -05001028 * Sets importance.
Julia Reynolds92d456e2016-01-25 16:36:59 -05001029 */
1030 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -05001031 public void setImportance(String pkgName, int uid, int importance) {
1032 getOrCreateRecord(pkgName, uid).importance = importance;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001033 updateConfig();
1034 }
1035
Chris Wrenacf424a2016-03-15 12:48:55 -04001036 public void setEnabled(String packageName, int uid, boolean enabled) {
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001037 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
Chris Wrenacf424a2016-03-15 12:48:55 -04001038 if (wasEnabled == enabled) {
1039 return;
1040 }
Julia Reynolds85769912016-10-25 09:08:57 -04001041 setImportance(packageName, uid,
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001042 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
Chris Wrenacf424a2016-03-15 12:48:55 -04001043 }
1044
Rohan Shah590e1b22018-04-10 23:48:47 -04001045 /**
1046 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1047 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1048 * considered for sentiment adjustments (and thus never show a blocking helper).
1049 */
1050 public void setAppImportanceLocked(String packageName, int uid) {
1051 Record record = getOrCreateRecord(packageName, uid);
1052 if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1053 return;
1054 }
1055
1056 record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1057 updateConfig();
1058 }
1059
Julia Reynoldse0b25742017-05-08 12:55:24 -04001060 @VisibleForTesting
1061 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
Julia Reynoldse0b25742017-05-08 12:55:24 -04001062 if (original.canBypassDnd() != update.canBypassDnd()) {
1063 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1064 }
1065 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1066 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1067 }
1068 if (original.getImportance() != update.getImportance()) {
1069 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1070 }
1071 if (original.shouldShowLights() != update.shouldShowLights()
1072 || original.getLightColor() != update.getLightColor()) {
1073 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1074 }
1075 if (!Objects.equals(original.getSound(), update.getSound())) {
1076 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1077 }
1078 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1079 || original.shouldVibrate() != update.shouldVibrate()) {
1080 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1081 }
1082 if (original.canShowBadge() != update.canShowBadge()) {
1083 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1084 }
1085 }
1086
Kweku Adams887f09c2017-11-13 17:12:20 -08001087 public void dump(PrintWriter pw, String prefix,
1088 @NonNull NotificationManagerService.DumpFilter filter) {
1089 final int N = mSignalExtractors.length;
1090 pw.print(prefix);
1091 pw.print("mSignalExtractors.length = ");
1092 pw.println(N);
1093 for (int i = 0; i < N; i++) {
Chris Wren54bbef42014-07-09 18:37:56 -04001094 pw.print(prefix);
Kweku Adams887f09c2017-11-13 17:12:20 -08001095 pw.print(" ");
1096 pw.println(mSignalExtractors[i].getClass().getSimpleName());
Chris Wren54bbef42014-07-09 18:37:56 -04001097 }
Kweku Adams887f09c2017-11-13 17:12:20 -08001098
1099 pw.print(prefix);
1100 pw.println("per-package config:");
1101
Julia Reynolds85aa6cb2016-01-08 17:49:11 -05001102 pw.println("Records:");
Julia Reynolds44011962017-04-14 12:29:04 -04001103 synchronized (mRecords) {
1104 dumpRecords(pw, prefix, filter, mRecords);
1105 }
Julia Reynolds85aa6cb2016-01-08 17:49:11 -05001106 pw.println("Restored without uid:");
John Spurlock35ef0a62015-05-28 11:24:10 -04001107 dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
1108 }
1109
Kweku Adams887f09c2017-11-13 17:12:20 -08001110 public void dump(ProtoOutputStream proto,
1111 @NonNull NotificationManagerService.DumpFilter filter) {
Kweku Adams62b42242017-09-25 12:54:02 -07001112 final int N = mSignalExtractors.length;
1113 for (int i = 0; i < N; i++) {
1114 proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
1115 mSignalExtractors[i].getClass().getSimpleName());
1116 }
1117 synchronized (mRecords) {
1118 dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
1119 }
1120 dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1121 mRestoredWithoutUids);
1122 }
1123
1124 private static void dumpRecords(ProtoOutputStream proto, long fieldId,
Kweku Adams887f09c2017-11-13 17:12:20 -08001125 @NonNull NotificationManagerService.DumpFilter filter,
1126 ArrayMap<String, Record> records) {
Kweku Adams62b42242017-09-25 12:54:02 -07001127 final int N = records.size();
1128 long fToken;
1129 for (int i = 0; i < N; i++) {
1130 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -08001131 if (filter.matches(r.pkg)) {
Kweku Adams62b42242017-09-25 12:54:02 -07001132 fToken = proto.start(fieldId);
1133
1134 proto.write(RecordProto.PACKAGE, r.pkg);
1135 proto.write(RecordProto.UID, r.uid);
1136 proto.write(RecordProto.IMPORTANCE, r.importance);
1137 proto.write(RecordProto.PRIORITY, r.priority);
1138 proto.write(RecordProto.VISIBILITY, r.visibility);
1139 proto.write(RecordProto.SHOW_BADGE, r.showBadge);
1140
Kweku Adams62b42242017-09-25 12:54:02 -07001141 for (NotificationChannel channel : r.channels.values()) {
Kweku Adamsbc84aec2018-01-23 13:33:12 -08001142 channel.writeToProto(proto, RecordProto.CHANNELS);
Kweku Adams62b42242017-09-25 12:54:02 -07001143 }
1144 for (NotificationChannelGroup group : r.groups.values()) {
Kweku Adamsbc84aec2018-01-23 13:33:12 -08001145 group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
Kweku Adams62b42242017-09-25 12:54:02 -07001146 }
1147
1148 proto.end(fToken);
1149 }
1150 }
1151 }
1152
John Spurlock35ef0a62015-05-28 11:24:10 -04001153 private static void dumpRecords(PrintWriter pw, String prefix,
Kweku Adams887f09c2017-11-13 17:12:20 -08001154 @NonNull NotificationManagerService.DumpFilter filter,
1155 ArrayMap<String, Record> records) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001156 final int N = records.size();
Chris Wren54bbef42014-07-09 18:37:56 -04001157 for (int i = 0; i < N; i++) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001158 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -08001159 if (filter.matches(r.pkg)) {
John Spurlock1d881a12015-03-18 19:21:54 -04001160 pw.print(prefix);
Julia Reynolds924eed12017-01-19 09:52:07 -05001161 pw.print(" AppSettings: ");
John Spurlock1d881a12015-03-18 19:21:54 -04001162 pw.print(r.pkg);
1163 pw.print(" (");
John Spurlock35ef0a62015-05-28 11:24:10 -04001164 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
John Spurlock1d881a12015-03-18 19:21:54 -04001165 pw.print(')');
Julia Reynolds92d456e2016-01-25 16:36:59 -05001166 if (r.importance != DEFAULT_IMPORTANCE) {
1167 pw.print(" importance=");
1168 pw.print(Ranking.importanceToString(r.importance));
1169 }
1170 if (r.priority != DEFAULT_PRIORITY) {
1171 pw.print(" priority=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001172 pw.print(Notification.priorityToString(r.priority));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001173 }
1174 if (r.visibility != DEFAULT_VISIBILITY) {
1175 pw.print(" visibility=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001176 pw.print(Notification.visibilityToString(r.visibility));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001177 }
Julia Reynolds924eed12017-01-19 09:52:07 -05001178 pw.print(" showBadge=");
1179 pw.print(Boolean.toString(r.showBadge));
John Spurlock1d881a12015-03-18 19:21:54 -04001180 pw.println();
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001181 for (NotificationChannel channel : r.channels.values()) {
1182 pw.print(prefix);
1183 pw.print(" ");
1184 pw.print(" ");
1185 pw.println(channel);
1186 }
Julia Reynolds59e152e2017-01-25 17:42:53 -05001187 for (NotificationChannelGroup group : r.groups.values()) {
1188 pw.print(prefix);
1189 pw.print(" ");
1190 pw.print(" ");
1191 pw.println(group);
1192 }
Chris Wren54bbef42014-07-09 18:37:56 -04001193 }
1194 }
1195 }
John Spurlock1d881a12015-03-18 19:21:54 -04001196
Chris Wrenacf424a2016-03-15 12:48:55 -04001197 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1198 JSONObject ranking = new JSONObject();
1199 JSONArray records = new JSONArray();
1200 try {
1201 ranking.put("noUid", mRestoredWithoutUids.size());
1202 } catch (JSONException e) {
1203 // pass
1204 }
Julia Reynolds44011962017-04-14 12:29:04 -04001205 synchronized (mRecords) {
1206 final int N = mRecords.size();
1207 for (int i = 0; i < N; i++) {
1208 final Record r = mRecords.valueAt(i);
1209 if (filter == null || filter.matches(r.pkg)) {
1210 JSONObject record = new JSONObject();
1211 try {
1212 record.put("userId", UserHandle.getUserId(r.uid));
1213 record.put("packageName", r.pkg);
1214 if (r.importance != DEFAULT_IMPORTANCE) {
1215 record.put("importance", Ranking.importanceToString(r.importance));
1216 }
1217 if (r.priority != DEFAULT_PRIORITY) {
1218 record.put("priority", Notification.priorityToString(r.priority));
1219 }
1220 if (r.visibility != DEFAULT_VISIBILITY) {
1221 record.put("visibility", Notification.visibilityToString(r.visibility));
1222 }
1223 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1224 record.put("showBadge", Boolean.valueOf(r.showBadge));
1225 }
Julia Reynoldsad23a802018-04-19 15:50:31 -04001226 JSONArray channels = new JSONArray();
Julia Reynolds44011962017-04-14 12:29:04 -04001227 for (NotificationChannel channel : r.channels.values()) {
Julia Reynoldsad23a802018-04-19 15:50:31 -04001228 channels.put(channel.toJson());
Julia Reynolds44011962017-04-14 12:29:04 -04001229 }
Julia Reynoldsad23a802018-04-19 15:50:31 -04001230 record.put("channels", channels);
1231 JSONArray groups = new JSONArray();
Julia Reynolds44011962017-04-14 12:29:04 -04001232 for (NotificationChannelGroup group : r.groups.values()) {
Julia Reynoldsad23a802018-04-19 15:50:31 -04001233 groups.put(group.toJson());
Julia Reynolds44011962017-04-14 12:29:04 -04001234 }
Julia Reynoldsad23a802018-04-19 15:50:31 -04001235 record.put("groups", groups);
Julia Reynolds44011962017-04-14 12:29:04 -04001236 } catch (JSONException e) {
1237 // pass
Chris Wrenacf424a2016-03-15 12:48:55 -04001238 }
Julia Reynolds44011962017-04-14 12:29:04 -04001239 records.put(record);
Chris Wrenacf424a2016-03-15 12:48:55 -04001240 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001241 }
1242 }
1243 try {
1244 ranking.put("records", records);
1245 } catch (JSONException e) {
1246 // pass
1247 }
1248 return ranking;
1249 }
1250
1251 /**
1252 * Dump only the ban information as structured JSON for the stats collector.
1253 *
Andrew Solovaya44f2c072018-10-02 14:14:42 -07001254 * This is intentionally redundant with {@link dumpJson} because the old
Chris Wrenacf424a2016-03-15 12:48:55 -04001255 * scraper will expect this format.
1256 *
1257 * @param filter
1258 * @return
1259 */
1260 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1261 JSONArray bans = new JSONArray();
1262 Map<Integer, String> packageBans = getPackageBans();
1263 for(Entry<Integer, String> ban : packageBans.entrySet()) {
1264 final int userId = UserHandle.getUserId(ban.getKey());
1265 final String packageName = ban.getValue();
1266 if (filter == null || filter.matches(packageName)) {
1267 JSONObject banJson = new JSONObject();
1268 try {
1269 banJson.put("userId", userId);
1270 banJson.put("packageName", packageName);
1271 } catch (JSONException e) {
1272 e.printStackTrace();
1273 }
1274 bans.put(banJson);
1275 }
1276 }
1277 return bans;
1278 }
1279
1280 public Map<Integer, String> getPackageBans() {
Julia Reynolds44011962017-04-14 12:29:04 -04001281 synchronized (mRecords) {
1282 final int N = mRecords.size();
1283 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1284 for (int i = 0; i < N; i++) {
1285 final Record r = mRecords.valueAt(i);
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001286 if (r.importance == IMPORTANCE_NONE) {
Julia Reynolds44011962017-04-14 12:29:04 -04001287 packageBans.put(r.uid, r.pkg);
1288 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001289 }
Julia Reynolds44011962017-04-14 12:29:04 -04001290
1291 return packageBans;
Chris Wrenacf424a2016-03-15 12:48:55 -04001292 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001293 }
1294
Julia Reynoldsd373d782017-03-03 13:32:57 -05001295 /**
1296 * Dump only the channel information as structured JSON for the stats collector.
1297 *
Andrew Solovaya44f2c072018-10-02 14:14:42 -07001298 * This is intentionally redundant with {@link dumpJson} because the old
Julia Reynoldsd373d782017-03-03 13:32:57 -05001299 * scraper will expect this format.
1300 *
1301 * @param filter
1302 * @return
1303 */
1304 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1305 JSONArray channels = new JSONArray();
1306 Map<String, Integer> packageChannels = getPackageChannels();
1307 for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1308 final String packageName = channelCount.getKey();
1309 if (filter == null || filter.matches(packageName)) {
1310 JSONObject channelCountJson = new JSONObject();
1311 try {
1312 channelCountJson.put("packageName", packageName);
1313 channelCountJson.put("channelCount", channelCount.getValue());
1314 } catch (JSONException e) {
1315 e.printStackTrace();
1316 }
1317 channels.put(channelCountJson);
1318 }
1319 }
1320 return channels;
1321 }
1322
1323 private Map<String, Integer> getPackageChannels() {
1324 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynolds44011962017-04-14 12:29:04 -04001325 synchronized (mRecords) {
1326 for (int i = 0; i < mRecords.size(); i++) {
1327 final Record r = mRecords.valueAt(i);
1328 int channelCount = 0;
1329 for (int j = 0; j < r.channels.size(); j++) {
1330 if (!r.channels.valueAt(j).isDeleted()) {
1331 channelCount++;
1332 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001333 }
Julia Reynolds44011962017-04-14 12:29:04 -04001334 packageChannels.put(r.pkg, channelCount);
Julia Reynoldsd373d782017-03-03 13:32:57 -05001335 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001336 }
1337 return packageChannels;
1338 }
1339
Julia Reynolds2e9bf5f2017-05-03 13:23:30 -04001340 public void onUserRemoved(int userId) {
1341 synchronized (mRecords) {
1342 int N = mRecords.size();
1343 for (int i = N - 1; i >= 0 ; i--) {
1344 Record record = mRecords.valueAt(i);
1345 if (UserHandle.getUserId(record.uid) == userId) {
1346 mRecords.removeAt(i);
1347 }
1348 }
1349 }
1350 }
1351
Julia Reynolds816797a2017-08-11 15:47:09 -04001352 protected void onLocaleChanged(Context context, int userId) {
1353 synchronized (mRecords) {
1354 int N = mRecords.size();
1355 for (int i = 0; i < N; i++) {
1356 Record record = mRecords.valueAt(i);
1357 if (UserHandle.getUserId(record.uid) == userId) {
1358 if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1359 record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1360 context.getResources().getString(
1361 R.string.default_notification_channel_label));
1362 }
1363 }
1364 }
1365 }
1366 }
1367
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001368 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1369 int[] uidList) {
1370 if (pkgList == null || pkgList.length == 0) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001371 return; // nothing to do
1372 }
John Spurlock35ef0a62015-05-28 11:24:10 -04001373 boolean updated = false;
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001374 if (removingPackage) {
1375 // Remove notification settings for uninstalled package
1376 int size = Math.min(pkgList.length, uidList.length);
1377 for (int i = 0; i < size; i++) {
1378 final String pkg = pkgList[i];
1379 final int uid = uidList[i];
Julia Reynolds44011962017-04-14 12:29:04 -04001380 synchronized (mRecords) {
1381 mRecords.remove(recordKey(pkg, uid));
1382 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001383 mRestoredWithoutUids.remove(pkg);
1384 updated = true;
1385 }
1386 } else {
1387 for (String pkg : pkgList) {
1388 // Package install
1389 final Record r = mRestoredWithoutUids.get(pkg);
1390 if (r != null) {
1391 try {
1392 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1393 mRestoredWithoutUids.remove(pkg);
Julia Reynolds44011962017-04-14 12:29:04 -04001394 synchronized (mRecords) {
1395 mRecords.put(recordKey(r.pkg, r.uid), r);
1396 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001397 updated = true;
1398 } catch (NameNotFoundException e) {
1399 // noop
1400 }
1401 }
1402 // Package upgrade
John Spurlock35ef0a62015-05-28 11:24:10 -04001403 try {
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001404 Record fullRecord = getRecord(pkg,
1405 mPm.getPackageUidAsUser(pkg, changeUserId));
1406 if (fullRecord != null) {
Julia Reynoldsf26eb912017-05-22 15:47:06 -04001407 createDefaultChannelIfNeeded(fullRecord);
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001408 deleteDefaultChannelIfNeeded(fullRecord);
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001409 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001410 } catch (NameNotFoundException e) {}
John Spurlock35ef0a62015-05-28 11:24:10 -04001411 }
1412 }
Julia Reynolds1864ff52016-11-02 09:54:47 -04001413
John Spurlock35ef0a62015-05-28 11:24:10 -04001414 if (updated) {
1415 updateConfig();
1416 }
1417 }
1418
Julia Reynoldsd373d782017-03-03 13:32:57 -05001419 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1420 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
1421 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1422 .setPackageName(pkg)
1423 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
1424 channel.getId())
1425 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1426 channel.getImportance());
1427 }
1428
Dan Sandler0171f572017-06-14 14:03:18 -04001429 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1430 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
1431 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1432 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1433 groupId)
1434 .setPackageName(pkg);
1435 }
1436
Chris Wren89aa2262017-05-05 18:05:56 -04001437 public void updateBadgingEnabled() {
1438 if (mBadgingEnabled == null) {
1439 mBadgingEnabled = new SparseBooleanArray();
1440 }
1441 boolean changed = false;
1442 // update the cached values
1443 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1444 int userId = mBadgingEnabled.keyAt(index);
1445 final boolean oldValue = mBadgingEnabled.get(userId);
1446 final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
1447 Secure.NOTIFICATION_BADGING,
1448 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1449 mBadgingEnabled.put(userId, newValue);
1450 changed |= oldValue != newValue;
1451 }
1452 if (changed) {
Julia Reynoldsf5395772017-09-06 14:08:23 -04001453 updateConfig();
Chris Wren89aa2262017-05-05 18:05:56 -04001454 }
1455 }
1456
1457 public boolean badgingEnabled(UserHandle userHandle) {
1458 int userId = userHandle.getIdentifier();
Chris Wren13f157f2017-05-12 15:02:06 -04001459 if (userId == UserHandle.USER_ALL) {
1460 return false;
1461 }
Chris Wren89aa2262017-05-05 18:05:56 -04001462 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1463 mBadgingEnabled.put(userId,
1464 Secure.getIntForUser(mContext.getContentResolver(),
1465 Secure.NOTIFICATION_BADGING,
1466 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1467 }
1468 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1469 }
1470
1471
John Spurlock1d881a12015-03-18 19:21:54 -04001472 private static class Record {
John Spurlock35ef0a62015-05-28 11:24:10 -04001473 static int UNKNOWN_UID = UserHandle.USER_NULL;
1474
John Spurlock1d881a12015-03-18 19:21:54 -04001475 String pkg;
John Spurlock35ef0a62015-05-28 11:24:10 -04001476 int uid = UNKNOWN_UID;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001477 int importance = DEFAULT_IMPORTANCE;
Julia Reynolds92d456e2016-01-25 16:36:59 -05001478 int priority = DEFAULT_PRIORITY;
1479 int visibility = DEFAULT_VISIBILITY;
Julia Reynolds924eed12017-01-19 09:52:07 -05001480 boolean showBadge = DEFAULT_SHOW_BADGE;
Rohan Shah590e1b22018-04-10 23:48:47 -04001481 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001482
1483 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Shunta Sato642b8d42017-06-13 16:16:13 +09001484 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynolds233a5f92015-10-19 13:51:23 -04001485 }
Chris Wren54bbef42014-07-09 18:37:56 -04001486}