blob: 89bd6608f53dc3702bc56a1e2174fe9e384c6a3d [file] [log] [blame]
Chris Wren54bbef42014-07-09 18:37:56 -04001/**
2 * Copyright (c) 2014, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.server.notification;
17
Julia Reynoldsf2e499d2018-03-30 10:36:42 -040018import static android.app.NotificationManager.IMPORTANCE_NONE;
19
Julia Reynolds85769912016-10-25 09:08:57 -040020import com.android.internal.R;
Julia Reynolds4036e8d2017-01-13 09:50:05 -050021import com.android.internal.annotations.VisibleForTesting;
Julia Reynoldsd373d782017-03-03 13:32:57 -050022import com.android.internal.logging.MetricsLogger;
23import com.android.internal.logging.nano.MetricsProto;
Julia Reynolds52e64d02016-12-09 15:36:12 -050024import com.android.internal.util.Preconditions;
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040025import com.android.internal.util.XmlUtils;
Julia Reynolds85769912016-10-25 09:08:57 -040026
Rohan Shah590e1b22018-04-10 23:48:47 -040027import android.annotation.IntDef;
Kweku Adams887f09c2017-11-13 17:12:20 -080028import android.annotation.NonNull;
Chris Wren54bbef42014-07-09 18:37:56 -040029import android.app.Notification;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040030import android.app.NotificationChannel;
Julia Reynolds59e152e2017-01-25 17:42:53 -050031import android.app.NotificationChannelGroup;
Julia Reynolds85769912016-10-25 09:08:57 -040032import android.app.NotificationManager;
Chris Wren54bbef42014-07-09 18:37:56 -040033import android.content.Context;
Julia Reynolds85769912016-10-25 09:08:57 -040034import android.content.pm.ApplicationInfo;
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -040035import android.content.pm.PackageInfo;
John Spurlock35ef0a62015-05-28 11:24:10 -040036import android.content.pm.PackageManager;
37import android.content.pm.PackageManager.NameNotFoundException;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040038import android.content.pm.ParceledListSlice;
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -040039import android.content.pm.Signature;
Julia Reynoldsd373d782017-03-03 13:32:57 -050040import android.metrics.LogMaker;
Julia Reynolds85769912016-10-25 09:08:57 -040041import android.os.Build;
Chris Wren54bbef42014-07-09 18:37:56 -040042import android.os.UserHandle;
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -040043import android.print.PrintManager;
Chris Wren89aa2262017-05-05 18:05:56 -040044import android.provider.Settings.Secure;
Julia Reynolds5d25ee72015-11-20 15:38:20 -050045import android.service.notification.NotificationListenerService.Ranking;
Kweku Adams62b42242017-09-25 12:54:02 -070046import android.service.notification.RankingHelperProto;
47import android.service.notification.RankingHelperProto.RecordProto;
Chris Wren54bbef42014-07-09 18:37:56 -040048import android.text.TextUtils;
49import android.util.ArrayMap;
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -040050import android.util.Pair;
Chris Wren54bbef42014-07-09 18:37:56 -040051import android.util.Slog;
Chris Wren89aa2262017-05-05 18:05:56 -040052import android.util.SparseBooleanArray;
Kweku Adams62b42242017-09-25 12:54:02 -070053import android.util.proto.ProtoOutputStream;
John Spurlock1d881a12015-03-18 19:21:54 -040054
Chris Wrenacf424a2016-03-15 12:48:55 -040055import org.json.JSONArray;
56import org.json.JSONException;
57import org.json.JSONObject;
Chris Wren54bbef42014-07-09 18:37:56 -040058import org.xmlpull.v1.XmlPullParser;
59import org.xmlpull.v1.XmlPullParserException;
60import org.xmlpull.v1.XmlSerializer;
61
62import java.io.IOException;
63import java.io.PrintWriter;
64import java.util.ArrayList;
Julia Reynoldse0b25742017-05-08 12:55:24 -040065import java.util.Arrays;
Julia Reynoldsf02562a2017-01-26 13:33:56 -050066import java.util.Collection;
Chris Wren54bbef42014-07-09 18:37:56 -040067import java.util.Collections;
Shunta Sato642b8d42017-06-13 16:16:13 +090068import java.util.concurrent.ConcurrentHashMap;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040069import java.util.List;
Chris Wrenacf424a2016-03-15 12:48:55 -040070import java.util.Map;
71import java.util.Map.Entry;
Julia Reynoldse0b25742017-05-08 12:55:24 -040072import java.util.Objects;
Chris Wren54bbef42014-07-09 18:37:56 -040073
74public class RankingHelper implements RankingConfig {
75 private static final String TAG = "RankingHelper";
Chris Wren54bbef42014-07-09 18:37:56 -040076
77 private static final int XML_VERSION = 1;
78
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -040079 static final String TAG_RANKING = "ranking";
Chris Wren54bbef42014-07-09 18:37:56 -040080 private static final String TAG_PACKAGE = "package";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040081 private static final String TAG_CHANNEL = "channel";
Julia Reynolds59e152e2017-01-25 17:42:53 -050082 private static final String TAG_GROUP = "channelGroup";
Chris Wren54bbef42014-07-09 18:37:56 -040083
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040084 private static final String ATT_VERSION = "version";
Chris Wren54bbef42014-07-09 18:37:56 -040085 private static final String ATT_NAME = "name";
86 private static final String ATT_UID = "uid";
Julia Reynoldsb5e44b72016-08-16 15:00:25 -040087 private static final String ATT_ID = "id";
Chris Wren54bbef42014-07-09 18:37:56 -040088 private static final String ATT_PRIORITY = "priority";
Chris Wren3ad4e3a2014-09-02 17:23:51 -040089 private static final String ATT_VISIBILITY = "visibility";
Julia Reynolds5d25ee72015-11-20 15:38:20 -050090 private static final String ATT_IMPORTANCE = "importance";
Julia Reynolds924eed12017-01-19 09:52:07 -050091 private static final String ATT_SHOW_BADGE = "show_badge";
Rohan Shah590e1b22018-04-10 23:48:47 -040092 private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
Chris Wren54bbef42014-07-09 18:37:56 -040093
John Spurlock1d881a12015-03-18 19:21:54 -040094 private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
Julia Reynolds85769912016-10-25 09:08:57 -040095 private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
96 private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
Julia Reynolds924eed12017-01-19 09:52:07 -050097 private static final boolean DEFAULT_SHOW_BADGE = true;
Rohan Shah590e1b22018-04-10 23:48:47 -040098 /**
99 * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
100 * fields.
101 */
102 private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
103
104 /**
105 * All user-lockable fields for a given application.
106 */
107 @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
108 public @interface LockableAppFields {
109 int USER_LOCKED_IMPORTANCE = 0x00000001;
110 }
John Spurlock1d881a12015-03-18 19:21:54 -0400111
Chris Wren54bbef42014-07-09 18:37:56 -0400112 private final NotificationSignalExtractor[] mSignalExtractors;
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500113 private final NotificationComparator mPreliminaryComparator;
Christoph Studercd4adf82014-08-19 17:50:49 +0200114 private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
Chris Wren54bbef42014-07-09 18:37:56 -0400115
John Spurlock1d881a12015-03-18 19:21:54 -0400116 private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
117 private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
John Spurlock35ef0a62015-05-28 11:24:10 -0400118 private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400119 private final ArrayMap<Pair<String, Integer>, Boolean> mSystemAppCache = new ArrayMap<>();
Chris Wren54bbef42014-07-09 18:37:56 -0400120
121 private final Context mContext;
Chris Wren51017d02015-12-15 15:34:46 -0500122 private final RankingHandler mRankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400123 private final PackageManager mPm;
Chris Wren89aa2262017-05-05 18:05:56 -0400124 private SparseBooleanArray mBadgingEnabled;
Chris Wren54bbef42014-07-09 18:37:56 -0400125
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400126 private Signature[] mSystemSignature;
127 private String mPermissionControllerPackageName;
128 private String mServicesSystemSharedLibPackageName;
129 private String mSharedSystemSharedLibPackageName;
130
Julia Reynolds85769912016-10-25 09:08:57 -0400131 public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500132 ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
Chris Wren54bbef42014-07-09 18:37:56 -0400133 mContext = context;
134 mRankingHandler = rankingHandler;
Julia Reynolds85769912016-10-25 09:08:57 -0400135 mPm = pm;
Chris Wren54bbef42014-07-09 18:37:56 -0400136
Julia Reynolds4a02afb2016-12-13 13:39:52 -0500137 mPreliminaryComparator = new NotificationComparator(mContext);
138
Chris Wren89aa2262017-05-05 18:05:56 -0400139 updateBadgingEnabled();
140
Chris Wren54bbef42014-07-09 18:37:56 -0400141 final int N = extractorNames.length;
142 mSignalExtractors = new NotificationSignalExtractor[N];
143 for (int i = 0; i < N; i++) {
144 try {
145 Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
146 NotificationSignalExtractor extractor =
147 (NotificationSignalExtractor) extractorClass.newInstance();
Chris Wren5eab2b72015-06-16 13:56:22 -0400148 extractor.initialize(mContext, usageStats);
Chris Wren54bbef42014-07-09 18:37:56 -0400149 extractor.setConfig(this);
Julia Reynoldsc861a3d2018-02-15 10:34:49 -0500150 extractor.setZenHelper(zenHelper);
Chris Wren54bbef42014-07-09 18:37:56 -0400151 mSignalExtractors[i] = extractor;
152 } catch (ClassNotFoundException e) {
153 Slog.w(TAG, "Couldn't find extractor " + extractorNames[i] + ".", e);
154 } catch (InstantiationException e) {
155 Slog.w(TAG, "Couldn't instantiate extractor " + extractorNames[i] + ".", e);
156 } catch (IllegalAccessException e) {
157 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
158 }
159 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400160
161 getSignatures();
Chris Wren54bbef42014-07-09 18:37:56 -0400162 }
163
John Spurlock1d881a12015-03-18 19:21:54 -0400164 @SuppressWarnings("unchecked")
John Spurlock2b122f42014-08-27 16:29:47 -0400165 public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
166 final int N = mSignalExtractors.length;
167 for (int i = 0; i < N; i++) {
168 final NotificationSignalExtractor extractor = mSignalExtractors[i];
169 if (extractorClass.equals(extractor.getClass())) {
170 return (T) extractor;
171 }
172 }
173 return null;
174 }
175
Chris Wren54bbef42014-07-09 18:37:56 -0400176 public void extractSignals(NotificationRecord r) {
177 final int N = mSignalExtractors.length;
178 for (int i = 0; i < N; i++) {
179 NotificationSignalExtractor extractor = mSignalExtractors[i];
180 try {
181 RankingReconsideration recon = extractor.process(r);
182 if (recon != null) {
Chris Wren51017d02015-12-15 15:34:46 -0500183 mRankingHandler.requestReconsideration(recon);
Chris Wren54bbef42014-07-09 18:37:56 -0400184 }
185 } catch (Throwable t) {
186 Slog.w(TAG, "NotificationSignalExtractor failed.", t);
187 }
188 }
189 }
190
John Spurlock35ef0a62015-05-28 11:24:10 -0400191 public void readXml(XmlPullParser parser, boolean forRestore)
192 throws XmlPullParserException, IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400193 int type = parser.getEventType();
194 if (type != XmlPullParser.START_TAG) return;
195 String tag = parser.getName();
196 if (!TAG_RANKING.equals(tag)) return;
Geoffrey Pitsch6eccf002017-04-05 12:33:59 -0400197 // Clobber groups and channels with the xml, but don't delete other data that wasn't present
198 // at the time of serialization.
John Spurlock35ef0a62015-05-28 11:24:10 -0400199 mRestoredWithoutUids.clear();
Chris Wren54bbef42014-07-09 18:37:56 -0400200 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
201 tag = parser.getName();
202 if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
203 return;
204 }
205 if (type == XmlPullParser.START_TAG) {
206 if (TAG_PACKAGE.equals(tag)) {
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400207 int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
Chris Wren54bbef42014-07-09 18:37:56 -0400208 String name = parser.getAttributeValue(null, ATT_NAME);
Chris Wren3ad4e3a2014-09-02 17:23:51 -0400209 if (!TextUtils.isEmpty(name)) {
John Spurlock35ef0a62015-05-28 11:24:10 -0400210 if (forRestore) {
211 try {
Xiaohui Chenddbe4ca2015-08-13 16:20:56 -0700212 //TODO: http://b/22388012
Julia Reynolds85769912016-10-25 09:08:57 -0400213 uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
John Spurlock35ef0a62015-05-28 11:24:10 -0400214 } catch (NameNotFoundException e) {
215 // noop
216 }
217 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400218
Julia Reynolds85769912016-10-25 09:08:57 -0400219 Record r = getOrCreateRecord(name, uid,
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400220 XmlUtils.readIntAttribute(
221 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
222 XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
223 XmlUtils.readIntAttribute(
224 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
225 XmlUtils.readBooleanAttribute(
226 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
227 r.importance = XmlUtils.readIntAttribute(
228 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
229 r.priority = XmlUtils.readIntAttribute(
230 parser, ATT_PRIORITY, DEFAULT_PRIORITY);
231 r.visibility = XmlUtils.readIntAttribute(
232 parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
233 r.showBadge = XmlUtils.readBooleanAttribute(
234 parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
Rohan Shah590e1b22018-04-10 23:48:47 -0400235 r.lockedAppFields = XmlUtils.readIntAttribute(parser,
236 ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
Julia Reynolds85769912016-10-25 09:08:57 -0400237
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400238 final int innerDepth = parser.getDepth();
239 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
240 && (type != XmlPullParser.END_TAG
241 || parser.getDepth() > innerDepth)) {
242 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
243 continue;
244 }
245
246 String tagName = parser.getName();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500247 // Channel groups
248 if (TAG_GROUP.equals(tagName)) {
249 String id = parser.getAttributeValue(null, ATT_ID);
250 CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
251 if (!TextUtils.isEmpty(id)) {
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400252 NotificationChannelGroup group
253 = new NotificationChannelGroup(id, groupName);
Julia Reynolds005c8b92017-08-24 10:35:53 -0400254 group.populateFromXml(parser);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500255 r.groups.put(id, group);
256 }
257 }
258 // Channels
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400259 if (TAG_CHANNEL.equals(tagName)) {
260 String id = parser.getAttributeValue(null, ATT_ID);
Julia Reynolds2c891c92017-03-17 14:23:47 -0400261 String channelName = parser.getAttributeValue(null, ATT_NAME);
Julia Reynoldsd1bf5f02017-07-11 10:39:58 -0400262 int channelImportance = XmlUtils.readIntAttribute(
Julia Reynoldsb852e562017-06-06 16:14:18 -0400263 parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400264 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
265 NotificationChannel channel = new NotificationChannel(id,
266 channelName, channelImportance);
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100267 if (forRestore) {
268 channel.populateFromXmlForRestore(parser, mContext);
269 } else {
270 channel.populateFromXml(parser);
271 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400272 r.channels.put(id, channel);
273 }
274 }
275 }
Julia Reynolds85769912016-10-25 09:08:57 -0400276
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500277 try {
278 deleteDefaultChannelIfNeeded(r);
279 } catch (NameNotFoundException e) {
280 Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
281 }
Chris Wren54bbef42014-07-09 18:37:56 -0400282 }
283 }
284 }
285 }
286 throw new IllegalStateException("Failed to reach END_DOCUMENT");
287 }
288
John Spurlock1d881a12015-03-18 19:21:54 -0400289 private static String recordKey(String pkg, int uid) {
290 return pkg + "|" + uid;
291 }
292
Julia Reynolds85769912016-10-25 09:08:57 -0400293 private Record getRecord(String pkg, int uid) {
John Spurlock1d881a12015-03-18 19:21:54 -0400294 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400295 synchronized (mRecords) {
296 return mRecords.get(key);
297 }
Julia Reynolds85769912016-10-25 09:08:57 -0400298 }
299
300 private Record getOrCreateRecord(String pkg, int uid) {
301 return getOrCreateRecord(pkg, uid,
Julia Reynolds924eed12017-01-19 09:52:07 -0500302 DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
Julia Reynolds85769912016-10-25 09:08:57 -0400303 }
304
305 private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
Julia Reynolds924eed12017-01-19 09:52:07 -0500306 int visibility, boolean showBadge) {
Julia Reynolds85769912016-10-25 09:08:57 -0400307 final String key = recordKey(pkg, uid);
Julia Reynolds44011962017-04-14 12:29:04 -0400308 synchronized (mRecords) {
309 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
310 key);
311 if (r == null) {
312 r = new Record();
313 r.pkg = pkg;
314 r.uid = uid;
315 r.importance = importance;
316 r.priority = priority;
317 r.visibility = visibility;
318 r.showBadge = showBadge;
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500319
Julia Reynolds44011962017-04-14 12:29:04 -0400320 try {
321 createDefaultChannelIfNeeded(r);
322 } catch (NameNotFoundException e) {
323 Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
324 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500325
Julia Reynolds44011962017-04-14 12:29:04 -0400326 if (r.uid == Record.UNKNOWN_UID) {
327 mRestoredWithoutUids.put(pkg, r);
328 } else {
329 mRecords.put(key, r);
330 }
Julia Reynolds85769912016-10-25 09:08:57 -0400331 }
Julia Reynolds44011962017-04-14 12:29:04 -0400332 return r;
John Spurlock1d881a12015-03-18 19:21:54 -0400333 }
John Spurlock1d881a12015-03-18 19:21:54 -0400334 }
335
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500336 private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
337 final int userId = UserHandle.getUserId(r.uid);
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000338 final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
Dan Sandlere103b782017-05-17 16:07:56 -0700339 if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400340 // O apps should not have the default channel.
341 return false;
Julia Reynolds85769912016-10-25 09:08:57 -0400342 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500343
Geoffrey Pitsch5644ccd2017-04-17 11:33:24 -0400344 // Otherwise, this app should have the default channel.
345 return true;
Julia Reynolds85769912016-10-25 09:08:57 -0400346 }
347
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500348 private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
Julia Reynolds85769912016-10-25 09:08:57 -0400349 if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500350 // Not present
351 return;
Julia Reynolds85769912016-10-25 09:08:57 -0400352 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500353
354 if (shouldHaveDefaultChannel(r)) {
355 // Keep the default channel until upgraded.
356 return;
357 }
358
359 // Remove Default Channel.
360 r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
361 }
362
363 private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
364 if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
Julia Reynolds17717f52017-05-09 11:46:06 -0400365 r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
366 mContext.getString(R.string.default_notification_channel_label));
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -0500367 return;
368 }
369
370 if (!shouldHaveDefaultChannel(r)) {
371 // Keep the default channel until upgraded.
372 return;
373 }
374
375 // Create Default Channel
376 NotificationChannel channel;
377 channel = new NotificationChannel(
378 NotificationChannel.DEFAULT_CHANNEL_ID,
379 mContext.getString(R.string.default_notification_channel_label),
380 r.importance);
381 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
382 channel.setLockscreenVisibility(r.visibility);
383 if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
384 channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
385 }
386 if (r.priority != DEFAULT_PRIORITY) {
387 channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
388 }
389 if (r.visibility != DEFAULT_VISIBILITY) {
390 channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
391 }
392 r.channels.put(channel.getId(), channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400393 }
394
John Spurlock35ef0a62015-05-28 11:24:10 -0400395 public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
Chris Wren54bbef42014-07-09 18:37:56 -0400396 out.startTag(null, TAG_RANKING);
397 out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
398
Julia Reynolds44011962017-04-14 12:29:04 -0400399 synchronized (mRecords) {
400 final int N = mRecords.size();
401 for (int i = 0; i < N; i++) {
402 final Record r = mRecords.valueAt(i);
403 //TODO: http://b/22388012
404 if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
405 continue;
Julia Reynoldsef37f282016-02-12 09:11:27 -0500406 }
Rohan Shah590e1b22018-04-10 23:48:47 -0400407 final boolean hasNonDefaultSettings =
408 r.importance != DEFAULT_IMPORTANCE
409 || r.priority != DEFAULT_PRIORITY
410 || r.visibility != DEFAULT_VISIBILITY
411 || r.showBadge != DEFAULT_SHOW_BADGE
412 || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
413 || r.channels.size() > 0
414 || r.groups.size() > 0;
Julia Reynolds44011962017-04-14 12:29:04 -0400415 if (hasNonDefaultSettings) {
416 out.startTag(null, TAG_PACKAGE);
417 out.attribute(null, ATT_NAME, r.pkg);
418 if (r.importance != DEFAULT_IMPORTANCE) {
419 out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
Julia Reynoldsd0a5b162017-03-18 13:42:09 -0400420 }
Julia Reynolds44011962017-04-14 12:29:04 -0400421 if (r.priority != DEFAULT_PRIORITY) {
422 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
423 }
424 if (r.visibility != DEFAULT_VISIBILITY) {
425 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
426 }
427 out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
Rohan Shah590e1b22018-04-10 23:48:47 -0400428 out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
429 Integer.toString(r.lockedAppFields));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400430
Julia Reynolds44011962017-04-14 12:29:04 -0400431 if (!forBackup) {
432 out.attribute(null, ATT_UID, Integer.toString(r.uid));
433 }
434
435 for (NotificationChannelGroup group : r.groups.values()) {
436 group.writeXml(out);
437 }
438
439 for (NotificationChannel channel : r.channels.values()) {
Bernardo Rufinoc27bb6a2017-10-03 13:55:10 +0100440 if (forBackup) {
441 if (!channel.isDeleted()) {
442 channel.writeXmlForBackup(out, mContext);
443 }
444 } else {
Julia Reynolds44011962017-04-14 12:29:04 -0400445 channel.writeXml(out);
446 }
447 }
448
449 out.endTag(null, TAG_PACKAGE);
450 }
Julia Reynoldsef37f282016-02-12 09:11:27 -0500451 }
Chris Wren54bbef42014-07-09 18:37:56 -0400452 }
453 out.endTag(null, TAG_RANKING);
454 }
455
456 private void updateConfig() {
457 final int N = mSignalExtractors.length;
458 for (int i = 0; i < N; i++) {
459 mSignalExtractors[i].setConfig(this);
460 }
Julia Reynoldseb3dca72017-07-11 10:39:58 -0400461 mRankingHandler.requestSort();
Chris Wren54bbef42014-07-09 18:37:56 -0400462 }
463
464 public void sort(ArrayList<NotificationRecord> notificationList) {
Chris Wren1031c972014-07-23 13:11:45 +0000465 final int N = notificationList.size();
Christoph Studercd4adf82014-08-19 17:50:49 +0200466 // clear global sort keys
Chris Wren1031c972014-07-23 13:11:45 +0000467 for (int i = N - 1; i >= 0; i--) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200468 notificationList.get(i).setGlobalSortKey(null);
Chris Wren1031c972014-07-23 13:11:45 +0000469 }
470
Christoph Studer85374052014-10-08 11:19:55 -0700471 // rank each record individually
472 Collections.sort(notificationList, mPreliminaryComparator);
Christoph Studercd4adf82014-08-19 17:50:49 +0200473
474 synchronized (mProxyByGroupTmp) {
475 // record individual ranking result and nominate proxies for each group
476 for (int i = N - 1; i >= 0; i--) {
477 final NotificationRecord record = notificationList.get(i);
478 record.setAuthoritativeRank(i);
479 final String groupKey = record.getGroupKey();
Julia Reynolds3fb989b2017-02-28 16:06:55 -0500480 NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
Julia Reynolds51710712017-07-19 13:48:07 -0400481 if (existingProxy == null) {
Christoph Studercd4adf82014-08-19 17:50:49 +0200482 mProxyByGroupTmp.put(groupKey, record);
483 }
484 }
485 // assign global sort key:
486 // is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
487 for (int i = 0; i < N; i++) {
488 final NotificationRecord record = notificationList.get(i);
489 NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
490 String groupSortKey = record.getNotification().getSortKey();
491
492 // We need to make sure the developer provided group sort key (gsk) is handled
493 // correctly:
494 // gsk="" < gsk=non-null-string < gsk=null
495 //
496 // We enforce this by using different prefixes for these three cases.
497 String groupSortKeyPortion;
498 if (groupSortKey == null) {
499 groupSortKeyPortion = "nsk";
500 } else if (groupSortKey.equals("")) {
501 groupSortKeyPortion = "esk";
502 } else {
503 groupSortKeyPortion = "gsk=" + groupSortKey;
504 }
505
506 boolean isGroupSummary = record.getNotification().isGroupSummary();
507 record.setGlobalSortKey(
508 String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
Selim Cinek55a3e732017-05-25 18:30:10 -0700509 record.isRecentlyIntrusive()
510 && record.getImportance() > NotificationManager.IMPORTANCE_MIN
511 ? '0' : '1',
Christoph Studercd4adf82014-08-19 17:50:49 +0200512 groupProxy.getAuthoritativeRank(),
513 isGroupSummary ? '0' : '1',
514 groupSortKeyPortion,
515 record.getAuthoritativeRank()));
516 }
517 mProxyByGroupTmp.clear();
Chris Wren1031c972014-07-23 13:11:45 +0000518 }
Christoph Studercd4adf82014-08-19 17:50:49 +0200519
Chris Wren1031c972014-07-23 13:11:45 +0000520 // Do a second ranking pass, using group proxies
521 Collections.sort(notificationList, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400522 }
523
524 public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
Chris Wren1031c972014-07-23 13:11:45 +0000525 return Collections.binarySearch(notificationList, target, mFinalComparator);
Chris Wren54bbef42014-07-09 18:37:56 -0400526 }
527
Julia Reynoldsef37f282016-02-12 09:11:27 -0500528 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -0500529 * Gets importance.
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500530 */
531 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -0500532 public int getImportance(String packageName, int uid) {
533 return getOrCreateRecord(packageName, uid).importance;
Julia Reynolds81afbcd2016-02-09 14:54:08 -0500534 }
535
Rohan Shah590e1b22018-04-10 23:48:47 -0400536
537 /**
538 * Returns whether the importance of the corresponding notification is user-locked and shouldn't
539 * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
540 * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
541 */
542 public boolean getIsAppImportanceLocked(String packageName, int uid) {
543 int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
544 return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
545 }
546
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400547 @Override
Julia Reynolds924eed12017-01-19 09:52:07 -0500548 public boolean canShowBadge(String packageName, int uid) {
549 return getOrCreateRecord(packageName, uid).showBadge;
550 }
551
552 @Override
553 public void setShowBadge(String packageName, int uid, boolean showBadge) {
554 getOrCreateRecord(packageName, uid).showBadge = showBadge;
555 updateConfig();
556 }
557
Julia Reynolds005c8b92017-08-24 10:35:53 -0400558 @Override
559 public boolean isGroupBlocked(String packageName, int uid, String groupId) {
560 if (groupId == null) {
561 return false;
562 }
563 Record r = getOrCreateRecord(packageName, uid);
564 NotificationChannelGroup group = r.groups.get(groupId);
565 if (group == null) {
566 return false;
567 }
568 return group.isBlocked();
569 }
570
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400571 int getPackagePriority(String pkg, int uid) {
572 return getOrCreateRecord(pkg, uid).priority;
573 }
574
575 int getPackageVisibility(String pkg, int uid) {
576 return getOrCreateRecord(pkg, uid).visibility;
577 }
578
Julia Reynolds924eed12017-01-19 09:52:07 -0500579 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500580 public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
581 boolean fromTargetApp) {
582 Preconditions.checkNotNull(pkg);
583 Preconditions.checkNotNull(group);
584 Preconditions.checkNotNull(group.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400585 Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500586 Record r = getOrCreateRecord(pkg, uid);
587 if (r == null) {
588 throw new IllegalArgumentException("Invalid package");
589 }
Dan Sandler0171f572017-06-14 14:03:18 -0400590 final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
591 if (!group.equals(oldGroup)) {
Julia Reynolds005c8b92017-08-24 10:35:53 -0400592 // will log for new entries as well as name/description changes
Dan Sandler0171f572017-06-14 14:03:18 -0400593 MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
594 }
Julia Reynolds005c8b92017-08-24 10:35:53 -0400595 if (oldGroup != null) {
596 group.setChannels(oldGroup.getChannels());
597
598 if (fromTargetApp) {
599 group.setBlocked(oldGroup.isBlocked());
600 }
601 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500602 r.groups.put(group.getId(), group);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500603 }
604
605 @Override
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500606 public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
Julia Reynolds1fe10942018-03-28 12:46:51 -0400607 boolean fromTargetApp, boolean hasDndAccess) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500608 Preconditions.checkNotNull(pkg);
609 Preconditions.checkNotNull(channel);
610 Preconditions.checkNotNull(channel.getId());
Julia Reynolds1d97e6a2017-03-13 15:05:40 -0400611 Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400612 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500613 if (r == null) {
614 throw new IllegalArgumentException("Invalid package");
615 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500616 if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
617 throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
618 }
Julia Reynolds03fa85d2017-03-06 15:14:50 -0500619 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
620 throw new IllegalArgumentException("Reserved id");
621 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400622 final boolean isSystemApp = isSystemPackage(pkg, uid);
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500623 NotificationChannel existing = r.channels.get(channel.getId());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400624 // Keep most of the existing settings
Julia Reynolds3d91f112017-03-03 08:59:53 -0500625 if (existing != null && fromTargetApp) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500626 if (existing.isDeleted()) {
627 existing.setDeleted(false);
Dan Sandler0171f572017-06-14 14:03:18 -0400628
629 // log a resurrected channel as if it's new again
630 MetricsLogger.action(getChannelLog(channel, pkg).setType(
631 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500632 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500633
Julia Reynolds2c891c92017-03-17 14:23:47 -0400634 existing.setName(channel.getName().toString());
635 existing.setDescription(channel.getDescription());
Julia Reynoldsf7321592017-05-24 16:09:19 -0400636 existing.setBlockableSystem(channel.isBlockableSystem());
Julia Reynolds005c8b92017-08-24 10:35:53 -0400637 if (existing.getGroup() == null) {
638 existing.setGroup(channel.getGroup());
639 }
Julia Reynoldsd373d782017-03-03 13:32:57 -0500640
Geoffrey Pitsch76a3aa02017-07-26 15:07:34 -0400641 // Apps are allowed to downgrade channel importance if the user has not changed any
642 // fields on this channel yet.
643 if (existing.getUserLockedFields() == 0 &&
644 channel.getImportance() < existing.getImportance()) {
645 existing.setImportance(channel.getImportance());
646 }
647
Julia Reynolds1fe10942018-03-28 12:46:51 -0400648 // system apps and dnd access apps can bypass dnd if the user hasn't changed any
649 // fields on the channel yet
650 if (existing.getUserLockedFields() == 0 && (isSystemApp || hasDndAccess)) {
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400651 existing.setBypassDnd(channel.canBypassDnd());
652 }
653
Julia Reynoldsd373d782017-03-03 13:32:57 -0500654 updateConfig();
Geoffrey Pitsch03533712017-01-05 10:30:07 -0500655 return;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400656 }
Julia Reynoldsf2e499d2018-03-30 10:36:42 -0400657 if (channel.getImportance() < IMPORTANCE_NONE
Julia Reynolds85769912016-10-25 09:08:57 -0400658 || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
659 throw new IllegalArgumentException("Invalid importance level");
660 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400661
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500662 // Reset fields that apps aren't allowed to set.
Julia Reynolds1fe10942018-03-28 12:46:51 -0400663 if (fromTargetApp && !(isSystemApp || hasDndAccess)) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500664 channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400665 }
666 if (fromTargetApp) {
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500667 channel.setLockscreenVisibility(r.visibility);
668 }
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500669 clearLockedFields(channel);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400670 if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
671 channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
672 }
Julia Reynolds924eed12017-01-19 09:52:07 -0500673 if (!r.showBadge) {
674 channel.setShowBadge(false);
675 }
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400676
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400677 r.channels.put(channel.getId(), channel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500678 MetricsLogger.action(getChannelLog(channel, pkg).setType(
679 MetricsProto.MetricsEvent.TYPE_OPEN));
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400680 }
681
Julia Reynoldse0b25742017-05-08 12:55:24 -0400682 void clearLockedFields(NotificationChannel channel) {
683 channel.unlockFields(channel.getUserLockedFields());
Julia Reynoldsbaff4002016-12-15 11:34:26 -0500684 }
685
Julia Reynoldsbcc62ea2018-03-19 10:26:32 -0400686 /**
687 * Determine whether a package is a "system package", in which case certain things (like
688 * bypassing DND) should be allowed.
689 */
690 private boolean isSystemPackage(String pkg, int uid) {
691 Pair<String, Integer> app = new Pair(pkg, uid);
692 if (mSystemAppCache.containsKey(app)) {
693 return mSystemAppCache.get(app);
694 }
695
696 PackageInfo pi;
697 try {
698 pi = mPm.getPackageInfoAsUser(
699 pkg, PackageManager.GET_SIGNATURES, UserHandle.getUserId(uid));
700 } catch (NameNotFoundException e) {
701 Slog.w(TAG, "Can't find pkg", e);
702 return false;
703 }
704 boolean isSystem = (mSystemSignature[0] != null
705 && mSystemSignature[0].equals(getFirstSignature(pi)))
706 || pkg.equals(mPermissionControllerPackageName)
707 || pkg.equals(mServicesSystemSharedLibPackageName)
708 || pkg.equals(mSharedSystemSharedLibPackageName)
709 || pkg.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
710 || isDeviceProvisioningPackage(pkg);
711 mSystemAppCache.put(app, isSystem);
712 return isSystem;
713 }
714
715 private Signature getFirstSignature(PackageInfo pkg) {
716 if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
717 return pkg.signatures[0];
718 }
719 return null;
720 }
721
722 private Signature getSystemSignature() {
723 try {
724 final PackageInfo sys = mPm.getPackageInfoAsUser(
725 "android", PackageManager.GET_SIGNATURES, UserHandle.USER_SYSTEM);
726 return getFirstSignature(sys);
727 } catch (NameNotFoundException e) {
728 }
729 return null;
730 }
731
732 private boolean isDeviceProvisioningPackage(String packageName) {
733 String deviceProvisioningPackage = mContext.getResources().getString(
734 com.android.internal.R.string.config_deviceProvisioningPackage);
735 return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
736 }
737
738 private void getSignatures() {
739 mSystemSignature = new Signature[]{getSystemSignature()};
740 mPermissionControllerPackageName = mPm.getPermissionControllerPackageName();
741 mServicesSystemSharedLibPackageName = mPm.getServicesSystemSharedLibraryPackageName();
742 mSharedSystemSharedLibPackageName = mPm.getSharedSystemSharedLibraryPackageName();
743 }
744
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400745 @Override
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400746 public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
747 boolean fromUser) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500748 Preconditions.checkNotNull(updatedChannel);
749 Preconditions.checkNotNull(updatedChannel.getId());
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400750 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500751 if (r == null) {
752 throw new IllegalArgumentException("Invalid package");
753 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400754 NotificationChannel channel = r.channels.get(updatedChannel.getId());
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500755 if (channel == null || channel.isDeleted()) {
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400756 throw new IllegalArgumentException("Channel does not exist");
757 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400758 if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
759 updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
760 }
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500761 if (!fromUser) {
762 updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
763 }
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400764 if (fromUser) {
Julia Reynoldsc65656a2018-02-12 09:55:14 -0500765 updatedChannel.lockFields(channel.getUserLockedFields());
Julia Reynolds8617e4e2017-09-18 16:52:37 -0400766 lockFieldsForUpdate(channel, updatedChannel);
767 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400768 r.channels.put(updatedChannel.getId(), updatedChannel);
Julia Reynoldsd373d782017-03-03 13:32:57 -0500769
Julia Reynoldsfa04e142017-04-23 13:32:01 -0400770 if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
771 // copy settings to app level so they are inherited by new channels
772 // when the app migrates
773 r.importance = updatedChannel.getImportance();
774 r.priority = updatedChannel.canBypassDnd()
775 ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
776 r.visibility = updatedChannel.getLockscreenVisibility();
777 r.showBadge = updatedChannel.canShowBadge();
778 }
779
Dan Sandler0171f572017-06-14 14:03:18 -0400780 if (!channel.equals(updatedChannel)) {
781 // only log if there are real changes
782 MetricsLogger.action(getChannelLog(updatedChannel, pkg));
783 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400784 updateConfig();
785 }
786
787 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500788 public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
789 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500790 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400791 Record r = getOrCreateRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500792 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500793 return null;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500794 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400795 if (channelId == null) {
796 channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
797 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500798 final NotificationChannel nc = r.channels.get(channelId);
799 if (nc != null && (includeDeleted || !nc.isDeleted())) {
800 return nc;
801 }
802 return null;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400803 }
804
805 @Override
806 public void deleteNotificationChannel(String pkg, int uid, String channelId) {
Julia Reynolds85769912016-10-25 09:08:57 -0400807 Record r = getRecord(pkg, uid);
Julia Reynolds52e64d02016-12-09 15:36:12 -0500808 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500809 return;
Julia Reynolds52e64d02016-12-09 15:36:12 -0500810 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500811 NotificationChannel channel = r.channels.get(channelId);
812 if (channel != null) {
813 channel.setDeleted(true);
Julia Reynolds8e0eb372017-03-21 15:04:50 -0400814 LogMaker lm = getChannelLog(channel, pkg);
815 lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
816 MetricsLogger.action(lm);
Julia Reynolds85769912016-10-25 09:08:57 -0400817 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400818 }
819
820 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500821 @VisibleForTesting
822 public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
823 Preconditions.checkNotNull(pkg);
824 Preconditions.checkNotNull(channelId);
825 Record r = getRecord(pkg, uid);
826 if (r == null) {
827 return;
828 }
829 r.channels.remove(channelId);
830 }
831
832 @Override
833 public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
834 Preconditions.checkNotNull(pkg);
835 Record r = getRecord(pkg, uid);
836 if (r == null) {
837 return;
838 }
Geoffrey Pitscha22f6442017-05-05 16:47:38 +0000839 int N = r.channels.size() - 1;
840 for (int i = N; i >= 0; i--) {
841 String key = r.channels.keyAt(i);
842 if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
843 r.channels.remove(key);
844 }
845 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500846 }
847
Julia Reynolds005c8b92017-08-24 10:35:53 -0400848 public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
849 int uid, String groupId, boolean includeDeleted) {
850 Preconditions.checkNotNull(pkg);
851 Record r = getRecord(pkg, uid);
852 if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
853 return null;
854 }
855 NotificationChannelGroup group = r.groups.get(groupId).clone();
856 group.setChannels(new ArrayList<>());
857 int N = r.channels.size();
858 for (int i = 0; i < N; i++) {
859 final NotificationChannel nc = r.channels.valueAt(i);
860 if (includeDeleted || !nc.isDeleted()) {
861 if (groupId.equals(nc.getGroup())) {
862 group.addChannel(nc);
863 }
864 }
865 }
866 return group;
867 }
868
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500869 public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
870 int uid) {
871 Preconditions.checkNotNull(pkg);
872 Record r = getRecord(pkg, uid);
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500873 if (r == null) {
874 return null;
875 }
Geoffrey Pitschdf44b602017-02-03 13:31:50 -0500876 return r.groups.get(groupId);
877 }
878
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500879 @Override
Julia Reynolds59e152e2017-01-25 17:42:53 -0500880 public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500881 int uid, boolean includeDeleted, boolean includeNonGrouped) {
Julia Reynolds59e152e2017-01-25 17:42:53 -0500882 Preconditions.checkNotNull(pkg);
Julia Reynolds74856c42017-02-08 14:47:23 -0500883 Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
Julia Reynolds59e152e2017-01-25 17:42:53 -0500884 Record r = getRecord(pkg, uid);
885 if (r == null) {
886 return ParceledListSlice.emptyList();
887 }
888 NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
889 int N = r.channels.size();
890 for (int i = 0; i < N; i++) {
891 final NotificationChannel nc = r.channels.valueAt(i);
892 if (includeDeleted || !nc.isDeleted()) {
893 if (nc.getGroup() != null) {
Julia Reynolds9bfba592017-03-15 14:03:55 -0400894 if (r.groups.get(nc.getGroup()) != null) {
895 NotificationChannelGroup ncg = groups.get(nc.getGroup());
896 if (ncg == null) {
897 ncg = r.groups.get(nc.getGroup()).clone();
Julia Reynolds005c8b92017-08-24 10:35:53 -0400898 ncg.setChannels(new ArrayList<>());
Julia Reynolds9bfba592017-03-15 14:03:55 -0400899 groups.put(nc.getGroup(), ncg);
900
901 }
902 ncg.addChannel(nc);
Julia Reynolds74856c42017-02-08 14:47:23 -0500903 }
Julia Reynolds59e152e2017-01-25 17:42:53 -0500904 } else {
905 nonGrouped.addChannel(nc);
906 }
907 }
908 }
Julia Reynolds3eb3ffd2017-11-16 10:11:32 -0500909 if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
Julia Reynolds74856c42017-02-08 14:47:23 -0500910 groups.put(null, nonGrouped);
Julia Reynolds59e152e2017-01-25 17:42:53 -0500911 }
Julia Reynolds74856c42017-02-08 14:47:23 -0500912 return new ParceledListSlice<>(new ArrayList<>(groups.values()));
Julia Reynolds59e152e2017-01-25 17:42:53 -0500913 }
914
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400915 public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
Julia Reynolds9bfba592017-03-15 14:03:55 -0400916 String groupId) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400917 List<NotificationChannel> deletedChannels = new ArrayList<>();
Julia Reynolds9bfba592017-03-15 14:03:55 -0400918 Record r = getRecord(pkg, uid);
919 if (r == null || TextUtils.isEmpty(groupId)) {
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400920 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400921 }
922
923 r.groups.remove(groupId);
924
925 int N = r.channels.size();
926 for (int i = 0; i < N; i++) {
927 final NotificationChannel nc = r.channels.valueAt(i);
928 if (groupId.equals(nc.getGroup())) {
929 nc.setDeleted(true);
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400930 deletedChannels.add(nc);
Julia Reynolds9bfba592017-03-15 14:03:55 -0400931 }
932 }
Julia Reynolds73ed76b2017-04-04 17:04:38 -0400933 return deletedChannels;
Julia Reynolds9bfba592017-03-15 14:03:55 -0400934 }
935
Julia Reynolds59e152e2017-01-25 17:42:53 -0500936 @Override
Julia Reynoldsf02562a2017-01-26 13:33:56 -0500937 public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
938 int uid) {
939 Record r = getRecord(pkg, uid);
940 if (r == null) {
941 return new ArrayList<>();
942 }
943 return r.groups.values();
944 }
945
946 @Override
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500947 public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
948 boolean includeDeleted) {
Julia Reynolds52e64d02016-12-09 15:36:12 -0500949 Preconditions.checkNotNull(pkg);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400950 List<NotificationChannel> channels = new ArrayList<>();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500951 Record r = getRecord(pkg, uid);
952 if (r == null) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500953 return ParceledListSlice.emptyList();
Julia Reynolds52e64d02016-12-09 15:36:12 -0500954 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400955 int N = r.channels.size();
956 for (int i = 0; i < N; i++) {
Julia Reynolds4036e8d2017-01-13 09:50:05 -0500957 final NotificationChannel nc = r.channels.valueAt(i);
958 if (includeDeleted || !nc.isDeleted()) {
959 channels.add(nc);
960 }
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400961 }
Julia Reynolds85769912016-10-25 09:08:57 -0400962 return new ParceledListSlice<>(channels);
Julia Reynoldsb5e44b72016-08-16 15:00:25 -0400963 }
964
Julia Reynolds17717f52017-05-09 11:46:06 -0400965 /**
966 * True for pre-O apps that only have the default channel, or pre O apps that have no
967 * channels yet. This method will create the default channel for pre-O apps that don't have it.
968 * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
969 * upgrades.
970 */
971 public boolean onlyHasDefaultChannel(String pkg, int uid) {
972 Record r = getOrCreateRecord(pkg, uid);
973 if (r.channels.size() == 1
974 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
975 return true;
976 }
977 return false;
978 }
979
Julia Reynolds41103f42017-03-15 11:36:35 -0400980 public int getDeletedChannelCount(String pkg, int uid) {
981 Preconditions.checkNotNull(pkg);
982 int deletedCount = 0;
983 Record r = getRecord(pkg, uid);
984 if (r == null) {
985 return deletedCount;
986 }
987 int N = r.channels.size();
988 for (int i = 0; i < N; i++) {
989 final NotificationChannel nc = r.channels.valueAt(i);
990 if (nc.isDeleted()) {
991 deletedCount++;
992 }
993 }
994 return deletedCount;
995 }
996
Julia Reynoldsf2e499d2018-03-30 10:36:42 -0400997 public int getBlockedChannelCount(String pkg, int uid) {
998 Preconditions.checkNotNull(pkg);
999 int blockedCount = 0;
1000 Record r = getRecord(pkg, uid);
1001 if (r == null) {
1002 return blockedCount;
1003 }
1004 int N = r.channels.size();
1005 for (int i = 0; i < N; i++) {
1006 final NotificationChannel nc = r.channels.valueAt(i);
1007 if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
1008 blockedCount++;
1009 }
1010 }
1011 return blockedCount;
1012 }
1013
Julia Reynoldse273f082018-04-12 13:48:49 -04001014 public int getBlockedAppCount(int userId) {
1015 int count = 0;
1016 synchronized (mRecords) {
1017 final int N = mRecords.size();
1018 for (int i = 0; i < N; i++) {
1019 final Record r = mRecords.valueAt(i);
1020 if (userId == UserHandle.getUserId(r.uid)
1021 && r.importance == IMPORTANCE_NONE) {
1022 count++;
1023 }
1024 }
1025 }
1026 return count;
1027 }
1028
Julia Reynolds81afbcd2016-02-09 14:54:08 -05001029 /**
Julia Reynoldsef37f282016-02-12 09:11:27 -05001030 * Sets importance.
Julia Reynolds92d456e2016-01-25 16:36:59 -05001031 */
1032 @Override
Julia Reynoldsef37f282016-02-12 09:11:27 -05001033 public void setImportance(String pkgName, int uid, int importance) {
1034 getOrCreateRecord(pkgName, uid).importance = importance;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001035 updateConfig();
1036 }
1037
Chris Wrenacf424a2016-03-15 12:48:55 -04001038 public void setEnabled(String packageName, int uid, boolean enabled) {
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001039 boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
Chris Wrenacf424a2016-03-15 12:48:55 -04001040 if (wasEnabled == enabled) {
1041 return;
1042 }
Julia Reynolds85769912016-10-25 09:08:57 -04001043 setImportance(packageName, uid,
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001044 enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
Chris Wrenacf424a2016-03-15 12:48:55 -04001045 }
1046
Rohan Shah590e1b22018-04-10 23:48:47 -04001047 /**
1048 * Sets whether any notifications from the app, represented by the given {@code pkgName} and
1049 * {@code uid}, have their importance locked by the user. Locked notifications don't get
1050 * considered for sentiment adjustments (and thus never show a blocking helper).
1051 */
1052 public void setAppImportanceLocked(String packageName, int uid) {
1053 Record record = getOrCreateRecord(packageName, uid);
1054 if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
1055 return;
1056 }
1057
1058 record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
1059 updateConfig();
1060 }
1061
Julia Reynoldse0b25742017-05-08 12:55:24 -04001062 @VisibleForTesting
1063 void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
Julia Reynoldse0b25742017-05-08 12:55:24 -04001064 if (original.canBypassDnd() != update.canBypassDnd()) {
1065 update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
1066 }
1067 if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
1068 update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
1069 }
1070 if (original.getImportance() != update.getImportance()) {
1071 update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
1072 }
1073 if (original.shouldShowLights() != update.shouldShowLights()
1074 || original.getLightColor() != update.getLightColor()) {
1075 update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
1076 }
1077 if (!Objects.equals(original.getSound(), update.getSound())) {
1078 update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
1079 }
1080 if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
1081 || original.shouldVibrate() != update.shouldVibrate()) {
1082 update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
1083 }
1084 if (original.canShowBadge() != update.canShowBadge()) {
1085 update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
1086 }
1087 }
1088
Kweku Adams887f09c2017-11-13 17:12:20 -08001089 public void dump(PrintWriter pw, String prefix,
1090 @NonNull NotificationManagerService.DumpFilter filter) {
1091 final int N = mSignalExtractors.length;
1092 pw.print(prefix);
1093 pw.print("mSignalExtractors.length = ");
1094 pw.println(N);
1095 for (int i = 0; i < N; i++) {
Chris Wren54bbef42014-07-09 18:37:56 -04001096 pw.print(prefix);
Kweku Adams887f09c2017-11-13 17:12:20 -08001097 pw.print(" ");
1098 pw.println(mSignalExtractors[i].getClass().getSimpleName());
Chris Wren54bbef42014-07-09 18:37:56 -04001099 }
Kweku Adams887f09c2017-11-13 17:12:20 -08001100
1101 pw.print(prefix);
1102 pw.println("per-package config:");
1103
Julia Reynolds85aa6cb2016-01-08 17:49:11 -05001104 pw.println("Records:");
Julia Reynolds44011962017-04-14 12:29:04 -04001105 synchronized (mRecords) {
1106 dumpRecords(pw, prefix, filter, mRecords);
1107 }
Julia Reynolds85aa6cb2016-01-08 17:49:11 -05001108 pw.println("Restored without uid:");
John Spurlock35ef0a62015-05-28 11:24:10 -04001109 dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
1110 }
1111
Kweku Adams887f09c2017-11-13 17:12:20 -08001112 public void dump(ProtoOutputStream proto,
1113 @NonNull NotificationManagerService.DumpFilter filter) {
Kweku Adams62b42242017-09-25 12:54:02 -07001114 final int N = mSignalExtractors.length;
1115 for (int i = 0; i < N; i++) {
1116 proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
1117 mSignalExtractors[i].getClass().getSimpleName());
1118 }
1119 synchronized (mRecords) {
1120 dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
1121 }
1122 dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
1123 mRestoredWithoutUids);
1124 }
1125
1126 private static void dumpRecords(ProtoOutputStream proto, long fieldId,
Kweku Adams887f09c2017-11-13 17:12:20 -08001127 @NonNull NotificationManagerService.DumpFilter filter,
1128 ArrayMap<String, Record> records) {
Kweku Adams62b42242017-09-25 12:54:02 -07001129 final int N = records.size();
1130 long fToken;
1131 for (int i = 0; i < N; i++) {
1132 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -08001133 if (filter.matches(r.pkg)) {
Kweku Adams62b42242017-09-25 12:54:02 -07001134 fToken = proto.start(fieldId);
1135
1136 proto.write(RecordProto.PACKAGE, r.pkg);
1137 proto.write(RecordProto.UID, r.uid);
1138 proto.write(RecordProto.IMPORTANCE, r.importance);
1139 proto.write(RecordProto.PRIORITY, r.priority);
1140 proto.write(RecordProto.VISIBILITY, r.visibility);
1141 proto.write(RecordProto.SHOW_BADGE, r.showBadge);
1142
Kweku Adams62b42242017-09-25 12:54:02 -07001143 for (NotificationChannel channel : r.channels.values()) {
Kweku Adamsbc84aec2018-01-23 13:33:12 -08001144 channel.writeToProto(proto, RecordProto.CHANNELS);
Kweku Adams62b42242017-09-25 12:54:02 -07001145 }
1146 for (NotificationChannelGroup group : r.groups.values()) {
Kweku Adamsbc84aec2018-01-23 13:33:12 -08001147 group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
Kweku Adams62b42242017-09-25 12:54:02 -07001148 }
1149
1150 proto.end(fToken);
1151 }
1152 }
1153 }
1154
John Spurlock35ef0a62015-05-28 11:24:10 -04001155 private static void dumpRecords(PrintWriter pw, String prefix,
Kweku Adams887f09c2017-11-13 17:12:20 -08001156 @NonNull NotificationManagerService.DumpFilter filter,
1157 ArrayMap<String, Record> records) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001158 final int N = records.size();
Chris Wren54bbef42014-07-09 18:37:56 -04001159 for (int i = 0; i < N; i++) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001160 final Record r = records.valueAt(i);
Kweku Adams887f09c2017-11-13 17:12:20 -08001161 if (filter.matches(r.pkg)) {
John Spurlock1d881a12015-03-18 19:21:54 -04001162 pw.print(prefix);
Julia Reynolds924eed12017-01-19 09:52:07 -05001163 pw.print(" AppSettings: ");
John Spurlock1d881a12015-03-18 19:21:54 -04001164 pw.print(r.pkg);
1165 pw.print(" (");
John Spurlock35ef0a62015-05-28 11:24:10 -04001166 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
John Spurlock1d881a12015-03-18 19:21:54 -04001167 pw.print(')');
Julia Reynolds92d456e2016-01-25 16:36:59 -05001168 if (r.importance != DEFAULT_IMPORTANCE) {
1169 pw.print(" importance=");
1170 pw.print(Ranking.importanceToString(r.importance));
1171 }
1172 if (r.priority != DEFAULT_PRIORITY) {
1173 pw.print(" priority=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001174 pw.print(Notification.priorityToString(r.priority));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001175 }
1176 if (r.visibility != DEFAULT_VISIBILITY) {
1177 pw.print(" visibility=");
Chris Wrenacf424a2016-03-15 12:48:55 -04001178 pw.print(Notification.visibilityToString(r.visibility));
Julia Reynolds92d456e2016-01-25 16:36:59 -05001179 }
Julia Reynolds924eed12017-01-19 09:52:07 -05001180 pw.print(" showBadge=");
1181 pw.print(Boolean.toString(r.showBadge));
John Spurlock1d881a12015-03-18 19:21:54 -04001182 pw.println();
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001183 for (NotificationChannel channel : r.channels.values()) {
1184 pw.print(prefix);
1185 pw.print(" ");
1186 pw.print(" ");
1187 pw.println(channel);
1188 }
Julia Reynolds59e152e2017-01-25 17:42:53 -05001189 for (NotificationChannelGroup group : r.groups.values()) {
1190 pw.print(prefix);
1191 pw.print(" ");
1192 pw.print(" ");
1193 pw.println(group);
1194 }
Chris Wren54bbef42014-07-09 18:37:56 -04001195 }
1196 }
1197 }
John Spurlock1d881a12015-03-18 19:21:54 -04001198
Chris Wrenacf424a2016-03-15 12:48:55 -04001199 public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
1200 JSONObject ranking = new JSONObject();
1201 JSONArray records = new JSONArray();
1202 try {
1203 ranking.put("noUid", mRestoredWithoutUids.size());
1204 } catch (JSONException e) {
1205 // pass
1206 }
Julia Reynolds44011962017-04-14 12:29:04 -04001207 synchronized (mRecords) {
1208 final int N = mRecords.size();
1209 for (int i = 0; i < N; i++) {
1210 final Record r = mRecords.valueAt(i);
1211 if (filter == null || filter.matches(r.pkg)) {
1212 JSONObject record = new JSONObject();
1213 try {
1214 record.put("userId", UserHandle.getUserId(r.uid));
1215 record.put("packageName", r.pkg);
1216 if (r.importance != DEFAULT_IMPORTANCE) {
1217 record.put("importance", Ranking.importanceToString(r.importance));
1218 }
1219 if (r.priority != DEFAULT_PRIORITY) {
1220 record.put("priority", Notification.priorityToString(r.priority));
1221 }
1222 if (r.visibility != DEFAULT_VISIBILITY) {
1223 record.put("visibility", Notification.visibilityToString(r.visibility));
1224 }
1225 if (r.showBadge != DEFAULT_SHOW_BADGE) {
1226 record.put("showBadge", Boolean.valueOf(r.showBadge));
1227 }
1228 for (NotificationChannel channel : r.channels.values()) {
1229 record.put("channel", channel.toJson());
1230 }
1231 for (NotificationChannelGroup group : r.groups.values()) {
1232 record.put("group", group.toJson());
1233 }
1234 } catch (JSONException e) {
1235 // pass
Chris Wrenacf424a2016-03-15 12:48:55 -04001236 }
Julia Reynolds44011962017-04-14 12:29:04 -04001237 records.put(record);
Chris Wrenacf424a2016-03-15 12:48:55 -04001238 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001239 }
1240 }
1241 try {
1242 ranking.put("records", records);
1243 } catch (JSONException e) {
1244 // pass
1245 }
1246 return ranking;
1247 }
1248
1249 /**
1250 * Dump only the ban information as structured JSON for the stats collector.
1251 *
1252 * This is intentionally redundant with {#link dumpJson} because the old
1253 * scraper will expect this format.
1254 *
1255 * @param filter
1256 * @return
1257 */
1258 public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
1259 JSONArray bans = new JSONArray();
1260 Map<Integer, String> packageBans = getPackageBans();
1261 for(Entry<Integer, String> ban : packageBans.entrySet()) {
1262 final int userId = UserHandle.getUserId(ban.getKey());
1263 final String packageName = ban.getValue();
1264 if (filter == null || filter.matches(packageName)) {
1265 JSONObject banJson = new JSONObject();
1266 try {
1267 banJson.put("userId", userId);
1268 banJson.put("packageName", packageName);
1269 } catch (JSONException e) {
1270 e.printStackTrace();
1271 }
1272 bans.put(banJson);
1273 }
1274 }
1275 return bans;
1276 }
1277
1278 public Map<Integer, String> getPackageBans() {
Julia Reynolds44011962017-04-14 12:29:04 -04001279 synchronized (mRecords) {
1280 final int N = mRecords.size();
1281 ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
1282 for (int i = 0; i < N; i++) {
1283 final Record r = mRecords.valueAt(i);
Julia Reynoldsf2e499d2018-03-30 10:36:42 -04001284 if (r.importance == IMPORTANCE_NONE) {
Julia Reynolds44011962017-04-14 12:29:04 -04001285 packageBans.put(r.uid, r.pkg);
1286 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001287 }
Julia Reynolds44011962017-04-14 12:29:04 -04001288
1289 return packageBans;
Chris Wrenacf424a2016-03-15 12:48:55 -04001290 }
Chris Wrenacf424a2016-03-15 12:48:55 -04001291 }
1292
Julia Reynoldsd373d782017-03-03 13:32:57 -05001293 /**
1294 * Dump only the channel information as structured JSON for the stats collector.
1295 *
1296 * This is intentionally redundant with {#link dumpJson} because the old
1297 * scraper will expect this format.
1298 *
1299 * @param filter
1300 * @return
1301 */
1302 public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
1303 JSONArray channels = new JSONArray();
1304 Map<String, Integer> packageChannels = getPackageChannels();
1305 for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
1306 final String packageName = channelCount.getKey();
1307 if (filter == null || filter.matches(packageName)) {
1308 JSONObject channelCountJson = new JSONObject();
1309 try {
1310 channelCountJson.put("packageName", packageName);
1311 channelCountJson.put("channelCount", channelCount.getValue());
1312 } catch (JSONException e) {
1313 e.printStackTrace();
1314 }
1315 channels.put(channelCountJson);
1316 }
1317 }
1318 return channels;
1319 }
1320
1321 private Map<String, Integer> getPackageChannels() {
1322 ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
Julia Reynolds44011962017-04-14 12:29:04 -04001323 synchronized (mRecords) {
1324 for (int i = 0; i < mRecords.size(); i++) {
1325 final Record r = mRecords.valueAt(i);
1326 int channelCount = 0;
1327 for (int j = 0; j < r.channels.size(); j++) {
1328 if (!r.channels.valueAt(j).isDeleted()) {
1329 channelCount++;
1330 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001331 }
Julia Reynolds44011962017-04-14 12:29:04 -04001332 packageChannels.put(r.pkg, channelCount);
Julia Reynoldsd373d782017-03-03 13:32:57 -05001333 }
Julia Reynoldsd373d782017-03-03 13:32:57 -05001334 }
1335 return packageChannels;
1336 }
1337
Julia Reynolds2e9bf5f2017-05-03 13:23:30 -04001338 public void onUserRemoved(int userId) {
1339 synchronized (mRecords) {
1340 int N = mRecords.size();
1341 for (int i = N - 1; i >= 0 ; i--) {
1342 Record record = mRecords.valueAt(i);
1343 if (UserHandle.getUserId(record.uid) == userId) {
1344 mRecords.removeAt(i);
1345 }
1346 }
1347 }
1348 }
1349
Julia Reynolds816797a2017-08-11 15:47:09 -04001350 protected void onLocaleChanged(Context context, int userId) {
1351 synchronized (mRecords) {
1352 int N = mRecords.size();
1353 for (int i = 0; i < N; i++) {
1354 Record record = mRecords.valueAt(i);
1355 if (UserHandle.getUserId(record.uid) == userId) {
1356 if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
1357 record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
1358 context.getResources().getString(
1359 R.string.default_notification_channel_label));
1360 }
1361 }
1362 }
1363 }
1364 }
1365
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001366 public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
1367 int[] uidList) {
1368 if (pkgList == null || pkgList.length == 0) {
John Spurlock35ef0a62015-05-28 11:24:10 -04001369 return; // nothing to do
1370 }
John Spurlock35ef0a62015-05-28 11:24:10 -04001371 boolean updated = false;
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001372 if (removingPackage) {
1373 // Remove notification settings for uninstalled package
1374 int size = Math.min(pkgList.length, uidList.length);
1375 for (int i = 0; i < size; i++) {
1376 final String pkg = pkgList[i];
1377 final int uid = uidList[i];
Julia Reynolds44011962017-04-14 12:29:04 -04001378 synchronized (mRecords) {
1379 mRecords.remove(recordKey(pkg, uid));
1380 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001381 mRestoredWithoutUids.remove(pkg);
1382 updated = true;
1383 }
1384 } else {
1385 for (String pkg : pkgList) {
1386 // Package install
1387 final Record r = mRestoredWithoutUids.get(pkg);
1388 if (r != null) {
1389 try {
1390 r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
1391 mRestoredWithoutUids.remove(pkg);
Julia Reynolds44011962017-04-14 12:29:04 -04001392 synchronized (mRecords) {
1393 mRecords.put(recordKey(r.pkg, r.uid), r);
1394 }
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001395 updated = true;
1396 } catch (NameNotFoundException e) {
1397 // noop
1398 }
1399 }
1400 // Package upgrade
John Spurlock35ef0a62015-05-28 11:24:10 -04001401 try {
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001402 Record fullRecord = getRecord(pkg,
1403 mPm.getPackageUidAsUser(pkg, changeUserId));
1404 if (fullRecord != null) {
Julia Reynoldsf26eb912017-05-22 15:47:06 -04001405 createDefaultChannelIfNeeded(fullRecord);
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001406 deleteDefaultChannelIfNeeded(fullRecord);
Julia Reynolds4036e8d2017-01-13 09:50:05 -05001407 }
Geoffrey Pitsch1f17e022017-01-03 16:44:20 -05001408 } catch (NameNotFoundException e) {}
John Spurlock35ef0a62015-05-28 11:24:10 -04001409 }
1410 }
Julia Reynolds1864ff52016-11-02 09:54:47 -04001411
John Spurlock35ef0a62015-05-28 11:24:10 -04001412 if (updated) {
1413 updateConfig();
1414 }
1415 }
1416
Julia Reynoldsd373d782017-03-03 13:32:57 -05001417 private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
1418 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
1419 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1420 .setPackageName(pkg)
1421 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
1422 channel.getId())
1423 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
1424 channel.getImportance());
1425 }
1426
Dan Sandler0171f572017-06-14 14:03:18 -04001427 private LogMaker getChannelGroupLog(String groupId, String pkg) {
1428 return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
1429 .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
1430 .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
1431 groupId)
1432 .setPackageName(pkg);
1433 }
1434
Chris Wren89aa2262017-05-05 18:05:56 -04001435 public void updateBadgingEnabled() {
1436 if (mBadgingEnabled == null) {
1437 mBadgingEnabled = new SparseBooleanArray();
1438 }
1439 boolean changed = false;
1440 // update the cached values
1441 for (int index = 0; index < mBadgingEnabled.size(); index++) {
1442 int userId = mBadgingEnabled.keyAt(index);
1443 final boolean oldValue = mBadgingEnabled.get(userId);
1444 final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
1445 Secure.NOTIFICATION_BADGING,
1446 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
1447 mBadgingEnabled.put(userId, newValue);
1448 changed |= oldValue != newValue;
1449 }
1450 if (changed) {
Julia Reynoldsf5395772017-09-06 14:08:23 -04001451 updateConfig();
Chris Wren89aa2262017-05-05 18:05:56 -04001452 }
1453 }
1454
1455 public boolean badgingEnabled(UserHandle userHandle) {
1456 int userId = userHandle.getIdentifier();
Chris Wren13f157f2017-05-12 15:02:06 -04001457 if (userId == UserHandle.USER_ALL) {
1458 return false;
1459 }
Chris Wren89aa2262017-05-05 18:05:56 -04001460 if (mBadgingEnabled.indexOfKey(userId) < 0) {
1461 mBadgingEnabled.put(userId,
1462 Secure.getIntForUser(mContext.getContentResolver(),
1463 Secure.NOTIFICATION_BADGING,
1464 DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
1465 }
1466 return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
1467 }
1468
1469
John Spurlock1d881a12015-03-18 19:21:54 -04001470 private static class Record {
John Spurlock35ef0a62015-05-28 11:24:10 -04001471 static int UNKNOWN_UID = UserHandle.USER_NULL;
1472
John Spurlock1d881a12015-03-18 19:21:54 -04001473 String pkg;
John Spurlock35ef0a62015-05-28 11:24:10 -04001474 int uid = UNKNOWN_UID;
Julia Reynoldsa07af882015-12-17 08:32:48 -05001475 int importance = DEFAULT_IMPORTANCE;
Julia Reynolds92d456e2016-01-25 16:36:59 -05001476 int priority = DEFAULT_PRIORITY;
1477 int visibility = DEFAULT_VISIBILITY;
Julia Reynolds924eed12017-01-19 09:52:07 -05001478 boolean showBadge = DEFAULT_SHOW_BADGE;
Rohan Shah590e1b22018-04-10 23:48:47 -04001479 int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
Julia Reynoldsb5e44b72016-08-16 15:00:25 -04001480
1481 ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
Shunta Sato642b8d42017-06-13 16:16:13 +09001482 Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
Julia Reynolds233a5f92015-10-19 13:51:23 -04001483 }
Chris Wren54bbef42014-07-09 18:37:56 -04001484}