blob: d7b36aaa80088fee5ac3d148c4d7702b708a8037 [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 Reynolds85769912016-10-25 09:08:57 -040018import com.android.internal.R;
Julia Reynolds4036e8d2017-01-13 09:50:05 -050019import com.android.internal.annotations.VisibleForTesting;
Julia Reynoldsd373d782017-03-03 13:32:57 -050020import com.android.internal.logging.MetricsLogger;
21import com.android.internal.logging.nano.MetricsProto;
Julia Reynolds52e64d02016-12-09 15:36:12 -050022import com.android.internal.util.Preconditions;
Julia Reynolds85769912016-10-25 09:08:57 -040023
Chris Wren54bbef42014-07-09 18:37:56 -040024import android.app.Notification;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040025import android.app.NotificationChannel;
Julia Reynolds59e152e2017-01-25 17:42:53 -050026import android.app.NotificationChannelGroup;
Julia Reynolds85769912016-10-25 09:08:57 -040027import android.app.NotificationManager;
Chris Wren54bbef42014-07-09 18:37:56 -040028import android.content.Context;
Julia Reynolds85769912016-10-25 09:08:57 -040029import android.content.pm.ApplicationInfo;
John Spurlock35ef0a62015-05-28 11:24:10 -040030import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040032import android.content.pm.ParceledListSlice;
Julia Reynoldsd373d782017-03-03 13:32:57 -050033import android.metrics.LogMaker;
Julia Reynolds85769912016-10-25 09:08:57 -040034import android.os.Build;
Chris Wren54bbef42014-07-09 18:37:56 -040035import android.os.UserHandle;
Chris Wren89aa2262017-05-05 18:05:56 -040036import android.provider.Settings.Secure;
Julia Reynolds5d25ee72015-11-20 15:38:20 -050037import android.service.notification.NotificationListenerService.Ranking;
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;
John Spurlock1d881a12015-03-18 19:21:54 -040042
Chris Wrenacf424a2016-03-15 12:48:55 -040043import org.json.JSONArray;
44import org.json.JSONException;
45import org.json.JSONObject;
Chris Wren54bbef42014-07-09 18:37:56 -040046import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48import org.xmlpull.v1.XmlSerializer;
49
50import java.io.IOException;
51import java.io.PrintWriter;
52import java.util.ArrayList;
Julia Reynoldse0b25742017-05-08 12:55:24 -040053import java.util.Arrays;
Julia Reynoldsf02562a2017-01-26 13:33:56 -050054import java.util.Collection;
Chris Wren54bbef42014-07-09 18:37:56 -040055import java.util.Collections;
Shunta Sato642b8d42017-06-13 16:16:13 +090056import java.util.concurrent.ConcurrentHashMap;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040057import java.util.List;
Chris Wrenacf424a2016-03-15 12:48:55 -040058import java.util.Map;
59import java.util.Map.Entry;
Julia Reynoldse0b25742017-05-08 12:55:24 -040060import java.util.Objects;
Chris Wren54bbef42014-07-09 18:37:56 -040061
62public class RankingHelper implements RankingConfig {
63 private static final String TAG = "RankingHelper";
Chris Wren54bbef42014-07-09 18:37:56 -040064
65 private static final int XML_VERSION = 1;
66
67 private static final String TAG_RANKING = "ranking";
68 private static final String TAG_PACKAGE = "package";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040069 private static final String TAG_CHANNEL = "channel";
Julia Reynolds59e152e2017-01-25 17:42:53 -050070 private static final String TAG_GROUP = "channelGroup";
Chris Wren54bbef42014-07-09 18:37:56 -040071
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040072 private static final String ATT_VERSION = "version";
Chris Wren54bbef42014-07-09 18:37:56 -040073 private static final String ATT_NAME = "name";
74 private static final String ATT_UID = "uid";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040075 private static final String ATT_ID = "id";
Chris Wren54bbef42014-07-09 18:37:56 -040076 private static final String ATT_PRIORITY = "priority";
Chris Wren3ad4e3a2014-09-02 17:23:51 -040077 private static final String ATT_VISIBILITY = "visibility";
Julia Reynolds5d25ee72015-11-20 15:38:20 -050078 private static final String ATT_IMPORTANCE = "importance";
Julia Reynolds924eed12017-01-19 09:52:07 -050079 private static final String ATT_SHOW_BADGE = "show_badge";
Chris Wren54bbef42014-07-09 18:37:56 -040080
John Spurlock1d881a12015-03-18 19:21:54 -040081 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
Julia Reynolds85769912016-10-25 09:08:57 -040082 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
83 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds924eed12017-01-19 09:52:07 -050084 private static final boolean DEFAULT_SHOW_BADGE = true;
John Spurlock1d881a12015-03-18 19:21:54 -040085
Chris Wren54bbef42014-07-09 18:37:56 -040086 private final NotificationSignalExtractor[] mSignalExtractors;
Julia Reynolds4a02afb2016-12-13 13:39:52 -050087 private final NotificationComparator mPreliminaryComparator;
Christoph Studercd4adf82014-08-19 17:50:49 +020088 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
Chris Wren54bbef42014-07-09 18:37:56 -040089
John Spurlock1d881a12015-03-18 19:21:54 -040090 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
91 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
John Spurlock35ef0a62015-05-28 11:24:10 -040092 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
Chris Wren54bbef42014-07-09 18:37:56 -040093
94 private final Context mContext;
Chris Wren51017d02015-12-15 15:34:46 -050095 private final RankingHandler mRankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -040096 private final PackageManager mPm;
Chris Wren89aa2262017-05-05 18:05:56 -040097 private SparseBooleanArray mBadgingEnabled;
Chris Wren54bbef42014-07-09 18:37:56 -040098
Julia Reynolds85769912016-10-25 09:08:57 -040099 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
Chris Wren51017d02015-12-15 15:34:46 -0500100 NotificationUsageStats usageStats, String[] extractorNames) {
Chris Wren54bbef42014-07-09 18:37:56 -0400101 mContext = context;
102 mRankingHandler = rankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400103 mPm = pm;
Chris Wren54bbef42014-07-09 18:37:56 -0400104
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500105 mPreliminaryComparator = new NotificationComparator(mContext);
106
Chris Wren89aa2262017-05-05 18:05:56 -0400107 updateBadgingEnabled();
108
Chris Wren54bbef42014-07-09 18:37:56 -0400109 final int N = extractorNames.length;
110 mSignalExtractors = new NotificationSignalExtractor[N];
111 for (int i = 0; i < N; i++) {
112 try {
113 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
114 NotificationSignalExtractor extractor =
115 (NotificationSignalExtractor) extractorClass.newInstance();
Chris Wren5eab2b72015-06-16 13:56:22 -0400116 extractor.initialize(mContext, usageStats);
Chris Wren54bbef42014-07-09 18:37:56 -0400117 extractor.setConfig(this);
118 mSignalExtractors[i] = extractor;
119 } catch (ClassNotFoundException e) {
120 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
121 } catch (InstantiationException e) {
122 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
123 } catch (IllegalAccessException e) {
124 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
125 }
126 }
127 }
128
John Spurlock1d881a12015-03-18 19:21:54 -0400129 @SuppressWarnings("unchecked")
John Spurlock2b122f42014-08-27 16:29:47 -0400130 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
131 final int N = mSignalExtractors.length;
132 for (int i = 0; i < N; i++) {
133 final NotificationSignalExtractor extractor = mSignalExtractors[i];
134 if (extractorClass.equals(extractor.getClass())) {
135 return (T) extractor;
136 }
137 }
138 return null;
139 }
140
Chris Wren54bbef42014-07-09 18:37:56 -0400141 public void extractSignals(NotificationRecord r) {
142 final int N = mSignalExtractors.length;
143 for (int i = 0; i < N; i++) {
144 NotificationSignalExtractor extractor = mSignalExtractors[i];
145 try {
146 RankingReconsideration recon = extractor.process(r);
147 if (recon != null) {
Chris Wren51017d02015-12-15 15:34:46 -0500148 mRankingHandler.requestReconsideration(recon);
Chris Wren54bbef42014-07-09 18:37:56 -0400149 }
150 } catch (Throwable t) {
151 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
152 }
153 }
154 }
155
John Spurlock35ef0a62015-05-28 11:24:10 -0400156 public void readXml(XmlPullParser parser, boolean forRestore)
157 throws XmlPullParserException, IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400158 int type = parser.getEventType();
159 if (type != XmlPullParser.START_TAG) return;
160 String tag = parser.getName();
161 if (!TAG_RANKING.equals(tag)) return;
Geoffrey Pitsch6eccf002017-04-05 12:33:59 -0400162 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
163 // at the time of serialization.
John Spurlock35ef0a62015-05-28 11:24:10 -0400164 mRestoredWithoutUids.clear();
Chris Wren54bbef42014-07-09 18:37:56 -0400165 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
166 tag = parser.getName();
167 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
168 return;
169 }
170 if (type == XmlPullParser.START_TAG) {
171 if (TAG_PACKAGE.equals(tag)) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400172 int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
Chris Wren54bbef42014-07-09 18:37:56 -0400173 String name = parser.getAttributeValue(null, ATT_NAME);
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400174 if (!TextUtils.isEmpty(name)) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400175 if (forRestore) {
176 try {
Xiaohui Chenddbe4ca2015-08-13 16:20:56 -0700177 //TODO: http://b/22388012
Julia Reynolds85769912016-10-25 09:08:57 -0400178 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
John Spurlock35ef0a62015-05-28 11:24:10 -0400179 } catch (NameNotFoundException e) {
180 // noop
181 }
182 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400183
Julia Reynolds85769912016-10-25 09:08:57 -0400184 Record r = getOrCreateRecord(name, uid,
185 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
186 safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
Julia Reynolds924eed12017-01-19 09:52:07 -0500187 safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
188 safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
Julia Reynolds5fe2eae2017-05-22 08:45:27 -0400189 r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
190 r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
191 r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
192 r.showBadge = safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400193
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400194 final int innerDepth = parser.getDepth();
195 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
196 && (type != XmlPullParser.END_TAG
197 || parser.getDepth() > innerDepth)) {
198 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
199 continue;
200 }
201
202 String tagName = parser.getName();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500203 // Channel groups
204 if (TAG_GROUP.equals(tagName)) {
205 String id = parser.getAttributeValue(null, ATT_ID);
206 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
207 if (!TextUtils.isEmpty(id)) {
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400208 NotificationChannelGroup group
209 = new NotificationChannelGroup(id, groupName);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500210 r.groups.put(id, group);
211 }
212 }
213 // Channels
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400214 if (TAG_CHANNEL.equals(tagName)) {
215 String id = parser.getAttributeValue(null, ATT_ID);
Julia Reynolds2c891c92017-03-17 14:23:47 -0400216 String channelName = parser.getAttributeValue(null, ATT_NAME);
Julia Reynolds85769912016-10-25 09:08:57 -0400217 int channelImportance =
218 safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400219 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
220 NotificationChannel channel = new NotificationChannel(id,
221 channelName, channelImportance);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400222 channel.populateFromXml(parser);
223 r.channels.put(id, channel);
224 }
225 }
226 }
Julia Reynolds85769912016-10-25 09:08:57 -0400227
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500228 try {
229 deleteDefaultChannelIfNeeded(r);
230 } catch (NameNotFoundException e) {
231 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
232 }
Chris Wren54bbef42014-07-09 18:37:56 -0400233 }
234 }
235 }
236 }
237 throw new IllegalStateException("Failed to reach END_DOCUMENT");
238 }
239
John Spurlock1d881a12015-03-18 19:21:54 -0400240 private static String recordKey(String pkg, int uid) {
241 return pkg + "|" + uid;
242 }
243
Julia Reynolds85769912016-10-25 09:08:57 -0400244 private Record getRecord(String pkg, int uid) {
John Spurlock1d881a12015-03-18 19:21:54 -0400245 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400246 synchronized (mRecords) {
247 return mRecords.get(key);
248 }
Julia Reynolds85769912016-10-25 09:08:57 -0400249 }
250
251 private Record getOrCreateRecord(String pkg, int uid) {
252 return getOrCreateRecord(pkg, uid,
Julia Reynolds924eed12017-01-19 09:52:07 -0500253 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400254 }
255
256 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
Julia Reynolds924eed12017-01-19 09:52:07 -0500257 int visibility, boolean showBadge) {
Julia Reynolds85769912016-10-25 09:08:57 -0400258 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400259 synchronized (mRecords) {
260 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
261 key);
262 if (r == null) {
263 r = new Record();
264 r.pkg = pkg;
265 r.uid = uid;
266 r.importance = importance;
267 r.priority = priority;
268 r.visibility = visibility;
269 r.showBadge = showBadge;
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500270
Julia Reynolds44011962017-04-14 12:29:04 -0400271 try {
272 createDefaultChannelIfNeeded(r);
273 } catch (NameNotFoundException e) {
274 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
275 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500276
Julia Reynolds44011962017-04-14 12:29:04 -0400277 if (r.uid == Record.UNKNOWN_UID) {
278 mRestoredWithoutUids.put(pkg, r);
279 } else {
280 mRecords.put(key, r);
281 }
Julia Reynolds85769912016-10-25 09:08:57 -0400282 }
Julia Reynolds44011962017-04-14 12:29:04 -0400283 return r;
John Spurlock1d881a12015-03-18 19:21:54 -0400284 }
John Spurlock1d881a12015-03-18 19:21:54 -0400285 }
286
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500287 private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
288 final int userId = UserHandle.getUserId(r.uid);
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000289 final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
Dan Sandlere103b782017-05-17 16:07:56 -0700290 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400291 // O apps should not have the default channel.
292 return false;
Julia Reynolds85769912016-10-25 09:08:57 -0400293 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500294
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400295 // Otherwise, this app should have the default channel.
296 return true;
Julia Reynolds85769912016-10-25 09:08:57 -0400297 }
298
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500299 private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
Julia Reynolds85769912016-10-25 09:08:57 -0400300 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500301 // Not present
302 return;
Julia Reynolds85769912016-10-25 09:08:57 -0400303 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500304
305 if (shouldHaveDefaultChannel(r)) {
306 // Keep the default channel until upgraded.
307 return;
308 }
309
310 // Remove Default Channel.
311 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
312 }
313
314 private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
315 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Julia Reynolds17717f52017-05-09 11:46:06 -0400316 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
317 mContext.getString(R.string.default_notification_channel_label));
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500318 return;
319 }
320
321 if (!shouldHaveDefaultChannel(r)) {
322 // Keep the default channel until upgraded.
323 return;
324 }
325
326 // Create Default Channel
327 NotificationChannel channel;
328 channel = new NotificationChannel(
329 NotificationChannel.DEFAULT_CHANNEL_ID,
330 mContext.getString(R.string.default_notification_channel_label),
331 r.importance);
332 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
333 channel.setLockscreenVisibility(r.visibility);
334 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
335 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
336 }
337 if (r.priority != DEFAULT_PRIORITY) {
338 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
339 }
340 if (r.visibility != DEFAULT_VISIBILITY) {
341 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
342 }
343 r.channels.put(channel.getId(), channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400344 }
345
John Spurlock35ef0a62015-05-28 11:24:10 -0400346 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400347 out.startTag(null, TAG_RANKING);
348 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
349
Julia Reynolds44011962017-04-14 12:29:04 -0400350 synchronized (mRecords) {
351 final int N = mRecords.size();
352 for (int i = 0; i < N; i++) {
353 final Record r = mRecords.valueAt(i);
354 //TODO: http://b/22388012
355 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
356 continue;
Julia Reynoldsef37f282016-02-12 09:11:27 -0500357 }
Julia Reynolds44011962017-04-14 12:29:04 -0400358 final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
359 || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
360 || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0
361 || r.groups.size() > 0;
362 if (hasNonDefaultSettings) {
363 out.startTag(null, TAG_PACKAGE);
364 out.attribute(null, ATT_NAME, r.pkg);
365 if (r.importance != DEFAULT_IMPORTANCE) {
366 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
Julia Reynoldsd0a5b162017-03-18 13:42:09 -0400367 }
Julia Reynolds44011962017-04-14 12:29:04 -0400368 if (r.priority != DEFAULT_PRIORITY) {
369 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
370 }
371 if (r.visibility != DEFAULT_VISIBILITY) {
372 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
373 }
374 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400375
Julia Reynolds44011962017-04-14 12:29:04 -0400376 if (!forBackup) {
377 out.attribute(null, ATT_UID, Integer.toString(r.uid));
378 }
379
380 for (NotificationChannelGroup group : r.groups.values()) {
381 group.writeXml(out);
382 }
383
384 for (NotificationChannel channel : r.channels.values()) {
385 if (!forBackup || (forBackup && !channel.isDeleted())) {
386 channel.writeXml(out);
387 }
388 }
389
390 out.endTag(null, TAG_PACKAGE);
391 }
Julia Reynoldsef37f282016-02-12 09:11:27 -0500392 }
Chris Wren54bbef42014-07-09 18:37:56 -0400393 }
394 out.endTag(null, TAG_RANKING);
395 }
396
397 private void updateConfig() {
398 final int N = mSignalExtractors.length;
399 for (int i = 0; i < N; i++) {
400 mSignalExtractors[i].setConfig(this);
401 }
Julia Reynolds22f02b32016-12-01 15:05:13 -0500402 mRankingHandler.requestSort(false);
Chris Wren54bbef42014-07-09 18:37:56 -0400403 }
404
405 public void sort(ArrayList<NotificationRecord> notificationList) {
Chris Wren1031c972014-07-23 13:11:45 +0000406 final int N = notificationList.size();
Christoph Studercd4adf82014-08-19 17:50:49 +0200407 // clear global sort keys
Chris Wren1031c972014-07-23 13:11:45 +0000408 for (int i = N - 1; i >= 0; i--) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200409 notificationList.get(i).setGlobalSortKey(null);
Chris Wren1031c972014-07-23 13:11:45 +0000410 }
411
Christoph Studer85374052014-10-08 11:19:55 -0700412 // rank each record individually
413 Collections.sort(notificationList, mPreliminaryComparator);
Christoph Studercd4adf82014-08-19 17:50:49 +0200414
415 synchronized (mProxyByGroupTmp) {
416 // record individual ranking result and nominate proxies for each group
417 for (int i = N - 1; i >= 0; i--) {
418 final NotificationRecord record = notificationList.get(i);
419 record.setAuthoritativeRank(i);
420 final String groupKey = record.getGroupKey();
Julia Reynolds3fb989b2017-02-28 16:06:55 -0500421 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
422 if (existingProxy == null
423 || record.getImportance() > existingProxy.getImportance()) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200424 mProxyByGroupTmp.put(groupKey, record);
425 }
426 }
427 // assign global sort key:
428 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
429 for (int i = 0; i < N; i++) {
430 final NotificationRecord record = notificationList.get(i);
431 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
432 String groupSortKey = record.getNotification().getSortKey();
433
434 // We need to make sure the developer provided group sort key (gsk) is handled
435 // correctly:
436 // gsk="" < gsk=non-null-string < gsk=null
437 //
438 // We enforce this by using different prefixes for these three cases.
439 String groupSortKeyPortion;
440 if (groupSortKey == null) {
441 groupSortKeyPortion = "nsk";
442 } else if (groupSortKey.equals("")) {
443 groupSortKeyPortion = "esk";
444 } else {
445 groupSortKeyPortion = "gsk=" + groupSortKey;
446 }
447
448 boolean isGroupSummary = record.getNotification().isGroupSummary();
449 record.setGlobalSortKey(
450 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
Selim Cinek55a3e732017-05-25 18:30:10 -0700451 record.isRecentlyIntrusive()
452 && record.getImportance() > NotificationManager.IMPORTANCE_MIN
453 ? '0' : '1',
Christoph Studercd4adf82014-08-19 17:50:49 +0200454 groupProxy.getAuthoritativeRank(),
455 isGroupSummary ? '0' : '1',
456 groupSortKeyPortion,
457 record.getAuthoritativeRank()));
458 }
459 mProxyByGroupTmp.clear();
Chris Wren1031c972014-07-23 13:11:45 +0000460 }
Christoph Studercd4adf82014-08-19 17:50:49 +0200461
Chris Wren1031c972014-07-23 13:11:45 +0000462 // Do a second ranking pass, using group proxies
463 Collections.sort(notificationList, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400464 }
465
466 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
Chris Wren1031c972014-07-23 13:11:45 +0000467 return Collections.binarySearch(notificationList, target, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400468 }
469
Julia Reynolds924eed12017-01-19 09:52:07 -0500470 private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
471 final String value = parser.getAttributeValue(null, att);
472 if (TextUtils.isEmpty(value)) return defValue;
473 return Boolean.parseBoolean(value);
474 }
475
Chris Wren54bbef42014-07-09 18:37:56 -0400476 private static int safeInt(XmlPullParser parser, String att, int defValue) {
477 final String val = parser.getAttributeValue(null, att);
478 return tryParseInt(val, defValue);
479 }
480
481 private static int tryParseInt(String value, int defValue) {
482 if (TextUtils.isEmpty(value)) return defValue;
483 try {
Narayan Kamatha09b4d22016-04-15 18:32:45 +0100484 return Integer.parseInt(value);
Chris Wren54bbef42014-07-09 18:37:56 -0400485 } catch (NumberFormatException e) {
486 return defValue;
487 }
488 }
489
Julia Reynoldsef37f282016-02-12 09:11:27 -0500490 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -0500491 * Gets importance.
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500492 */
493 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -0500494 public int getImportance(String packageName, int uid) {
495 return getOrCreateRecord(packageName, uid).importance;
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500496 }
497
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400498 @Override
Julia Reynolds924eed12017-01-19 09:52:07 -0500499 public boolean canShowBadge(String packageName, int uid) {
500 return getOrCreateRecord(packageName, uid).showBadge;
501 }
502
503 @Override
504 public void setShowBadge(String packageName, int uid, boolean showBadge) {
505 getOrCreateRecord(packageName, uid).showBadge = showBadge;
506 updateConfig();
507 }
508
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400509 int getPackagePriority(String pkg, int uid) {
510 return getOrCreateRecord(pkg, uid).priority;
511 }
512
513 int getPackageVisibility(String pkg, int uid) {
514 return getOrCreateRecord(pkg, uid).visibility;
515 }
516
Julia Reynolds924eed12017-01-19 09:52:07 -0500517 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500518 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
519 boolean fromTargetApp) {
520 Preconditions.checkNotNull(pkg);
521 Preconditions.checkNotNull(group);
522 Preconditions.checkNotNull(group.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400523 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500524 Record r = getOrCreateRecord(pkg, uid);
525 if (r == null) {
526 throw new IllegalArgumentException("Invalid package");
527 }
Dan Sandler0171f572017-06-14 14:03:18 -0400528 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
529 if (!group.equals(oldGroup)) {
530 // will log for new entries as well as name changes
531 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
532 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500533 r.groups.put(group.getId(), group);
534 updateConfig();
535 }
536
537 @Override
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500538 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
539 boolean fromTargetApp) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500540 Preconditions.checkNotNull(pkg);
541 Preconditions.checkNotNull(channel);
542 Preconditions.checkNotNull(channel.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400543 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400544 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500545 if (r == null) {
546 throw new IllegalArgumentException("Invalid package");
547 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500548 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
549 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
550 }
Julia Reynolds03fa85d2017-03-06 15:14:50 -0500551 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
552 throw new IllegalArgumentException("Reserved id");
553 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500554
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500555 NotificationChannel existing = r.channels.get(channel.getId());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400556 // Keep most of the existing settings
Julia Reynolds3d91f112017-03-03 08:59:53 -0500557 if (existing != null && fromTargetApp) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500558 if (existing.isDeleted()) {
559 existing.setDeleted(false);
Dan Sandler0171f572017-06-14 14:03:18 -0400560
561 // log a resurrected channel as if it's new again
562 MetricsLogger.action(getChannelLog(channel, pkg).setType(
563 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500564 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500565
Julia Reynolds2c891c92017-03-17 14:23:47 -0400566 existing.setName(channel.getName().toString());
567 existing.setDescription(channel.getDescription());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400568 existing.setBlockableSystem(channel.isBlockableSystem());
Julia Reynoldsd373d782017-03-03 13:32:57 -0500569
570 updateConfig();
Geoffrey Pitsch03533712017-01-05 10:30:07 -0500571 return;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400572 }
Julia Reynolds3e50bf62017-05-24 16:33:32 -0400573 if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE
Julia Reynolds85769912016-10-25 09:08:57 -0400574 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
575 throw new IllegalArgumentException("Invalid importance level");
576 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500577 // Reset fields that apps aren't allowed to set.
578 if (fromTargetApp) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500579 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
580 channel.setLockscreenVisibility(r.visibility);
581 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500582 clearLockedFields(channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400583 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
584 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
585 }
Julia Reynolds924eed12017-01-19 09:52:07 -0500586 if (!r.showBadge) {
587 channel.setShowBadge(false);
588 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400589 r.channels.put(channel.getId(), channel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500590 MetricsLogger.action(getChannelLog(channel, pkg).setType(
591 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400592 updateConfig();
593 }
594
Julia Reynoldse0b25742017-05-08 12:55:24 -0400595 void clearLockedFields(NotificationChannel channel) {
596 channel.unlockFields(channel.getUserLockedFields());
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500597 }
598
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400599 @Override
Julia Reynolds85769912016-10-25 09:08:57 -0400600 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500601 Preconditions.checkNotNull(updatedChannel);
602 Preconditions.checkNotNull(updatedChannel.getId());
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400603 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500604 if (r == null) {
605 throw new IllegalArgumentException("Invalid package");
606 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400607 NotificationChannel channel = r.channels.get(updatedChannel.getId());
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500608 if (channel == null || channel.isDeleted()) {
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400609 throw new IllegalArgumentException("Channel does not exist");
610 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400611 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
612 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
613 }
Julia Reynoldse0b25742017-05-08 12:55:24 -0400614 lockFieldsForUpdate(channel, updatedChannel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400615 r.channels.put(updatedChannel.getId(), updatedChannel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500616
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400617 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
618 // copy settings to app level so they are inherited by new channels
619 // when the app migrates
620 r.importance = updatedChannel.getImportance();
621 r.priority = updatedChannel.canBypassDnd()
622 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
623 r.visibility = updatedChannel.getLockscreenVisibility();
624 r.showBadge = updatedChannel.canShowBadge();
625 }
626
Dan Sandler0171f572017-06-14 14:03:18 -0400627 if (!channel.equals(updatedChannel)) {
628 // only log if there are real changes
629 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
630 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400631 updateConfig();
632 }
633
634 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500635 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
636 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500637 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400638 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500639 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500640 return null;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500641 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400642 if (channelId == null) {
643 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
644 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500645 final NotificationChannel nc = r.channels.get(channelId);
646 if (nc != null && (includeDeleted || !nc.isDeleted())) {
647 return nc;
648 }
649 return null;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400650 }
651
652 @Override
653 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
Julia Reynolds85769912016-10-25 09:08:57 -0400654 Record r = getRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500655 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500656 return;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500657 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500658 NotificationChannel channel = r.channels.get(channelId);
659 if (channel != null) {
660 channel.setDeleted(true);
Julia Reynolds8e0eb372017-03-21 15:04:50 -0400661 LogMaker lm = getChannelLog(channel, pkg);
662 lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
663 MetricsLogger.action(lm);
664 updateConfig();
Julia Reynolds85769912016-10-25 09:08:57 -0400665 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400666 }
667
668 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500669 @VisibleForTesting
670 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
671 Preconditions.checkNotNull(pkg);
672 Preconditions.checkNotNull(channelId);
673 Record r = getRecord(pkg, uid);
674 if (r == null) {
675 return;
676 }
677 r.channels.remove(channelId);
Julia Reynolds9bfba592017-03-15 14:03:55 -0400678 updateConfig();
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500679 }
680
681 @Override
682 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
683 Preconditions.checkNotNull(pkg);
684 Record r = getRecord(pkg, uid);
685 if (r == null) {
686 return;
687 }
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000688 int N = r.channels.size() - 1;
689 for (int i = N; i >= 0; i--) {
690 String key = r.channels.keyAt(i);
691 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
692 r.channels.remove(key);
693 }
694 }
Julia Reynolds9bfba592017-03-15 14:03:55 -0400695 updateConfig();
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500696 }
697
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500698 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
699 int uid) {
700 Preconditions.checkNotNull(pkg);
701 Record r = getRecord(pkg, uid);
702 return r.groups.get(groupId);
703 }
704
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500705 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500706 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
707 int uid, boolean includeDeleted) {
708 Preconditions.checkNotNull(pkg);
Julia Reynolds74856c42017-02-08 14:47:23 -0500709 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500710 Record r = getRecord(pkg, uid);
711 if (r == null) {
712 return ParceledListSlice.emptyList();
713 }
714 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
715 int N = r.channels.size();
716 for (int i = 0; i < N; i++) {
717 final NotificationChannel nc = r.channels.valueAt(i);
718 if (includeDeleted || !nc.isDeleted()) {
719 if (nc.getGroup() != null) {
Julia Reynolds9bfba592017-03-15 14:03:55 -0400720 if (r.groups.get(nc.getGroup()) != null) {
721 NotificationChannelGroup ncg = groups.get(nc.getGroup());
722 if (ncg == null) {
723 ncg = r.groups.get(nc.getGroup()).clone();
724 groups.put(nc.getGroup(), ncg);
725
726 }
727 ncg.addChannel(nc);
Julia Reynolds74856c42017-02-08 14:47:23 -0500728 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500729 } else {
730 nonGrouped.addChannel(nc);
731 }
732 }
733 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500734 if (nonGrouped.getChannels().size() > 0) {
Julia Reynolds74856c42017-02-08 14:47:23 -0500735 groups.put(null, nonGrouped);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500736 }
Julia Reynolds74856c42017-02-08 14:47:23 -0500737 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500738 }
739
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400740 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
Julia Reynolds9bfba592017-03-15 14:03:55 -0400741 String groupId) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400742 List<NotificationChannel> deletedChannels = new ArrayList<>();
Julia Reynolds9bfba592017-03-15 14:03:55 -0400743 Record r = getRecord(pkg, uid);
744 if (r == null || TextUtils.isEmpty(groupId)) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400745 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400746 }
747
748 r.groups.remove(groupId);
749
750 int N = r.channels.size();
751 for (int i = 0; i < N; i++) {
752 final NotificationChannel nc = r.channels.valueAt(i);
753 if (groupId.equals(nc.getGroup())) {
754 nc.setDeleted(true);
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400755 deletedChannels.add(nc);
Julia Reynolds9bfba592017-03-15 14:03:55 -0400756 }
757 }
758 updateConfig();
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400759 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400760 }
761
Julia Reynolds59e152e2017-01-25 17:42:53 -0500762 @Override
Julia Reynoldsf02562a2017-01-26 13:33:56 -0500763 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
764 int uid) {
765 Record r = getRecord(pkg, uid);
766 if (r == null) {
767 return new ArrayList<>();
768 }
769 return r.groups.values();
770 }
771
772 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500773 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
774 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500775 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400776 List<NotificationChannel> channels = new ArrayList<>();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500777 Record r = getRecord(pkg, uid);
778 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500779 return ParceledListSlice.emptyList();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500780 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400781 int N = r.channels.size();
782 for (int i = 0; i < N; i++) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500783 final NotificationChannel nc = r.channels.valueAt(i);
784 if (includeDeleted || !nc.isDeleted()) {
785 channels.add(nc);
786 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400787 }
Julia Reynolds85769912016-10-25 09:08:57 -0400788 return new ParceledListSlice<>(channels);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400789 }
790
Julia Reynolds17717f52017-05-09 11:46:06 -0400791 /**
792 * True for pre-O apps that only have the default channel, or pre O apps that have no
793 * channels yet. This method will create the default channel for pre-O apps that don't have it.
794 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
795 * upgrades.
796 */
797 public boolean onlyHasDefaultChannel(String pkg, int uid) {
798 Record r = getOrCreateRecord(pkg, uid);
799 if (r.channels.size() == 1
800 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
801 return true;
802 }
803 return false;
804 }
805
Julia Reynolds41103f42017-03-15 11:36:35 -0400806 public int getDeletedChannelCount(String pkg, int uid) {
807 Preconditions.checkNotNull(pkg);
808 int deletedCount = 0;
809 Record r = getRecord(pkg, uid);
810 if (r == null) {
811 return deletedCount;
812 }
813 int N = r.channels.size();
814 for (int i = 0; i < N; i++) {
815 final NotificationChannel nc = r.channels.valueAt(i);
816 if (nc.isDeleted()) {
817 deletedCount++;
818 }
819 }
820 return deletedCount;
821 }
822
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500823 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -0500824 * Sets importance.
Julia Reynolds92d456e2016-01-25 16:36:59 -0500825 */
826 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -0500827 public void setImportance(String pkgName, int uid, int importance) {
828 getOrCreateRecord(pkgName, uid).importance = importance;
Julia Reynoldsa07af882015-12-17 08:32:48 -0500829 updateConfig();
830 }
831
Chris Wrenacf424a2016-03-15 12:48:55 -0400832 public void setEnabled(String packageName, int uid, boolean enabled) {
Julia Reynolds85769912016-10-25 09:08:57 -0400833 boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE;
Chris Wrenacf424a2016-03-15 12:48:55 -0400834 if (wasEnabled == enabled) {
835 return;
836 }
Julia Reynolds85769912016-10-25 09:08:57 -0400837 setImportance(packageName, uid,
838 enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE);
Chris Wrenacf424a2016-03-15 12:48:55 -0400839 }
840
Julia Reynoldse0b25742017-05-08 12:55:24 -0400841 @VisibleForTesting
842 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
843 update.unlockFields(update.getUserLockedFields());
844 update.lockFields(original.getUserLockedFields());
845 if (original.canBypassDnd() != update.canBypassDnd()) {
846 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
847 }
848 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
849 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
850 }
851 if (original.getImportance() != update.getImportance()) {
852 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
853 }
854 if (original.shouldShowLights() != update.shouldShowLights()
855 || original.getLightColor() != update.getLightColor()) {
856 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
857 }
858 if (!Objects.equals(original.getSound(), update.getSound())) {
859 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
860 }
861 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
862 || original.shouldVibrate() != update.shouldVibrate()) {
863 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
864 }
865 if (original.canShowBadge() != update.canShowBadge()) {
866 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
867 }
868 }
869
Chris Wren54bbef42014-07-09 18:37:56 -0400870 public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
871 if (filter == null) {
872 final int N = mSignalExtractors.length;
873 pw.print(prefix);
874 pw.print("mSignalExtractors.length = ");
875 pw.println(N);
876 for (int i = 0; i < N; i++) {
877 pw.print(prefix);
878 pw.print(" ");
879 pw.println(mSignalExtractors[i]);
880 }
881 }
Chris Wren54bbef42014-07-09 18:37:56 -0400882 if (filter == null) {
883 pw.print(prefix);
John Spurlock1d881a12015-03-18 19:21:54 -0400884 pw.println("per-package config:");
Chris Wren54bbef42014-07-09 18:37:56 -0400885 }
Julia Reynolds85aa6cb2016-01-08 17:49:11 -0500886 pw.println("Records:");
Julia Reynolds44011962017-04-14 12:29:04 -0400887 synchronized (mRecords) {
888 dumpRecords(pw, prefix, filter, mRecords);
889 }
Julia Reynolds85aa6cb2016-01-08 17:49:11 -0500890 pw.println("Restored without uid:");
John Spurlock35ef0a62015-05-28 11:24:10 -0400891 dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
892 }
893
894 private static void dumpRecords(PrintWriter pw, String prefix,
895 NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
896 final int N = records.size();
Chris Wren54bbef42014-07-09 18:37:56 -0400897 for (int i = 0; i < N; i++) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400898 final Record r = records.valueAt(i);
John Spurlock1d881a12015-03-18 19:21:54 -0400899 if (filter == null || filter.matches(r.pkg)) {
900 pw.print(prefix);
Julia Reynolds924eed12017-01-19 09:52:07 -0500901 pw.print(" AppSettings: ");
John Spurlock1d881a12015-03-18 19:21:54 -0400902 pw.print(r.pkg);
903 pw.print(" (");
John Spurlock35ef0a62015-05-28 11:24:10 -0400904 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
John Spurlock1d881a12015-03-18 19:21:54 -0400905 pw.print(')');
Julia Reynolds92d456e2016-01-25 16:36:59 -0500906 if (r.importance != DEFAULT_IMPORTANCE) {
907 pw.print(" importance=");
908 pw.print(Ranking.importanceToString(r.importance));
909 }
910 if (r.priority != DEFAULT_PRIORITY) {
911 pw.print(" priority=");
Chris Wrenacf424a2016-03-15 12:48:55 -0400912 pw.print(Notification.priorityToString(r.priority));
Julia Reynolds92d456e2016-01-25 16:36:59 -0500913 }
914 if (r.visibility != DEFAULT_VISIBILITY) {
915 pw.print(" visibility=");
Chris Wrenacf424a2016-03-15 12:48:55 -0400916 pw.print(Notification.visibilityToString(r.visibility));
Julia Reynolds92d456e2016-01-25 16:36:59 -0500917 }
Julia Reynolds924eed12017-01-19 09:52:07 -0500918 pw.print(" showBadge=");
919 pw.print(Boolean.toString(r.showBadge));
John Spurlock1d881a12015-03-18 19:21:54 -0400920 pw.println();
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400921 for (NotificationChannel channel : r.channels.values()) {
922 pw.print(prefix);
923 pw.print(" ");
924 pw.print(" ");
925 pw.println(channel);
926 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500927 for (NotificationChannelGroup group : r.groups.values()) {
928 pw.print(prefix);
929 pw.print(" ");
930 pw.print(" ");
931 pw.println(group);
932 }
Chris Wren54bbef42014-07-09 18:37:56 -0400933 }
934 }
935 }
John Spurlock1d881a12015-03-18 19:21:54 -0400936
Chris Wrenacf424a2016-03-15 12:48:55 -0400937 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
938 JSONObject ranking = new JSONObject();
939 JSONArray records = new JSONArray();
940 try {
941 ranking.put("noUid", mRestoredWithoutUids.size());
942 } catch (JSONException e) {
943 // pass
944 }
Julia Reynolds44011962017-04-14 12:29:04 -0400945 synchronized (mRecords) {
946 final int N = mRecords.size();
947 for (int i = 0; i < N; i++) {
948 final Record r = mRecords.valueAt(i);
949 if (filter == null || filter.matches(r.pkg)) {
950 JSONObject record = new JSONObject();
951 try {
952 record.put("userId", UserHandle.getUserId(r.uid));
953 record.put("packageName", r.pkg);
954 if (r.importance != DEFAULT_IMPORTANCE) {
955 record.put("importance", Ranking.importanceToString(r.importance));
956 }
957 if (r.priority != DEFAULT_PRIORITY) {
958 record.put("priority", Notification.priorityToString(r.priority));
959 }
960 if (r.visibility != DEFAULT_VISIBILITY) {
961 record.put("visibility", Notification.visibilityToString(r.visibility));
962 }
963 if (r.showBadge != DEFAULT_SHOW_BADGE) {
964 record.put("showBadge", Boolean.valueOf(r.showBadge));
965 }
966 for (NotificationChannel channel : r.channels.values()) {
967 record.put("channel", channel.toJson());
968 }
969 for (NotificationChannelGroup group : r.groups.values()) {
970 record.put("group", group.toJson());
971 }
972 } catch (JSONException e) {
973 // pass
Chris Wrenacf424a2016-03-15 12:48:55 -0400974 }
Julia Reynolds44011962017-04-14 12:29:04 -0400975 records.put(record);
Chris Wrenacf424a2016-03-15 12:48:55 -0400976 }
Chris Wrenacf424a2016-03-15 12:48:55 -0400977 }
978 }
979 try {
980 ranking.put("records", records);
981 } catch (JSONException e) {
982 // pass
983 }
984 return ranking;
985 }
986
987 /**
988 * Dump only the ban information as structured JSON for the stats collector.
989 *
990 * This is intentionally redundant with {#link dumpJson} because the old
991 * scraper will expect this format.
992 *
993 * @param filter
994 * @return
995 */
996 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
997 JSONArray bans = new JSONArray();
998 Map<Integer, String> packageBans = getPackageBans();
999 for(Entry<Integer, String> ban : packageBans.entrySet()) {
1000 final int userId = UserHandle.getUserId(ban.getKey());
1001 final String packageName = ban.getValue();
1002 if (filter == null || filter.matches(packageName)) {
1003 JSONObject banJson = new JSONObject();
1004 try {
1005 banJson.put("userId", userId);
1006 banJson.put("packageName", packageName);
1007 } catch (JSONException e) {
1008 e.printStackTrace();
1009 }
1010 bans.put(banJson);
1011 }
1012 }
1013 return bans;
1014 }
1015
1016 public Map<Integer, String> getPackageBans() {
Julia Reynolds44011962017-04-14 12:29:04 -04001017 synchronized (mRecords) {
1018 final int N = mRecords.size();
1019 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1020 for (int i = 0; i < N; i++) {
1021 final Record r = mRecords.valueAt(i);
1022 if (r.importance == NotificationManager.IMPORTANCE_NONE) {
1023 packageBans.put(r.uid, r.pkg);
1024 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001025 }
Julia Reynolds44011962017-04-14 12:29:04 -04001026
1027 return packageBans;
Chris Wrenacf424a2016-03-15 12:48:55 -04001028 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001029 }
1030
Julia Reynoldsd373d782017-03-03 13:32:57 -05001031 /**
1032 * Dump only the channel information as structured JSON for the stats collector.
1033 *
1034 * This is intentionally redundant with {#link dumpJson} because the old
1035 * scraper will expect this format.
1036 *
1037 * @param filter
1038 * @return
1039 */
1040 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1041 JSONArray channels = new JSONArray();
1042 Map<String, Integer> packageChannels = getPackageChannels();
1043 for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1044 final String packageName = channelCount.getKey();
1045 if (filter == null || filter.matches(packageName)) {
1046 JSONObject channelCountJson = new JSONObject();
1047 try {
1048 channelCountJson.put("packageName", packageName);
1049 channelCountJson.put("channelCount", channelCount.getValue());
1050 } catch (JSONException e) {
1051 e.printStackTrace();
1052 }
1053 channels.put(channelCountJson);
1054 }
1055 }
1056 return channels;
1057 }
1058
1059 private Map<String, Integer> getPackageChannels() {
1060 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynolds44011962017-04-14 12:29:04 -04001061 synchronized (mRecords) {
1062 for (int i = 0; i < mRecords.size(); i++) {
1063 final Record r = mRecords.valueAt(i);
1064 int channelCount = 0;
1065 for (int j = 0; j < r.channels.size(); j++) {
1066 if (!r.channels.valueAt(j).isDeleted()) {
1067 channelCount++;
1068 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001069 }
Julia Reynolds44011962017-04-14 12:29:04 -04001070 packageChannels.put(r.pkg, channelCount);
Julia Reynoldsd373d782017-03-03 13:32:57 -05001071 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001072 }
1073 return packageChannels;
1074 }
1075
Julia Reynolds2e9bf5f2017-05-03 13:23:30 -04001076 public void onUserRemoved(int userId) {
1077 synchronized (mRecords) {
1078 int N = mRecords.size();
1079 for (int i = N - 1; i >= 0 ; i--) {
1080 Record record = mRecords.valueAt(i);
1081 if (UserHandle.getUserId(record.uid) == userId) {
1082 mRecords.removeAt(i);
1083 }
1084 }
1085 }
1086 }
1087
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001088 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1089 int[] uidList) {
1090 if (pkgList == null || pkgList.length == 0) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001091 return; // nothing to do
1092 }
John Spurlock35ef0a62015-05-28 11:24:10 -04001093 boolean updated = false;
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001094 if (removingPackage) {
1095 // Remove notification settings for uninstalled package
1096 int size = Math.min(pkgList.length, uidList.length);
1097 for (int i = 0; i < size; i++) {
1098 final String pkg = pkgList[i];
1099 final int uid = uidList[i];
Julia Reynolds44011962017-04-14 12:29:04 -04001100 synchronized (mRecords) {
1101 mRecords.remove(recordKey(pkg, uid));
1102 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001103 mRestoredWithoutUids.remove(pkg);
1104 updated = true;
1105 }
1106 } else {
1107 for (String pkg : pkgList) {
1108 // Package install
1109 final Record r = mRestoredWithoutUids.get(pkg);
1110 if (r != null) {
1111 try {
1112 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1113 mRestoredWithoutUids.remove(pkg);
Julia Reynolds44011962017-04-14 12:29:04 -04001114 synchronized (mRecords) {
1115 mRecords.put(recordKey(r.pkg, r.uid), r);
1116 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001117 updated = true;
1118 } catch (NameNotFoundException e) {
1119 // noop
1120 }
1121 }
1122 // Package upgrade
John Spurlock35ef0a62015-05-28 11:24:10 -04001123 try {
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001124 Record fullRecord = getRecord(pkg,
1125 mPm.getPackageUidAsUser(pkg, changeUserId));
1126 if (fullRecord != null) {
Julia Reynoldsf26eb912017-05-22 15:47:06 -04001127 createDefaultChannelIfNeeded(fullRecord);
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001128 deleteDefaultChannelIfNeeded(fullRecord);
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001129 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001130 } catch (NameNotFoundException e) {}
John Spurlock35ef0a62015-05-28 11:24:10 -04001131 }
1132 }
Julia Reynolds1864ff52016-11-02 09:54:47 -04001133
John Spurlock35ef0a62015-05-28 11:24:10 -04001134 if (updated) {
1135 updateConfig();
1136 }
1137 }
1138
Julia Reynoldsd373d782017-03-03 13:32:57 -05001139 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1140 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
1141 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1142 .setPackageName(pkg)
1143 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
1144 channel.getId())
1145 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1146 channel.getImportance());
1147 }
1148
Dan Sandler0171f572017-06-14 14:03:18 -04001149 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1150 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
1151 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1152 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1153 groupId)
1154 .setPackageName(pkg);
1155 }
1156
Chris Wren89aa2262017-05-05 18:05:56 -04001157 public void updateBadgingEnabled() {
1158 if (mBadgingEnabled == null) {
1159 mBadgingEnabled = new SparseBooleanArray();
1160 }
1161 boolean changed = false;
1162 // update the cached values
1163 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1164 int userId = mBadgingEnabled.keyAt(index);
1165 final boolean oldValue = mBadgingEnabled.get(userId);
1166 final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
1167 Secure.NOTIFICATION_BADGING,
1168 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1169 mBadgingEnabled.put(userId, newValue);
1170 changed |= oldValue != newValue;
1171 }
1172 if (changed) {
1173 mRankingHandler.requestSort(false);
1174 }
1175 }
1176
1177 public boolean badgingEnabled(UserHandle userHandle) {
1178 int userId = userHandle.getIdentifier();
Chris Wren13f157f2017-05-12 15:02:06 -04001179 if (userId == UserHandle.USER_ALL) {
1180 return false;
1181 }
Chris Wren89aa2262017-05-05 18:05:56 -04001182 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1183 mBadgingEnabled.put(userId,
1184 Secure.getIntForUser(mContext.getContentResolver(),
1185 Secure.NOTIFICATION_BADGING,
1186 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1187 }
1188 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1189 }
1190
1191
John Spurlock1d881a12015-03-18 19:21:54 -04001192 private static class Record {
John Spurlock35ef0a62015-05-28 11:24:10 -04001193 static int UNKNOWN_UID = UserHandle.USER_NULL;
1194
John Spurlock1d881a12015-03-18 19:21:54 -04001195 String pkg;
John Spurlock35ef0a62015-05-28 11:24:10 -04001196 int uid = UNKNOWN_UID;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001197 int importance = DEFAULT_IMPORTANCE;
Julia Reynolds92d456e2016-01-25 16:36:59 -05001198 int priority = DEFAULT_PRIORITY;
1199 int visibility = DEFAULT_VISIBILITY;
Julia Reynolds924eed12017-01-19 09:52:07 -05001200 boolean showBadge = DEFAULT_SHOW_BADGE;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001201
1202 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Shunta Sato642b8d42017-06-13 16:16:13 +09001203 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynolds233a5f92015-10-19 13:51:23 -04001204 }
Chris Wren54bbef42014-07-09 18:37:56 -04001205}