blob: c0dccb53c08ac4ac5700300519bd53d5d9fc796b [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 Reynoldsd1bf5f02017-07-11 10:39:58 -040023import com.android.internal.util.XmlUtils;
Julia Reynolds85769912016-10-25 09:08:57 -040024
Kweku Adams887f09c2017-11-13 17:12:20 -080025import android.annotation.NonNull;
Chris Wren54bbef42014-07-09 18:37:56 -040026import android.app.Notification;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040027import android.app.NotificationChannel;
Julia Reynolds59e152e2017-01-25 17:42:53 -050028import android.app.NotificationChannelGroup;
Julia Reynolds85769912016-10-25 09:08:57 -040029import android.app.NotificationManager;
Chris Wren54bbef42014-07-09 18:37:56 -040030import android.content.Context;
Julia Reynolds85769912016-10-25 09:08:57 -040031import android.content.pm.ApplicationInfo;
John Spurlock35ef0a62015-05-28 11:24:10 -040032import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040034import android.content.pm.ParceledListSlice;
Julia Reynoldsd373d782017-03-03 13:32:57 -050035import android.metrics.LogMaker;
Julia Reynolds85769912016-10-25 09:08:57 -040036import android.os.Build;
Chris Wren54bbef42014-07-09 18:37:56 -040037import android.os.UserHandle;
Chris Wren89aa2262017-05-05 18:05:56 -040038import android.provider.Settings.Secure;
Julia Reynolds5d25ee72015-11-20 15:38:20 -050039import android.service.notification.NotificationListenerService.Ranking;
Kweku Adams62b42242017-09-25 12:54:02 -070040import android.service.notification.RankingHelperProto;
41import android.service.notification.RankingHelperProto.RecordProto;
Chris Wren54bbef42014-07-09 18:37:56 -040042import android.text.TextUtils;
43import android.util.ArrayMap;
Chris Wren54bbef42014-07-09 18:37:56 -040044import android.util.Slog;
Chris Wren89aa2262017-05-05 18:05:56 -040045import android.util.SparseBooleanArray;
Kweku Adams62b42242017-09-25 12:54:02 -070046import android.util.proto.ProtoOutputStream;
John Spurlock1d881a12015-03-18 19:21:54 -040047
Chris Wrenacf424a2016-03-15 12:48:55 -040048import org.json.JSONArray;
49import org.json.JSONException;
50import org.json.JSONObject;
Chris Wren54bbef42014-07-09 18:37:56 -040051import org.xmlpull.v1.XmlPullParser;
52import org.xmlpull.v1.XmlPullParserException;
53import org.xmlpull.v1.XmlSerializer;
54
55import java.io.IOException;
56import java.io.PrintWriter;
57import java.util.ArrayList;
Julia Reynoldse0b25742017-05-08 12:55:24 -040058import java.util.Arrays;
Julia Reynoldsf02562a2017-01-26 13:33:56 -050059import java.util.Collection;
Chris Wren54bbef42014-07-09 18:37:56 -040060import java.util.Collections;
Shunta Sato642b8d42017-06-13 16:16:13 +090061import java.util.concurrent.ConcurrentHashMap;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040062import java.util.List;
Chris Wrenacf424a2016-03-15 12:48:55 -040063import java.util.Map;
64import java.util.Map.Entry;
Julia Reynoldse0b25742017-05-08 12:55:24 -040065import java.util.Objects;
Chris Wren54bbef42014-07-09 18:37:56 -040066
67public class RankingHelper implements RankingConfig {
68 private static final String TAG = "RankingHelper";
Chris Wren54bbef42014-07-09 18:37:56 -040069
70 private static final int XML_VERSION = 1;
71
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040072 static final String TAG_RANKING = "ranking";
Chris Wren54bbef42014-07-09 18:37:56 -040073 private static final String TAG_PACKAGE = "package";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040074 private static final String TAG_CHANNEL = "channel";
Julia Reynolds59e152e2017-01-25 17:42:53 -050075 private static final String TAG_GROUP = "channelGroup";
Chris Wren54bbef42014-07-09 18:37:56 -040076
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040077 private static final String ATT_VERSION = "version";
Chris Wren54bbef42014-07-09 18:37:56 -040078 private static final String ATT_NAME = "name";
79 private static final String ATT_UID = "uid";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040080 private static final String ATT_ID = "id";
Chris Wren54bbef42014-07-09 18:37:56 -040081 private static final String ATT_PRIORITY = "priority";
Chris Wren3ad4e3a2014-09-02 17:23:51 -040082 private static final String ATT_VISIBILITY = "visibility";
Julia Reynolds5d25ee72015-11-20 15:38:20 -050083 private static final String ATT_IMPORTANCE = "importance";
Julia Reynolds924eed12017-01-19 09:52:07 -050084 private static final String ATT_SHOW_BADGE = "show_badge";
Chris Wren54bbef42014-07-09 18:37:56 -040085
John Spurlock1d881a12015-03-18 19:21:54 -040086 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
Julia Reynolds85769912016-10-25 09:08:57 -040087 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
88 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds924eed12017-01-19 09:52:07 -050089 private static final boolean DEFAULT_SHOW_BADGE = true;
John Spurlock1d881a12015-03-18 19:21:54 -040090
Chris Wren54bbef42014-07-09 18:37:56 -040091 private final NotificationSignalExtractor[] mSignalExtractors;
Julia Reynolds4a02afb2016-12-13 13:39:52 -050092 private final NotificationComparator mPreliminaryComparator;
Christoph Studercd4adf82014-08-19 17:50:49 +020093 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
Chris Wren54bbef42014-07-09 18:37:56 -040094
John Spurlock1d881a12015-03-18 19:21:54 -040095 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
96 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
John Spurlock35ef0a62015-05-28 11:24:10 -040097 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
Chris Wren54bbef42014-07-09 18:37:56 -040098
99 private final Context mContext;
Chris Wren51017d02015-12-15 15:34:46 -0500100 private final RankingHandler mRankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400101 private final PackageManager mPm;
Chris Wren89aa2262017-05-05 18:05:56 -0400102 private SparseBooleanArray mBadgingEnabled;
Chris Wren54bbef42014-07-09 18:37:56 -0400103
Julia Reynolds85769912016-10-25 09:08:57 -0400104 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
Chris Wren51017d02015-12-15 15:34:46 -0500105 NotificationUsageStats usageStats, String[] extractorNames) {
Chris Wren54bbef42014-07-09 18:37:56 -0400106 mContext = context;
107 mRankingHandler = rankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400108 mPm = pm;
Chris Wren54bbef42014-07-09 18:37:56 -0400109
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500110 mPreliminaryComparator = new NotificationComparator(mContext);
111
Chris Wren89aa2262017-05-05 18:05:56 -0400112 updateBadgingEnabled();
113
Chris Wren54bbef42014-07-09 18:37:56 -0400114 final int N = extractorNames.length;
115 mSignalExtractors = new NotificationSignalExtractor[N];
116 for (int i = 0; i < N; i++) {
117 try {
118 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
119 NotificationSignalExtractor extractor =
120 (NotificationSignalExtractor) extractorClass.newInstance();
Chris Wren5eab2b72015-06-16 13:56:22 -0400121 extractor.initialize(mContext, usageStats);
Chris Wren54bbef42014-07-09 18:37:56 -0400122 extractor.setConfig(this);
123 mSignalExtractors[i] = extractor;
124 } catch (ClassNotFoundException e) {
125 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
126 } catch (InstantiationException e) {
127 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
128 } catch (IllegalAccessException e) {
129 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
130 }
131 }
132 }
133
John Spurlock1d881a12015-03-18 19:21:54 -0400134 @SuppressWarnings("unchecked")
John Spurlock2b122f42014-08-27 16:29:47 -0400135 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
136 final int N = mSignalExtractors.length;
137 for (int i = 0; i < N; i++) {
138 final NotificationSignalExtractor extractor = mSignalExtractors[i];
139 if (extractorClass.equals(extractor.getClass())) {
140 return (T) extractor;
141 }
142 }
143 return null;
144 }
145
Chris Wren54bbef42014-07-09 18:37:56 -0400146 public void extractSignals(NotificationRecord r) {
147 final int N = mSignalExtractors.length;
148 for (int i = 0; i < N; i++) {
149 NotificationSignalExtractor extractor = mSignalExtractors[i];
150 try {
151 RankingReconsideration recon = extractor.process(r);
152 if (recon != null) {
Chris Wren51017d02015-12-15 15:34:46 -0500153 mRankingHandler.requestReconsideration(recon);
Chris Wren54bbef42014-07-09 18:37:56 -0400154 }
155 } catch (Throwable t) {
156 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
157 }
158 }
159 }
160
John Spurlock35ef0a62015-05-28 11:24:10 -0400161 public void readXml(XmlPullParser parser, boolean forRestore)
162 throws XmlPullParserException, IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400163 int type = parser.getEventType();
164 if (type != XmlPullParser.START_TAG) return;
165 String tag = parser.getName();
166 if (!TAG_RANKING.equals(tag)) return;
Geoffrey Pitsch6eccf002017-04-05 12:33:59 -0400167 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
168 // at the time of serialization.
John Spurlock35ef0a62015-05-28 11:24:10 -0400169 mRestoredWithoutUids.clear();
Chris Wren54bbef42014-07-09 18:37:56 -0400170 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
171 tag = parser.getName();
172 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
173 return;
174 }
175 if (type == XmlPullParser.START_TAG) {
176 if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400177 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
Chris Wren54bbef42014-07-09 18:37:56 -0400178 String name = parser.getAttributeValue(null, ATT_NAME);
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400179 if (!TextUtils.isEmpty(name)) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400180 if (forRestore) {
181 try {
Xiaohui Chenddbe4ca2015-08-13 16:20:56 -0700182 //TODO: http://b/22388012
Julia Reynolds85769912016-10-25 09:08:57 -0400183 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
John Spurlock35ef0a62015-05-28 11:24:10 -0400184 } catch (NameNotFoundException e) {
185 // noop
186 }
187 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400188
Julia Reynolds85769912016-10-25 09:08:57 -0400189 Record r = getOrCreateRecord(name, uid,
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400190 XmlUtils.readIntAttribute(
191 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
192 XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
193 XmlUtils.readIntAttribute(
194 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
195 XmlUtils.readBooleanAttribute(
196 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
197 r.importance = XmlUtils.readIntAttribute(
198 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
199 r.priority = XmlUtils.readIntAttribute(
200 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
201 r.visibility = XmlUtils.readIntAttribute(
202 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
203 r.showBadge = XmlUtils.readBooleanAttribute(
204 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400205
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400206 final int innerDepth = parser.getDepth();
207 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
208 && (type != XmlPullParser.END_TAG
209 || parser.getDepth() > innerDepth)) {
210 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
211 continue;
212 }
213
214 String tagName = parser.getName();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500215 // Channel groups
216 if (TAG_GROUP.equals(tagName)) {
217 String id = parser.getAttributeValue(null, ATT_ID);
218 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
219 if (!TextUtils.isEmpty(id)) {
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400220 NotificationChannelGroup group
221 = new NotificationChannelGroup(id, groupName);
Julia Reynolds005c8b92017-08-24 10:35:53 -0400222 group.populateFromXml(parser);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500223 r.groups.put(id, group);
224 }
225 }
226 // Channels
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400227 if (TAG_CHANNEL.equals(tagName)) {
228 String id = parser.getAttributeValue(null, ATT_ID);
Julia Reynolds2c891c92017-03-17 14:23:47 -0400229 String channelName = parser.getAttributeValue(null, ATT_NAME);
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400230 int channelImportance = XmlUtils.readIntAttribute(
Julia Reynoldsb852e562017-06-06 16:14:18 -0400231 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400232 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
233 NotificationChannel channel = new NotificationChannel(id,
234 channelName, channelImportance);
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100235 if (forRestore) {
236 channel.populateFromXmlForRestore(parser, mContext);
237 } else {
238 channel.populateFromXml(parser);
239 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400240 r.channels.put(id, channel);
241 }
242 }
243 }
Julia Reynolds85769912016-10-25 09:08:57 -0400244
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500245 try {
246 deleteDefaultChannelIfNeeded(r);
247 } catch (NameNotFoundException e) {
248 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
249 }
Chris Wren54bbef42014-07-09 18:37:56 -0400250 }
251 }
252 }
253 }
254 throw new IllegalStateException("Failed to reach END_DOCUMENT");
255 }
256
John Spurlock1d881a12015-03-18 19:21:54 -0400257 private static String recordKey(String pkg, int uid) {
258 return pkg + "|" + uid;
259 }
260
Julia Reynolds85769912016-10-25 09:08:57 -0400261 private Record getRecord(String pkg, int uid) {
John Spurlock1d881a12015-03-18 19:21:54 -0400262 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400263 synchronized (mRecords) {
264 return mRecords.get(key);
265 }
Julia Reynolds85769912016-10-25 09:08:57 -0400266 }
267
268 private Record getOrCreateRecord(String pkg, int uid) {
269 return getOrCreateRecord(pkg, uid,
Julia Reynolds924eed12017-01-19 09:52:07 -0500270 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400271 }
272
273 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
Julia Reynolds924eed12017-01-19 09:52:07 -0500274 int visibility, boolean showBadge) {
Julia Reynolds85769912016-10-25 09:08:57 -0400275 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400276 synchronized (mRecords) {
277 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
278 key);
279 if (r == null) {
280 r = new Record();
281 r.pkg = pkg;
282 r.uid = uid;
283 r.importance = importance;
284 r.priority = priority;
285 r.visibility = visibility;
286 r.showBadge = showBadge;
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500287
Julia Reynolds44011962017-04-14 12:29:04 -0400288 try {
289 createDefaultChannelIfNeeded(r);
290 } catch (NameNotFoundException e) {
291 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
292 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500293
Julia Reynolds44011962017-04-14 12:29:04 -0400294 if (r.uid == Record.UNKNOWN_UID) {
295 mRestoredWithoutUids.put(pkg, r);
296 } else {
297 mRecords.put(key, r);
298 }
Julia Reynolds85769912016-10-25 09:08:57 -0400299 }
Julia Reynolds44011962017-04-14 12:29:04 -0400300 return r;
John Spurlock1d881a12015-03-18 19:21:54 -0400301 }
John Spurlock1d881a12015-03-18 19:21:54 -0400302 }
303
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500304 private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
305 final int userId = UserHandle.getUserId(r.uid);
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000306 final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
Dan Sandlere103b782017-05-17 16:07:56 -0700307 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400308 // O apps should not have the default channel.
309 return false;
Julia Reynolds85769912016-10-25 09:08:57 -0400310 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500311
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400312 // Otherwise, this app should have the default channel.
313 return true;
Julia Reynolds85769912016-10-25 09:08:57 -0400314 }
315
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500316 private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
Julia Reynolds85769912016-10-25 09:08:57 -0400317 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500318 // Not present
319 return;
Julia Reynolds85769912016-10-25 09:08:57 -0400320 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500321
322 if (shouldHaveDefaultChannel(r)) {
323 // Keep the default channel until upgraded.
324 return;
325 }
326
327 // Remove Default Channel.
328 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
329 }
330
331 private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
332 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Julia Reynolds17717f52017-05-09 11:46:06 -0400333 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
334 mContext.getString(R.string.default_notification_channel_label));
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500335 return;
336 }
337
338 if (!shouldHaveDefaultChannel(r)) {
339 // Keep the default channel until upgraded.
340 return;
341 }
342
343 // Create Default Channel
344 NotificationChannel channel;
345 channel = new NotificationChannel(
346 NotificationChannel.DEFAULT_CHANNEL_ID,
347 mContext.getString(R.string.default_notification_channel_label),
348 r.importance);
349 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
350 channel.setLockscreenVisibility(r.visibility);
351 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
352 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
353 }
354 if (r.priority != DEFAULT_PRIORITY) {
355 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
356 }
357 if (r.visibility != DEFAULT_VISIBILITY) {
358 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
359 }
360 r.channels.put(channel.getId(), channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400361 }
362
John Spurlock35ef0a62015-05-28 11:24:10 -0400363 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400364 out.startTag(null, TAG_RANKING);
365 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
366
Julia Reynolds44011962017-04-14 12:29:04 -0400367 synchronized (mRecords) {
368 final int N = mRecords.size();
369 for (int i = 0; i < N; i++) {
370 final Record r = mRecords.valueAt(i);
371 //TODO: http://b/22388012
372 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
373 continue;
Julia Reynoldsef37f282016-02-12 09:11:27 -0500374 }
Julia Reynolds44011962017-04-14 12:29:04 -0400375 final boolean hasNonDefaultSettings = r.importance != DEFAULT_IMPORTANCE
376 || r.priority != DEFAULT_PRIORITY || r.visibility != DEFAULT_VISIBILITY
377 || r.showBadge != DEFAULT_SHOW_BADGE || r.channels.size() > 0
378 || r.groups.size() > 0;
379 if (hasNonDefaultSettings) {
380 out.startTag(null, TAG_PACKAGE);
381 out.attribute(null, ATT_NAME, r.pkg);
382 if (r.importance != DEFAULT_IMPORTANCE) {
383 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
Julia Reynoldsd0a5b162017-03-18 13:42:09 -0400384 }
Julia Reynolds44011962017-04-14 12:29:04 -0400385 if (r.priority != DEFAULT_PRIORITY) {
386 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
387 }
388 if (r.visibility != DEFAULT_VISIBILITY) {
389 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
390 }
391 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400392
Julia Reynolds44011962017-04-14 12:29:04 -0400393 if (!forBackup) {
394 out.attribute(null, ATT_UID, Integer.toString(r.uid));
395 }
396
397 for (NotificationChannelGroup group : r.groups.values()) {
398 group.writeXml(out);
399 }
400
401 for (NotificationChannel channel : r.channels.values()) {
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100402 if (forBackup) {
403 if (!channel.isDeleted()) {
404 channel.writeXmlForBackup(out, mContext);
405 }
406 } else {
Julia Reynolds44011962017-04-14 12:29:04 -0400407 channel.writeXml(out);
408 }
409 }
410
411 out.endTag(null, TAG_PACKAGE);
412 }
Julia Reynoldsef37f282016-02-12 09:11:27 -0500413 }
Chris Wren54bbef42014-07-09 18:37:56 -0400414 }
415 out.endTag(null, TAG_RANKING);
416 }
417
418 private void updateConfig() {
419 final int N = mSignalExtractors.length;
420 for (int i = 0; i < N; i++) {
421 mSignalExtractors[i].setConfig(this);
422 }
Julia Reynoldseb3dca72017-07-11 10:39:58 -0400423 mRankingHandler.requestSort();
Chris Wren54bbef42014-07-09 18:37:56 -0400424 }
425
426 public void sort(ArrayList<NotificationRecord> notificationList) {
Chris Wren1031c972014-07-23 13:11:45 +0000427 final int N = notificationList.size();
Christoph Studercd4adf82014-08-19 17:50:49 +0200428 // clear global sort keys
Chris Wren1031c972014-07-23 13:11:45 +0000429 for (int i = N - 1; i >= 0; i--) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200430 notificationList.get(i).setGlobalSortKey(null);
Chris Wren1031c972014-07-23 13:11:45 +0000431 }
432
Christoph Studer85374052014-10-08 11:19:55 -0700433 // rank each record individually
434 Collections.sort(notificationList, mPreliminaryComparator);
Christoph Studercd4adf82014-08-19 17:50:49 +0200435
436 synchronized (mProxyByGroupTmp) {
437 // record individual ranking result and nominate proxies for each group
438 for (int i = N - 1; i >= 0; i--) {
439 final NotificationRecord record = notificationList.get(i);
440 record.setAuthoritativeRank(i);
441 final String groupKey = record.getGroupKey();
Julia Reynolds3fb989b2017-02-28 16:06:55 -0500442 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
Julia Reynolds51710712017-07-19 13:48:07 -0400443 if (existingProxy == null) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200444 mProxyByGroupTmp.put(groupKey, record);
445 }
446 }
447 // assign global sort key:
448 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
449 for (int i = 0; i < N; i++) {
450 final NotificationRecord record = notificationList.get(i);
451 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
452 String groupSortKey = record.getNotification().getSortKey();
453
454 // We need to make sure the developer provided group sort key (gsk) is handled
455 // correctly:
456 // gsk="" < gsk=non-null-string < gsk=null
457 //
458 // We enforce this by using different prefixes for these three cases.
459 String groupSortKeyPortion;
460 if (groupSortKey == null) {
461 groupSortKeyPortion = "nsk";
462 } else if (groupSortKey.equals("")) {
463 groupSortKeyPortion = "esk";
464 } else {
465 groupSortKeyPortion = "gsk=" + groupSortKey;
466 }
467
468 boolean isGroupSummary = record.getNotification().isGroupSummary();
469 record.setGlobalSortKey(
470 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
Selim Cinek55a3e732017-05-25 18:30:10 -0700471 record.isRecentlyIntrusive()
472 && record.getImportance() > NotificationManager.IMPORTANCE_MIN
473 ? '0' : '1',
Christoph Studercd4adf82014-08-19 17:50:49 +0200474 groupProxy.getAuthoritativeRank(),
475 isGroupSummary ? '0' : '1',
476 groupSortKeyPortion,
477 record.getAuthoritativeRank()));
478 }
479 mProxyByGroupTmp.clear();
Chris Wren1031c972014-07-23 13:11:45 +0000480 }
Christoph Studercd4adf82014-08-19 17:50:49 +0200481
Chris Wren1031c972014-07-23 13:11:45 +0000482 // Do a second ranking pass, using group proxies
483 Collections.sort(notificationList, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400484 }
485
486 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
Chris Wren1031c972014-07-23 13:11:45 +0000487 return Collections.binarySearch(notificationList, target, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400488 }
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 Reynolds005c8b92017-08-24 10:35:53 -0400509 @Override
510 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
511 if (groupId == null) {
512 return false;
513 }
514 Record r = getOrCreateRecord(packageName, uid);
515 NotificationChannelGroup group = r.groups.get(groupId);
516 if (group == null) {
517 return false;
518 }
519 return group.isBlocked();
520 }
521
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400522 int getPackagePriority(String pkg, int uid) {
523 return getOrCreateRecord(pkg, uid).priority;
524 }
525
526 int getPackageVisibility(String pkg, int uid) {
527 return getOrCreateRecord(pkg, uid).visibility;
528 }
529
Julia Reynolds924eed12017-01-19 09:52:07 -0500530 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500531 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
532 boolean fromTargetApp) {
533 Preconditions.checkNotNull(pkg);
534 Preconditions.checkNotNull(group);
535 Preconditions.checkNotNull(group.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400536 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500537 Record r = getOrCreateRecord(pkg, uid);
538 if (r == null) {
539 throw new IllegalArgumentException("Invalid package");
540 }
Dan Sandler0171f572017-06-14 14:03:18 -0400541 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
542 if (!group.equals(oldGroup)) {
Julia Reynolds005c8b92017-08-24 10:35:53 -0400543 // will log for new entries as well as name/description changes
Dan Sandler0171f572017-06-14 14:03:18 -0400544 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
545 }
Julia Reynolds005c8b92017-08-24 10:35:53 -0400546 if (oldGroup != null) {
547 group.setChannels(oldGroup.getChannels());
548
549 if (fromTargetApp) {
550 group.setBlocked(oldGroup.isBlocked());
551 }
552 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500553 r.groups.put(group.getId(), group);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500554 }
555
556 @Override
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500557 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
558 boolean fromTargetApp) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500559 Preconditions.checkNotNull(pkg);
560 Preconditions.checkNotNull(channel);
561 Preconditions.checkNotNull(channel.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400562 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400563 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500564 if (r == null) {
565 throw new IllegalArgumentException("Invalid package");
566 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500567 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
568 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
569 }
Julia Reynolds03fa85d2017-03-06 15:14:50 -0500570 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
571 throw new IllegalArgumentException("Reserved id");
572 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500573
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500574 NotificationChannel existing = r.channels.get(channel.getId());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400575 // Keep most of the existing settings
Julia Reynolds3d91f112017-03-03 08:59:53 -0500576 if (existing != null && fromTargetApp) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500577 if (existing.isDeleted()) {
578 existing.setDeleted(false);
Dan Sandler0171f572017-06-14 14:03:18 -0400579
580 // log a resurrected channel as if it's new again
581 MetricsLogger.action(getChannelLog(channel, pkg).setType(
582 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500583 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500584
Julia Reynolds2c891c92017-03-17 14:23:47 -0400585 existing.setName(channel.getName().toString());
586 existing.setDescription(channel.getDescription());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400587 existing.setBlockableSystem(channel.isBlockableSystem());
Julia Reynolds005c8b92017-08-24 10:35:53 -0400588 if (existing.getGroup() == null) {
589 existing.setGroup(channel.getGroup());
590 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500591
Geoffrey Pitsch76a3aa02017-07-26 15:07:34 -0400592 // Apps are allowed to downgrade channel importance if the user has not changed any
593 // fields on this channel yet.
594 if (existing.getUserLockedFields() == 0 &&
595 channel.getImportance() < existing.getImportance()) {
596 existing.setImportance(channel.getImportance());
597 }
598
Julia Reynoldsd373d782017-03-03 13:32:57 -0500599 updateConfig();
Geoffrey Pitsch03533712017-01-05 10:30:07 -0500600 return;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400601 }
Julia Reynolds3e50bf62017-05-24 16:33:32 -0400602 if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE
Julia Reynolds85769912016-10-25 09:08:57 -0400603 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
604 throw new IllegalArgumentException("Invalid importance level");
605 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500606 // Reset fields that apps aren't allowed to set.
607 if (fromTargetApp) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500608 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
609 channel.setLockscreenVisibility(r.visibility);
610 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500611 clearLockedFields(channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400612 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
613 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
614 }
Julia Reynolds924eed12017-01-19 09:52:07 -0500615 if (!r.showBadge) {
616 channel.setShowBadge(false);
617 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400618 r.channels.put(channel.getId(), channel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500619 MetricsLogger.action(getChannelLog(channel, pkg).setType(
620 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400621 }
622
Julia Reynoldse0b25742017-05-08 12:55:24 -0400623 void clearLockedFields(NotificationChannel channel) {
624 channel.unlockFields(channel.getUserLockedFields());
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500625 }
626
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400627 @Override
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400628 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
629 boolean fromUser) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500630 Preconditions.checkNotNull(updatedChannel);
631 Preconditions.checkNotNull(updatedChannel.getId());
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400632 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500633 if (r == null) {
634 throw new IllegalArgumentException("Invalid package");
635 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400636 NotificationChannel channel = r.channels.get(updatedChannel.getId());
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500637 if (channel == null || channel.isDeleted()) {
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400638 throw new IllegalArgumentException("Channel does not exist");
639 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400640 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
641 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
642 }
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400643 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
644 updatedChannel.lockFields(channel.getUserLockedFields());
645 if (fromUser) {
646 lockFieldsForUpdate(channel, updatedChannel);
647 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400648 r.channels.put(updatedChannel.getId(), updatedChannel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500649
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400650 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
651 // copy settings to app level so they are inherited by new channels
652 // when the app migrates
653 r.importance = updatedChannel.getImportance();
654 r.priority = updatedChannel.canBypassDnd()
655 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
656 r.visibility = updatedChannel.getLockscreenVisibility();
657 r.showBadge = updatedChannel.canShowBadge();
658 }
659
Dan Sandler0171f572017-06-14 14:03:18 -0400660 if (!channel.equals(updatedChannel)) {
661 // only log if there are real changes
662 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
663 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400664 updateConfig();
665 }
666
667 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500668 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
669 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500670 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400671 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500672 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500673 return null;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500674 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400675 if (channelId == null) {
676 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
677 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500678 final NotificationChannel nc = r.channels.get(channelId);
679 if (nc != null && (includeDeleted || !nc.isDeleted())) {
680 return nc;
681 }
682 return null;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400683 }
684
685 @Override
686 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
Julia Reynolds85769912016-10-25 09:08:57 -0400687 Record r = getRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500688 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500689 return;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500690 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500691 NotificationChannel channel = r.channels.get(channelId);
692 if (channel != null) {
693 channel.setDeleted(true);
Julia Reynolds8e0eb372017-03-21 15:04:50 -0400694 LogMaker lm = getChannelLog(channel, pkg);
695 lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
696 MetricsLogger.action(lm);
Julia Reynolds85769912016-10-25 09:08:57 -0400697 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400698 }
699
700 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500701 @VisibleForTesting
702 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
703 Preconditions.checkNotNull(pkg);
704 Preconditions.checkNotNull(channelId);
705 Record r = getRecord(pkg, uid);
706 if (r == null) {
707 return;
708 }
709 r.channels.remove(channelId);
710 }
711
712 @Override
713 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
714 Preconditions.checkNotNull(pkg);
715 Record r = getRecord(pkg, uid);
716 if (r == null) {
717 return;
718 }
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000719 int N = r.channels.size() - 1;
720 for (int i = N; i >= 0; i--) {
721 String key = r.channels.keyAt(i);
722 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
723 r.channels.remove(key);
724 }
725 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500726 }
727
Julia Reynolds005c8b92017-08-24 10:35:53 -0400728 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
729 int uid, String groupId, boolean includeDeleted) {
730 Preconditions.checkNotNull(pkg);
731 Record r = getRecord(pkg, uid);
732 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
733 return null;
734 }
735 NotificationChannelGroup group = r.groups.get(groupId).clone();
736 group.setChannels(new ArrayList<>());
737 int N = r.channels.size();
738 for (int i = 0; i < N; i++) {
739 final NotificationChannel nc = r.channels.valueAt(i);
740 if (includeDeleted || !nc.isDeleted()) {
741 if (groupId.equals(nc.getGroup())) {
742 group.addChannel(nc);
743 }
744 }
745 }
746 return group;
747 }
748
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500749 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
750 int uid) {
751 Preconditions.checkNotNull(pkg);
752 Record r = getRecord(pkg, uid);
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500753 if (r == null) {
754 return null;
755 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500756 return r.groups.get(groupId);
757 }
758
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500759 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500760 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500761 int uid, boolean includeDeleted, boolean includeNonGrouped) {
Julia Reynolds59e152e2017-01-25 17:42:53 -0500762 Preconditions.checkNotNull(pkg);
Julia Reynolds74856c42017-02-08 14:47:23 -0500763 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500764 Record r = getRecord(pkg, uid);
765 if (r == null) {
766 return ParceledListSlice.emptyList();
767 }
768 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
769 int N = r.channels.size();
770 for (int i = 0; i < N; i++) {
771 final NotificationChannel nc = r.channels.valueAt(i);
772 if (includeDeleted || !nc.isDeleted()) {
773 if (nc.getGroup() != null) {
Julia Reynolds9bfba592017-03-15 14:03:55 -0400774 if (r.groups.get(nc.getGroup()) != null) {
775 NotificationChannelGroup ncg = groups.get(nc.getGroup());
776 if (ncg == null) {
777 ncg = r.groups.get(nc.getGroup()).clone();
Julia Reynolds005c8b92017-08-24 10:35:53 -0400778 ncg.setChannels(new ArrayList<>());
Julia Reynolds9bfba592017-03-15 14:03:55 -0400779 groups.put(nc.getGroup(), ncg);
780
781 }
782 ncg.addChannel(nc);
Julia Reynolds74856c42017-02-08 14:47:23 -0500783 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500784 } else {
785 nonGrouped.addChannel(nc);
786 }
787 }
788 }
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500789 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
Julia Reynolds74856c42017-02-08 14:47:23 -0500790 groups.put(null, nonGrouped);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500791 }
Julia Reynolds74856c42017-02-08 14:47:23 -0500792 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500793 }
794
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400795 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
Julia Reynolds9bfba592017-03-15 14:03:55 -0400796 String groupId) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400797 List<NotificationChannel> deletedChannels = new ArrayList<>();
Julia Reynolds9bfba592017-03-15 14:03:55 -0400798 Record r = getRecord(pkg, uid);
799 if (r == null || TextUtils.isEmpty(groupId)) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400800 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400801 }
802
803 r.groups.remove(groupId);
804
805 int N = r.channels.size();
806 for (int i = 0; i < N; i++) {
807 final NotificationChannel nc = r.channels.valueAt(i);
808 if (groupId.equals(nc.getGroup())) {
809 nc.setDeleted(true);
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400810 deletedChannels.add(nc);
Julia Reynolds9bfba592017-03-15 14:03:55 -0400811 }
812 }
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400813 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400814 }
815
Julia Reynolds59e152e2017-01-25 17:42:53 -0500816 @Override
Julia Reynoldsf02562a2017-01-26 13:33:56 -0500817 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
818 int uid) {
819 Record r = getRecord(pkg, uid);
820 if (r == null) {
821 return new ArrayList<>();
822 }
823 return r.groups.values();
824 }
825
826 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500827 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
828 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500829 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400830 List<NotificationChannel> channels = new ArrayList<>();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500831 Record r = getRecord(pkg, uid);
832 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500833 return ParceledListSlice.emptyList();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500834 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400835 int N = r.channels.size();
836 for (int i = 0; i < N; i++) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500837 final NotificationChannel nc = r.channels.valueAt(i);
838 if (includeDeleted || !nc.isDeleted()) {
839 channels.add(nc);
840 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400841 }
Julia Reynolds85769912016-10-25 09:08:57 -0400842 return new ParceledListSlice<>(channels);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400843 }
844
Julia Reynolds17717f52017-05-09 11:46:06 -0400845 /**
846 * True for pre-O apps that only have the default channel, or pre O apps that have no
847 * channels yet. This method will create the default channel for pre-O apps that don't have it.
848 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
849 * upgrades.
850 */
851 public boolean onlyHasDefaultChannel(String pkg, int uid) {
852 Record r = getOrCreateRecord(pkg, uid);
853 if (r.channels.size() == 1
854 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
855 return true;
856 }
857 return false;
858 }
859
Julia Reynolds41103f42017-03-15 11:36:35 -0400860 public int getDeletedChannelCount(String pkg, int uid) {
861 Preconditions.checkNotNull(pkg);
862 int deletedCount = 0;
863 Record r = getRecord(pkg, uid);
864 if (r == null) {
865 return deletedCount;
866 }
867 int N = r.channels.size();
868 for (int i = 0; i < N; i++) {
869 final NotificationChannel nc = r.channels.valueAt(i);
870 if (nc.isDeleted()) {
871 deletedCount++;
872 }
873 }
874 return deletedCount;
875 }
876
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500877 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -0500878 * Sets importance.
Julia Reynolds92d456e2016-01-25 16:36:59 -0500879 */
880 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -0500881 public void setImportance(String pkgName, int uid, int importance) {
882 getOrCreateRecord(pkgName, uid).importance = importance;
Julia Reynoldsa07af882015-12-17 08:32:48 -0500883 updateConfig();
884 }
885
Chris Wrenacf424a2016-03-15 12:48:55 -0400886 public void setEnabled(String packageName, int uid, boolean enabled) {
Julia Reynolds85769912016-10-25 09:08:57 -0400887 boolean wasEnabled = getImportance(packageName, uid) != NotificationManager.IMPORTANCE_NONE;
Chris Wrenacf424a2016-03-15 12:48:55 -0400888 if (wasEnabled == enabled) {
889 return;
890 }
Julia Reynolds85769912016-10-25 09:08:57 -0400891 setImportance(packageName, uid,
892 enabled ? DEFAULT_IMPORTANCE : NotificationManager.IMPORTANCE_NONE);
Chris Wrenacf424a2016-03-15 12:48:55 -0400893 }
894
Julia Reynoldse0b25742017-05-08 12:55:24 -0400895 @VisibleForTesting
896 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
Julia Reynoldse0b25742017-05-08 12:55:24 -0400897 if (original.canBypassDnd() != update.canBypassDnd()) {
898 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
899 }
900 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
901 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
902 }
903 if (original.getImportance() != update.getImportance()) {
904 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
905 }
906 if (original.shouldShowLights() != update.shouldShowLights()
907 || original.getLightColor() != update.getLightColor()) {
908 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
909 }
910 if (!Objects.equals(original.getSound(), update.getSound())) {
911 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
912 }
913 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
914 || original.shouldVibrate() != update.shouldVibrate()) {
915 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
916 }
917 if (original.canShowBadge() != update.canShowBadge()) {
918 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
919 }
920 }
921
Kweku Adams887f09c2017-11-13 17:12:20 -0800922 public void dump(PrintWriter pw, String prefix,
923 @NonNull NotificationManagerService.DumpFilter filter) {
924 final int N = mSignalExtractors.length;
925 pw.print(prefix);
926 pw.print("mSignalExtractors.length = ");
927 pw.println(N);
928 for (int i = 0; i < N; i++) {
Chris Wren54bbef42014-07-09 18:37:56 -0400929 pw.print(prefix);
Kweku Adams887f09c2017-11-13 17:12:20 -0800930 pw.print(" ");
931 pw.println(mSignalExtractors[i].getClass().getSimpleName());
Chris Wren54bbef42014-07-09 18:37:56 -0400932 }
Kweku Adams887f09c2017-11-13 17:12:20 -0800933
934 pw.print(prefix);
935 pw.println("per-package config:");
936
Julia Reynolds85aa6cb2016-01-08 17:49:11 -0500937 pw.println("Records:");
Julia Reynolds44011962017-04-14 12:29:04 -0400938 synchronized (mRecords) {
939 dumpRecords(pw, prefix, filter, mRecords);
940 }
Julia Reynolds85aa6cb2016-01-08 17:49:11 -0500941 pw.println("Restored without uid:");
John Spurlock35ef0a62015-05-28 11:24:10 -0400942 dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
943 }
944
Kweku Adams887f09c2017-11-13 17:12:20 -0800945 public void dump(ProtoOutputStream proto,
946 @NonNull NotificationManagerService.DumpFilter filter) {
Kweku Adams62b42242017-09-25 12:54:02 -0700947 final int N = mSignalExtractors.length;
948 for (int i = 0; i < N; i++) {
949 proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
950 mSignalExtractors[i].getClass().getSimpleName());
951 }
952 synchronized (mRecords) {
953 dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
954 }
955 dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
956 mRestoredWithoutUids);
957 }
958
959 private static void dumpRecords(ProtoOutputStream proto, long fieldId,
Kweku Adams887f09c2017-11-13 17:12:20 -0800960 @NonNull NotificationManagerService.DumpFilter filter,
961 ArrayMap<String, Record> records) {
Kweku Adams62b42242017-09-25 12:54:02 -0700962 final int N = records.size();
963 long fToken;
964 for (int i = 0; i < N; i++) {
965 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -0800966 if (filter.matches(r.pkg)) {
Kweku Adams62b42242017-09-25 12:54:02 -0700967 fToken = proto.start(fieldId);
968
969 proto.write(RecordProto.PACKAGE, r.pkg);
970 proto.write(RecordProto.UID, r.uid);
971 proto.write(RecordProto.IMPORTANCE, r.importance);
972 proto.write(RecordProto.PRIORITY, r.priority);
973 proto.write(RecordProto.VISIBILITY, r.visibility);
974 proto.write(RecordProto.SHOW_BADGE, r.showBadge);
975
976 long token;
977 for (NotificationChannel channel : r.channels.values()) {
978 token = proto.start(RecordProto.CHANNELS);
979 channel.toProto(proto);
980 proto.end(token);
981 }
982 for (NotificationChannelGroup group : r.groups.values()) {
983 token = proto.start(RecordProto.CHANNEL_GROUPS);
984 group.toProto(proto);
985 proto.end(token);
986 }
987
988 proto.end(fToken);
989 }
990 }
991 }
992
John Spurlock35ef0a62015-05-28 11:24:10 -0400993 private static void dumpRecords(PrintWriter pw, String prefix,
Kweku Adams887f09c2017-11-13 17:12:20 -0800994 @NonNull NotificationManagerService.DumpFilter filter,
995 ArrayMap<String, Record> records) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400996 final int N = records.size();
Chris Wren54bbef42014-07-09 18:37:56 -0400997 for (int i = 0; i < N; i++) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400998 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -0800999 if (filter.matches(r.pkg)) {
John Spurlock1d881a12015-03-18 19:21:54 -04001000 pw.print(prefix);
Julia Reynolds924eed12017-01-19 09:52:07 -05001001 pw.print(" AppSettings: ");
John Spurlock1d881a12015-03-18 19:21:54 -04001002 pw.print(r.pkg);
1003 pw.print(" (");
John Spurlock35ef0a62015-05-28 11:24:10 -04001004 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
John Spurlock1d881a12015-03-18 19:21:54 -04001005 pw.print(')');
Julia Reynolds92d456e2016-01-25 16:36:59 -05001006 if (r.importance != DEFAULT_IMPORTANCE) {
1007 pw.print(" importance=");
1008 pw.print(Ranking.importanceToString(r.importance));
1009 }
1010 if (r.priority != DEFAULT_PRIORITY) {
1011 pw.print(" priority=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001012 pw.print(Notification.priorityToString(r.priority));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001013 }
1014 if (r.visibility != DEFAULT_VISIBILITY) {
1015 pw.print(" visibility=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001016 pw.print(Notification.visibilityToString(r.visibility));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001017 }
Julia Reynolds924eed12017-01-19 09:52:07 -05001018 pw.print(" showBadge=");
1019 pw.print(Boolean.toString(r.showBadge));
John Spurlock1d881a12015-03-18 19:21:54 -04001020 pw.println();
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001021 for (NotificationChannel channel : r.channels.values()) {
1022 pw.print(prefix);
1023 pw.print(" ");
1024 pw.print(" ");
1025 pw.println(channel);
1026 }
Julia Reynolds59e152e2017-01-25 17:42:53 -05001027 for (NotificationChannelGroup group : r.groups.values()) {
1028 pw.print(prefix);
1029 pw.print(" ");
1030 pw.print(" ");
1031 pw.println(group);
1032 }
Chris Wren54bbef42014-07-09 18:37:56 -04001033 }
1034 }
1035 }
John Spurlock1d881a12015-03-18 19:21:54 -04001036
Chris Wrenacf424a2016-03-15 12:48:55 -04001037 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1038 JSONObject ranking = new JSONObject();
1039 JSONArray records = new JSONArray();
1040 try {
1041 ranking.put("noUid", mRestoredWithoutUids.size());
1042 } catch (JSONException e) {
1043 // pass
1044 }
Julia Reynolds44011962017-04-14 12:29:04 -04001045 synchronized (mRecords) {
1046 final int N = mRecords.size();
1047 for (int i = 0; i < N; i++) {
1048 final Record r = mRecords.valueAt(i);
1049 if (filter == null || filter.matches(r.pkg)) {
1050 JSONObject record = new JSONObject();
1051 try {
1052 record.put("userId", UserHandle.getUserId(r.uid));
1053 record.put("packageName", r.pkg);
1054 if (r.importance != DEFAULT_IMPORTANCE) {
1055 record.put("importance", Ranking.importanceToString(r.importance));
1056 }
1057 if (r.priority != DEFAULT_PRIORITY) {
1058 record.put("priority", Notification.priorityToString(r.priority));
1059 }
1060 if (r.visibility != DEFAULT_VISIBILITY) {
1061 record.put("visibility", Notification.visibilityToString(r.visibility));
1062 }
1063 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1064 record.put("showBadge", Boolean.valueOf(r.showBadge));
1065 }
1066 for (NotificationChannel channel : r.channels.values()) {
1067 record.put("channel", channel.toJson());
1068 }
1069 for (NotificationChannelGroup group : r.groups.values()) {
1070 record.put("group", group.toJson());
1071 }
1072 } catch (JSONException e) {
1073 // pass
Chris Wrenacf424a2016-03-15 12:48:55 -04001074 }
Julia Reynolds44011962017-04-14 12:29:04 -04001075 records.put(record);
Chris Wrenacf424a2016-03-15 12:48:55 -04001076 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001077 }
1078 }
1079 try {
1080 ranking.put("records", records);
1081 } catch (JSONException e) {
1082 // pass
1083 }
1084 return ranking;
1085 }
1086
1087 /**
1088 * Dump only the ban information as structured JSON for the stats collector.
1089 *
1090 * This is intentionally redundant with {#link dumpJson} because the old
1091 * scraper will expect this format.
1092 *
1093 * @param filter
1094 * @return
1095 */
1096 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1097 JSONArray bans = new JSONArray();
1098 Map<Integer, String> packageBans = getPackageBans();
1099 for(Entry<Integer, String> ban : packageBans.entrySet()) {
1100 final int userId = UserHandle.getUserId(ban.getKey());
1101 final String packageName = ban.getValue();
1102 if (filter == null || filter.matches(packageName)) {
1103 JSONObject banJson = new JSONObject();
1104 try {
1105 banJson.put("userId", userId);
1106 banJson.put("packageName", packageName);
1107 } catch (JSONException e) {
1108 e.printStackTrace();
1109 }
1110 bans.put(banJson);
1111 }
1112 }
1113 return bans;
1114 }
1115
1116 public Map<Integer, String> getPackageBans() {
Julia Reynolds44011962017-04-14 12:29:04 -04001117 synchronized (mRecords) {
1118 final int N = mRecords.size();
1119 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1120 for (int i = 0; i < N; i++) {
1121 final Record r = mRecords.valueAt(i);
1122 if (r.importance == NotificationManager.IMPORTANCE_NONE) {
1123 packageBans.put(r.uid, r.pkg);
1124 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001125 }
Julia Reynolds44011962017-04-14 12:29:04 -04001126
1127 return packageBans;
Chris Wrenacf424a2016-03-15 12:48:55 -04001128 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001129 }
1130
Julia Reynoldsd373d782017-03-03 13:32:57 -05001131 /**
1132 * Dump only the channel information as structured JSON for the stats collector.
1133 *
1134 * This is intentionally redundant with {#link dumpJson} because the old
1135 * scraper will expect this format.
1136 *
1137 * @param filter
1138 * @return
1139 */
1140 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1141 JSONArray channels = new JSONArray();
1142 Map<String, Integer> packageChannels = getPackageChannels();
1143 for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1144 final String packageName = channelCount.getKey();
1145 if (filter == null || filter.matches(packageName)) {
1146 JSONObject channelCountJson = new JSONObject();
1147 try {
1148 channelCountJson.put("packageName", packageName);
1149 channelCountJson.put("channelCount", channelCount.getValue());
1150 } catch (JSONException e) {
1151 e.printStackTrace();
1152 }
1153 channels.put(channelCountJson);
1154 }
1155 }
1156 return channels;
1157 }
1158
1159 private Map<String, Integer> getPackageChannels() {
1160 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynolds44011962017-04-14 12:29:04 -04001161 synchronized (mRecords) {
1162 for (int i = 0; i < mRecords.size(); i++) {
1163 final Record r = mRecords.valueAt(i);
1164 int channelCount = 0;
1165 for (int j = 0; j < r.channels.size(); j++) {
1166 if (!r.channels.valueAt(j).isDeleted()) {
1167 channelCount++;
1168 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001169 }
Julia Reynolds44011962017-04-14 12:29:04 -04001170 packageChannels.put(r.pkg, channelCount);
Julia Reynoldsd373d782017-03-03 13:32:57 -05001171 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001172 }
1173 return packageChannels;
1174 }
1175
Julia Reynolds2e9bf5f2017-05-03 13:23:30 -04001176 public void onUserRemoved(int userId) {
1177 synchronized (mRecords) {
1178 int N = mRecords.size();
1179 for (int i = N - 1; i >= 0 ; i--) {
1180 Record record = mRecords.valueAt(i);
1181 if (UserHandle.getUserId(record.uid) == userId) {
1182 mRecords.removeAt(i);
1183 }
1184 }
1185 }
1186 }
1187
Julia Reynolds816797a2017-08-11 15:47:09 -04001188 protected void onLocaleChanged(Context context, int userId) {
1189 synchronized (mRecords) {
1190 int N = mRecords.size();
1191 for (int i = 0; i < N; i++) {
1192 Record record = mRecords.valueAt(i);
1193 if (UserHandle.getUserId(record.uid) == userId) {
1194 if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1195 record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1196 context.getResources().getString(
1197 R.string.default_notification_channel_label));
1198 }
1199 }
1200 }
1201 }
1202 }
1203
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001204 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1205 int[] uidList) {
1206 if (pkgList == null || pkgList.length == 0) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001207 return; // nothing to do
1208 }
John Spurlock35ef0a62015-05-28 11:24:10 -04001209 boolean updated = false;
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001210 if (removingPackage) {
1211 // Remove notification settings for uninstalled package
1212 int size = Math.min(pkgList.length, uidList.length);
1213 for (int i = 0; i < size; i++) {
1214 final String pkg = pkgList[i];
1215 final int uid = uidList[i];
Julia Reynolds44011962017-04-14 12:29:04 -04001216 synchronized (mRecords) {
1217 mRecords.remove(recordKey(pkg, uid));
1218 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001219 mRestoredWithoutUids.remove(pkg);
1220 updated = true;
1221 }
1222 } else {
1223 for (String pkg : pkgList) {
1224 // Package install
1225 final Record r = mRestoredWithoutUids.get(pkg);
1226 if (r != null) {
1227 try {
1228 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1229 mRestoredWithoutUids.remove(pkg);
Julia Reynolds44011962017-04-14 12:29:04 -04001230 synchronized (mRecords) {
1231 mRecords.put(recordKey(r.pkg, r.uid), r);
1232 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001233 updated = true;
1234 } catch (NameNotFoundException e) {
1235 // noop
1236 }
1237 }
1238 // Package upgrade
John Spurlock35ef0a62015-05-28 11:24:10 -04001239 try {
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001240 Record fullRecord = getRecord(pkg,
1241 mPm.getPackageUidAsUser(pkg, changeUserId));
1242 if (fullRecord != null) {
Julia Reynoldsf26eb912017-05-22 15:47:06 -04001243 createDefaultChannelIfNeeded(fullRecord);
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001244 deleteDefaultChannelIfNeeded(fullRecord);
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001245 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001246 } catch (NameNotFoundException e) {}
John Spurlock35ef0a62015-05-28 11:24:10 -04001247 }
1248 }
Julia Reynolds1864ff52016-11-02 09:54:47 -04001249
John Spurlock35ef0a62015-05-28 11:24:10 -04001250 if (updated) {
1251 updateConfig();
1252 }
1253 }
1254
Julia Reynoldsd373d782017-03-03 13:32:57 -05001255 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1256 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
1257 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1258 .setPackageName(pkg)
1259 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
1260 channel.getId())
1261 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1262 channel.getImportance());
1263 }
1264
Dan Sandler0171f572017-06-14 14:03:18 -04001265 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1266 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
1267 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1268 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1269 groupId)
1270 .setPackageName(pkg);
1271 }
1272
Chris Wren89aa2262017-05-05 18:05:56 -04001273 public void updateBadgingEnabled() {
1274 if (mBadgingEnabled == null) {
1275 mBadgingEnabled = new SparseBooleanArray();
1276 }
1277 boolean changed = false;
1278 // update the cached values
1279 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1280 int userId = mBadgingEnabled.keyAt(index);
1281 final boolean oldValue = mBadgingEnabled.get(userId);
1282 final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
1283 Secure.NOTIFICATION_BADGING,
1284 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1285 mBadgingEnabled.put(userId, newValue);
1286 changed |= oldValue != newValue;
1287 }
1288 if (changed) {
Julia Reynoldsf5395772017-09-06 14:08:23 -04001289 updateConfig();
Chris Wren89aa2262017-05-05 18:05:56 -04001290 }
1291 }
1292
1293 public boolean badgingEnabled(UserHandle userHandle) {
1294 int userId = userHandle.getIdentifier();
Chris Wren13f157f2017-05-12 15:02:06 -04001295 if (userId == UserHandle.USER_ALL) {
1296 return false;
1297 }
Chris Wren89aa2262017-05-05 18:05:56 -04001298 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1299 mBadgingEnabled.put(userId,
1300 Secure.getIntForUser(mContext.getContentResolver(),
1301 Secure.NOTIFICATION_BADGING,
1302 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1303 }
1304 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1305 }
1306
1307
John Spurlock1d881a12015-03-18 19:21:54 -04001308 private static class Record {
John Spurlock35ef0a62015-05-28 11:24:10 -04001309 static int UNKNOWN_UID = UserHandle.USER_NULL;
1310
John Spurlock1d881a12015-03-18 19:21:54 -04001311 String pkg;
John Spurlock35ef0a62015-05-28 11:24:10 -04001312 int uid = UNKNOWN_UID;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001313 int importance = DEFAULT_IMPORTANCE;
Julia Reynolds92d456e2016-01-25 16:36:59 -05001314 int priority = DEFAULT_PRIORITY;
1315 int visibility = DEFAULT_VISIBILITY;
Julia Reynolds924eed12017-01-19 09:52:07 -05001316 boolean showBadge = DEFAULT_SHOW_BADGE;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001317
1318 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Shunta Sato642b8d42017-06-13 16:16:13 +09001319 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynolds233a5f92015-10-19 13:51:23 -04001320 }
Chris Wren54bbef42014-07-09 18:37:56 -04001321}