blob: 6045f6c73b93387c84fe90deab1d8c3709c4c5a7 [file] [log] [blame]
John Spurlockb2278d62015-04-07 12:47:12 -04001/**
2 * Copyright (c) 2015, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.notification;
18
Julia Reynoldsccc6ae62018-03-01 16:24:49 -050019import static android.provider.Settings.Global.ZEN_MODE_OFF;
20
John Spurlockb2278d62015-04-07 12:47:12 -040021import android.app.Notification;
Julia Reynoldsccc6ae62018-03-01 16:24:49 -050022import android.app.NotificationManager;
John Spurlockb2278d62015-04-07 12:47:12 -040023import android.content.ComponentName;
24import android.content.Context;
25import android.media.AudioAttributes;
John Spurlockb2278d62015-04-07 12:47:12 -040026import android.os.Bundle;
27import android.os.UserHandle;
28import android.provider.Settings.Global;
John Spurlockb2278d62015-04-07 12:47:12 -040029import android.service.notification.ZenModeConfig;
30import android.telecom.TelecomManager;
John Spurlock1d7d2242015-04-10 08:10:22 -040031import android.util.ArrayMap;
John Spurlockb2278d62015-04-07 12:47:12 -040032import android.util.Slog;
33
Julia Reynoldsccc6ae62018-03-01 16:24:49 -050034import com.android.internal.messages.nano.SystemMessageProto;
Julia Reynolds51eb78f82018-03-07 07:35:21 -050035import com.android.internal.util.NotificationMessagingUtil;
Julia Reynoldsccc6ae62018-03-01 16:24:49 -050036
John Spurlock1d7d2242015-04-10 08:10:22 -040037import java.io.PrintWriter;
38import java.util.Date;
John Spurlockb2278d62015-04-07 12:47:12 -040039
40public class ZenModeFiltering {
41 private static final String TAG = ZenModeHelper.TAG;
42 private static final boolean DEBUG = ZenModeHelper.DEBUG;
43
John Spurlock1d7d2242015-04-10 08:10:22 -040044 static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
45
John Spurlockb2278d62015-04-07 12:47:12 -040046 private final Context mContext;
47
48 private ComponentName mDefaultPhoneApp;
Julia Reynolds51eb78f82018-03-07 07:35:21 -050049 private final NotificationMessagingUtil mMessagingUtil;
John Spurlockb2278d62015-04-07 12:47:12 -040050
51 public ZenModeFiltering(Context context) {
52 mContext = context;
Julia Reynolds51eb78f82018-03-07 07:35:21 -050053 mMessagingUtil = new NotificationMessagingUtil(mContext);
54 }
55
56 public ZenModeFiltering(Context context, NotificationMessagingUtil messagingUtil) {
57 mContext = context;
58 mMessagingUtil = messagingUtil;
John Spurlockb2278d62015-04-07 12:47:12 -040059 }
60
John Spurlock1d7d2242015-04-10 08:10:22 -040061 public void dump(PrintWriter pw, String prefix) {
62 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
63 pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
64 pw.println(REPEAT_CALLERS.mThresholdMinutes);
65 synchronized (REPEAT_CALLERS) {
66 if (!REPEAT_CALLERS.mCalls.isEmpty()) {
67 pw.print(prefix); pw.println("RepeatCallers.mCalls=");
68 for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
69 pw.print(prefix); pw.print(" ");
70 pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
71 pw.print(" at ");
72 pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
73 }
74 }
75 }
76 }
77
78 private static String ts(long time) {
79 return new Date(time) + " (" + time + ")";
John Spurlockb2278d62015-04-07 12:47:12 -040080 }
81
82 /**
83 * @param extras extras of the notification with EXTRA_PEOPLE populated
84 * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
85 * @param timeoutAffinity affinity to return when the timeout specified via
86 * <code>contactsTimeoutMs</code> is hit
87 */
Beverlyff2df9b2018-10-10 16:54:10 -040088 public static boolean matchesCallFilter(Context context, int zen, NotificationManager.Policy
89 consolidatedPolicy, UserHandle userHandle, Bundle extras,
90 ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
John Spurlockb2278d62015-04-07 12:47:12 -040091 if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
92 if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
93 if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
Beverlyff2df9b2018-10-10 16:54:10 -040094 if (consolidatedPolicy.allowRepeatCallers()
95 && REPEAT_CALLERS.isRepeat(context, extras)) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -040096 return true;
97 }
Beverlyff2df9b2018-10-10 16:54:10 -040098 if (!consolidatedPolicy.allowCalls()) return false; // no other calls get through
John Spurlockb2278d62015-04-07 12:47:12 -040099 if (validator != null) {
100 final float contactAffinity = validator.getContactAffinity(userHandle, extras,
101 contactsTimeoutMs, timeoutAffinity);
Beverlyff2df9b2018-10-10 16:54:10 -0400102 return audienceMatches(consolidatedPolicy.allowCallsFrom(), contactAffinity);
John Spurlockb2278d62015-04-07 12:47:12 -0400103 }
104 }
105 return true;
106 }
107
John Spurlock1d7d2242015-04-10 08:10:22 -0400108 private static Bundle extras(NotificationRecord record) {
109 return record != null && record.sbn != null && record.sbn.getNotification() != null
110 ? record.sbn.getNotification().extras : null;
111 }
112
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400113 protected void recordCall(NotificationRecord record) {
114 REPEAT_CALLERS.recordCall(mContext, extras(record));
115 }
116
Beverlyff2df9b2018-10-10 16:54:10 -0400117 /**
118 * Whether to intercept the notification based on the policy
119 */
120 public boolean shouldIntercept(int zen, NotificationManager.Policy policy,
121 NotificationRecord record) {
Brad Stenning1e14f9f2018-08-10 14:39:26 -0700122 // Zen mode is ignored for critical notifications.
123 if (zen == ZEN_MODE_OFF || isCritical(record)) {
Julia Reynoldsccc6ae62018-03-01 16:24:49 -0500124 return false;
125 }
126 // Make an exception to policy for the notification saying that policy has changed
Beverlyff2df9b2018-10-10 16:54:10 -0400127 if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects)
Julia Reynoldsccc6ae62018-03-01 16:24:49 -0500128 && "android".equals(record.sbn.getPackageName())
129 && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.sbn.getId()) {
130 ZenLog.traceNotIntercepted(record, "systemDndChangedNotification");
131 return false;
132 }
John Spurlockb2278d62015-04-07 12:47:12 -0400133 switch (zen) {
134 case Global.ZEN_MODE_NO_INTERRUPTIONS:
135 // #notevenalarms
136 ZenLog.traceIntercepted(record, "none");
137 return true;
138 case Global.ZEN_MODE_ALARMS:
139 if (isAlarm(record)) {
140 // Alarms only
141 return false;
142 }
143 ZenLog.traceIntercepted(record, "alarmsOnly");
144 return true;
145 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
John Spurlockb2278d62015-04-07 12:47:12 -0400146 // allow user-prioritized packages through in priority mode
147 if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
148 ZenLog.traceNotIntercepted(record, "priorityApp");
149 return false;
150 }
Beverly04216872017-09-28 10:55:32 -0400151
152 if (isAlarm(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400153 if (!policy.allowAlarms()) {
Beverly04216872017-09-28 10:55:32 -0400154 ZenLog.traceIntercepted(record, "!allowAlarms");
155 return true;
156 }
157 return false;
158 }
John Spurlockb2278d62015-04-07 12:47:12 -0400159 if (isCall(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400160 if (policy.allowRepeatCallers()
John Spurlock1d7d2242015-04-10 08:10:22 -0400161 && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
162 ZenLog.traceNotIntercepted(record, "repeatCaller");
163 return false;
164 }
Beverlyff2df9b2018-10-10 16:54:10 -0400165 if (!policy.allowCalls()) {
John Spurlockb2278d62015-04-07 12:47:12 -0400166 ZenLog.traceIntercepted(record, "!allowCalls");
167 return true;
168 }
Beverlyff2df9b2018-10-10 16:54:10 -0400169 return shouldInterceptAudience(policy.allowCallsFrom(), record);
John Spurlockb2278d62015-04-07 12:47:12 -0400170 }
171 if (isMessage(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400172 if (!policy.allowMessages()) {
John Spurlockb2278d62015-04-07 12:47:12 -0400173 ZenLog.traceIntercepted(record, "!allowMessages");
174 return true;
175 }
Beverlyff2df9b2018-10-10 16:54:10 -0400176 return shouldInterceptAudience(policy.allowMessagesFrom(), record);
John Spurlockb2278d62015-04-07 12:47:12 -0400177 }
178 if (isEvent(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400179 if (!policy.allowEvents()) {
John Spurlockb2278d62015-04-07 12:47:12 -0400180 ZenLog.traceIntercepted(record, "!allowEvents");
181 return true;
182 }
183 return false;
184 }
185 if (isReminder(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400186 if (!policy.allowReminders()) {
John Spurlockb2278d62015-04-07 12:47:12 -0400187 ZenLog.traceIntercepted(record, "!allowReminders");
188 return true;
189 }
190 return false;
191 }
Beverlyd6964762018-02-16 14:07:03 -0500192 if (isMedia(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400193 if (!policy.allowMedia()) {
Beverlyd6964762018-02-16 14:07:03 -0500194 ZenLog.traceIntercepted(record, "!allowMedia");
195 return true;
196 }
197 return false;
198 }
199 if (isSystem(record)) {
Beverlyff2df9b2018-10-10 16:54:10 -0400200 if (!policy.allowSystem()) {
Beverlyd6964762018-02-16 14:07:03 -0500201 ZenLog.traceIntercepted(record, "!allowSystem");
Beverly04216872017-09-28 10:55:32 -0400202 return true;
203 }
204 return false;
205 }
John Spurlockb2278d62015-04-07 12:47:12 -0400206 ZenLog.traceIntercepted(record, "!priority");
207 return true;
208 default:
209 return false;
210 }
211 }
212
Brad Stenning1e14f9f2018-08-10 14:39:26 -0700213 /**
214 * Check if the notification is too critical to be suppressed.
215 *
216 * @param record the record to test for criticality
217 * @return {@code true} if notification is considered critical
218 *
219 * @see CriticalNotificationExtractor for criteria
220 */
221 private boolean isCritical(NotificationRecord record) {
222 // 0 is the most critical
223 return record.getCriticality() < CriticalNotificationExtractor.NORMAL;
224 }
225
John Spurlocka492d1d2015-05-05 18:30:28 -0400226 private static boolean shouldInterceptAudience(int source, NotificationRecord record) {
227 if (!audienceMatches(source, record.getContactAffinity())) {
John Spurlockb2278d62015-04-07 12:47:12 -0400228 ZenLog.traceIntercepted(record, "!audienceMatches");
229 return true;
230 }
231 return false;
232 }
233
Julia Reynolds51eb78f82018-03-07 07:35:21 -0500234 protected static boolean isAlarm(NotificationRecord record) {
John Spurlockb2278d62015-04-07 12:47:12 -0400235 return record.isCategory(Notification.CATEGORY_ALARM)
John Spurlockb2278d62015-04-07 12:47:12 -0400236 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
237 }
238
239 private static boolean isEvent(NotificationRecord record) {
240 return record.isCategory(Notification.CATEGORY_EVENT);
241 }
242
243 private static boolean isReminder(NotificationRecord record) {
244 return record.isCategory(Notification.CATEGORY_REMINDER);
245 }
246
247 public boolean isCall(NotificationRecord record) {
248 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
249 || record.isCategory(Notification.CATEGORY_CALL));
250 }
251
Beverlyd6964762018-02-16 14:07:03 -0500252 public boolean isMedia(NotificationRecord record) {
253 AudioAttributes aa = record.getAudioAttributes();
254 return aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
255 AudioAttributes.SUPPRESSIBLE_MEDIA;
256 }
257
258 public boolean isSystem(NotificationRecord record) {
259 AudioAttributes aa = record.getAudioAttributes();
260 return aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
261 AudioAttributes.SUPPRESSIBLE_SYSTEM;
262 }
263
John Spurlockb2278d62015-04-07 12:47:12 -0400264 private boolean isDefaultPhoneApp(String pkg) {
265 if (mDefaultPhoneApp == null) {
266 final TelecomManager telecomm =
267 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
268 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
269 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
270 }
271 return pkg != null && mDefaultPhoneApp != null
272 && pkg.equals(mDefaultPhoneApp.getPackageName());
273 }
274
Julia Reynolds51eb78f82018-03-07 07:35:21 -0500275 protected boolean isMessage(NotificationRecord record) {
276 return mMessagingUtil.isMessaging(record.sbn);
John Spurlockb2278d62015-04-07 12:47:12 -0400277 }
278
John Spurlocka492d1d2015-05-05 18:30:28 -0400279 private static boolean audienceMatches(int source, float contactAffinity) {
280 switch (source) {
John Spurlockb2278d62015-04-07 12:47:12 -0400281 case ZenModeConfig.SOURCE_ANYONE:
282 return true;
283 case ZenModeConfig.SOURCE_CONTACT:
284 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
285 case ZenModeConfig.SOURCE_STAR:
286 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
287 default:
John Spurlocka492d1d2015-05-05 18:30:28 -0400288 Slog.w(TAG, "Encountered unknown source: " + source);
John Spurlockb2278d62015-04-07 12:47:12 -0400289 return true;
290 }
291 }
John Spurlock1d7d2242015-04-10 08:10:22 -0400292
293 private static class RepeatCallers {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400294 // Person : time
John Spurlock1d7d2242015-04-10 08:10:22 -0400295 private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
296 private int mThresholdMinutes;
297
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400298 private synchronized void recordCall(Context context, Bundle extras) {
299 setThresholdMinutes(context);
300 if (mThresholdMinutes <= 0 || extras == null) return;
301 final String peopleString = peopleString(extras);
302 if (peopleString == null) return;
303 final long now = System.currentTimeMillis();
304 cleanUp(mCalls, now);
305 mCalls.put(peopleString, now);
306 }
307
John Spurlock1d7d2242015-04-10 08:10:22 -0400308 private synchronized boolean isRepeat(Context context, Bundle extras) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400309 setThresholdMinutes(context);
John Spurlock1d7d2242015-04-10 08:10:22 -0400310 if (mThresholdMinutes <= 0 || extras == null) return false;
311 final String peopleString = peopleString(extras);
312 if (peopleString == null) return false;
313 final long now = System.currentTimeMillis();
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400314 cleanUp(mCalls, now);
315 return mCalls.containsKey(peopleString);
316 }
317
318 private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
319 final int N = calls.size();
John Spurlock1d7d2242015-04-10 08:10:22 -0400320 for (int i = N - 1; i >= 0; i--) {
321 final long time = mCalls.valueAt(i);
322 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400323 calls.removeAt(i);
John Spurlock1d7d2242015-04-10 08:10:22 -0400324 }
325 }
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400326 }
327
328 private void setThresholdMinutes(Context context) {
329 if (mThresholdMinutes <= 0) {
330 mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
331 .config_zen_repeat_callers_threshold);
332 }
John Spurlock1d7d2242015-04-10 08:10:22 -0400333 }
334
335 private static String peopleString(Bundle extras) {
336 final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
337 if (extraPeople == null || extraPeople.length == 0) return null;
338 final StringBuilder sb = new StringBuilder();
339 for (int i = 0; i < extraPeople.length; i++) {
340 String extraPerson = extraPeople[i];
341 if (extraPerson == null) continue;
342 extraPerson = extraPerson.trim();
343 if (extraPerson.isEmpty()) continue;
344 if (sb.length() > 0) {
345 sb.append('|');
346 }
347 sb.append(extraPerson);
348 }
349 return sb.length() == 0 ? null : sb.toString();
350 }
351 }
352
John Spurlockb2278d62015-04-07 12:47:12 -0400353}