blob: a7e0c51081885ed1ec329fcf84f98cccaf07cc6b [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;
26import android.media.AudioManager;
27import android.os.Bundle;
28import android.os.UserHandle;
29import android.provider.Settings.Global;
30import android.provider.Settings.Secure;
31import android.service.notification.ZenModeConfig;
32import android.telecom.TelecomManager;
John Spurlock1d7d2242015-04-10 08:10:22 -040033import android.util.ArrayMap;
John Spurlockb2278d62015-04-07 12:47:12 -040034import android.util.Slog;
35
Julia Reynoldsccc6ae62018-03-01 16:24:49 -050036import com.android.internal.messages.nano.SystemMessageProto;
37
John Spurlock1d7d2242015-04-10 08:10:22 -040038import java.io.PrintWriter;
39import java.util.Date;
John Spurlockb2278d62015-04-07 12:47:12 -040040import java.util.Objects;
41
42public class ZenModeFiltering {
43 private static final String TAG = ZenModeHelper.TAG;
44 private static final boolean DEBUG = ZenModeHelper.DEBUG;
45
John Spurlock1d7d2242015-04-10 08:10:22 -040046 static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
47
John Spurlockb2278d62015-04-07 12:47:12 -040048 private final Context mContext;
49
50 private ComponentName mDefaultPhoneApp;
51
52 public ZenModeFiltering(Context context) {
53 mContext = context;
54 }
55
John Spurlock1d7d2242015-04-10 08:10:22 -040056 public void dump(PrintWriter pw, String prefix) {
57 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
58 pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
59 pw.println(REPEAT_CALLERS.mThresholdMinutes);
60 synchronized (REPEAT_CALLERS) {
61 if (!REPEAT_CALLERS.mCalls.isEmpty()) {
62 pw.print(prefix); pw.println("RepeatCallers.mCalls=");
63 for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
64 pw.print(prefix); pw.print(" ");
65 pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
66 pw.print(" at ");
67 pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
68 }
69 }
70 }
71 }
72
73 private static String ts(long time) {
74 return new Date(time) + " (" + time + ")";
John Spurlockb2278d62015-04-07 12:47:12 -040075 }
76
77 /**
78 * @param extras extras of the notification with EXTRA_PEOPLE populated
79 * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
80 * @param timeoutAffinity affinity to return when the timeout specified via
81 * <code>contactsTimeoutMs</code> is hit
82 */
John Spurlock1d7d2242015-04-10 08:10:22 -040083 public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config,
84 UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator,
85 int contactsTimeoutMs, float timeoutAffinity) {
John Spurlockb2278d62015-04-07 12:47:12 -040086 if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
87 if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
88 if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -040089 if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) {
90 return true;
91 }
John Spurlock1d7d2242015-04-10 08:10:22 -040092 if (!config.allowCalls) return false; // no other calls get through
John Spurlockb2278d62015-04-07 12:47:12 -040093 if (validator != null) {
94 final float contactAffinity = validator.getContactAffinity(userHandle, extras,
95 contactsTimeoutMs, timeoutAffinity);
John Spurlocka492d1d2015-05-05 18:30:28 -040096 return audienceMatches(config.allowCallsFrom, contactAffinity);
John Spurlockb2278d62015-04-07 12:47:12 -040097 }
98 }
99 return true;
100 }
101
John Spurlock1d7d2242015-04-10 08:10:22 -0400102 private static Bundle extras(NotificationRecord record) {
103 return record != null && record.sbn != null && record.sbn.getNotification() != null
104 ? record.sbn.getNotification().extras : null;
105 }
106
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400107 protected void recordCall(NotificationRecord record) {
108 REPEAT_CALLERS.recordCall(mContext, extras(record));
109 }
110
John Spurlockb2278d62015-04-07 12:47:12 -0400111 public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
Julia Reynoldsccc6ae62018-03-01 16:24:49 -0500112 if (zen == ZEN_MODE_OFF) {
113 return false;
114 }
115 // Make an exception to policy for the notification saying that policy has changed
116 if (NotificationManager.Policy.areAllVisualEffectsSuppressed(config.suppressedVisualEffects)
117 && "android".equals(record.sbn.getPackageName())
118 && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.sbn.getId()) {
119 ZenLog.traceNotIntercepted(record, "systemDndChangedNotification");
120 return false;
121 }
John Spurlockb2278d62015-04-07 12:47:12 -0400122 switch (zen) {
123 case Global.ZEN_MODE_NO_INTERRUPTIONS:
124 // #notevenalarms
125 ZenLog.traceIntercepted(record, "none");
126 return true;
127 case Global.ZEN_MODE_ALARMS:
128 if (isAlarm(record)) {
129 // Alarms only
130 return false;
131 }
132 ZenLog.traceIntercepted(record, "alarmsOnly");
133 return true;
134 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
John Spurlockb2278d62015-04-07 12:47:12 -0400135 // allow user-prioritized packages through in priority mode
136 if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
137 ZenLog.traceNotIntercepted(record, "priorityApp");
138 return false;
139 }
Beverly04216872017-09-28 10:55:32 -0400140
141 if (isAlarm(record)) {
142 if (!config.allowAlarms) {
143 ZenLog.traceIntercepted(record, "!allowAlarms");
144 return true;
145 }
146 return false;
147 }
John Spurlockb2278d62015-04-07 12:47:12 -0400148 if (isCall(record)) {
John Spurlock1d7d2242015-04-10 08:10:22 -0400149 if (config.allowRepeatCallers
150 && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
151 ZenLog.traceNotIntercepted(record, "repeatCaller");
152 return false;
153 }
John Spurlockb2278d62015-04-07 12:47:12 -0400154 if (!config.allowCalls) {
155 ZenLog.traceIntercepted(record, "!allowCalls");
156 return true;
157 }
John Spurlocka492d1d2015-05-05 18:30:28 -0400158 return shouldInterceptAudience(config.allowCallsFrom, record);
John Spurlockb2278d62015-04-07 12:47:12 -0400159 }
160 if (isMessage(record)) {
161 if (!config.allowMessages) {
162 ZenLog.traceIntercepted(record, "!allowMessages");
163 return true;
164 }
John Spurlocka492d1d2015-05-05 18:30:28 -0400165 return shouldInterceptAudience(config.allowMessagesFrom, record);
John Spurlockb2278d62015-04-07 12:47:12 -0400166 }
167 if (isEvent(record)) {
168 if (!config.allowEvents) {
169 ZenLog.traceIntercepted(record, "!allowEvents");
170 return true;
171 }
172 return false;
173 }
174 if (isReminder(record)) {
175 if (!config.allowReminders) {
176 ZenLog.traceIntercepted(record, "!allowReminders");
177 return true;
178 }
179 return false;
180 }
Beverlyd6964762018-02-16 14:07:03 -0500181 if (isMedia(record)) {
182 if (!config.allowMedia) {
183 ZenLog.traceIntercepted(record, "!allowMedia");
184 return true;
185 }
186 return false;
187 }
188 if (isSystem(record)) {
189 if (!config.allowSystem) {
190 ZenLog.traceIntercepted(record, "!allowSystem");
Beverly04216872017-09-28 10:55:32 -0400191 return true;
192 }
193 return false;
194 }
John Spurlockb2278d62015-04-07 12:47:12 -0400195 ZenLog.traceIntercepted(record, "!priority");
196 return true;
197 default:
198 return false;
199 }
200 }
201
John Spurlocka492d1d2015-05-05 18:30:28 -0400202 private static boolean shouldInterceptAudience(int source, NotificationRecord record) {
203 if (!audienceMatches(source, record.getContactAffinity())) {
John Spurlockb2278d62015-04-07 12:47:12 -0400204 ZenLog.traceIntercepted(record, "!audienceMatches");
205 return true;
206 }
207 return false;
208 }
209
John Spurlockb2278d62015-04-07 12:47:12 -0400210 private static boolean isAlarm(NotificationRecord record) {
211 return record.isCategory(Notification.CATEGORY_ALARM)
212 || record.isAudioStream(AudioManager.STREAM_ALARM)
213 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
214 }
215
216 private static boolean isEvent(NotificationRecord record) {
217 return record.isCategory(Notification.CATEGORY_EVENT);
218 }
219
220 private static boolean isReminder(NotificationRecord record) {
221 return record.isCategory(Notification.CATEGORY_REMINDER);
222 }
223
224 public boolean isCall(NotificationRecord record) {
225 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
226 || record.isCategory(Notification.CATEGORY_CALL));
227 }
228
Beverlyd6964762018-02-16 14:07:03 -0500229 public boolean isMedia(NotificationRecord record) {
230 AudioAttributes aa = record.getAudioAttributes();
231 return aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
232 AudioAttributes.SUPPRESSIBLE_MEDIA;
233 }
234
235 public boolean isSystem(NotificationRecord record) {
236 AudioAttributes aa = record.getAudioAttributes();
237 return aa != null && AudioAttributes.SUPPRESSIBLE_USAGES.get(aa.getUsage()) ==
238 AudioAttributes.SUPPRESSIBLE_SYSTEM;
239 }
240
John Spurlockb2278d62015-04-07 12:47:12 -0400241 private boolean isDefaultPhoneApp(String pkg) {
242 if (mDefaultPhoneApp == null) {
243 final TelecomManager telecomm =
244 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
245 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
246 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
247 }
248 return pkg != null && mDefaultPhoneApp != null
249 && pkg.equals(mDefaultPhoneApp.getPackageName());
250 }
251
252 @SuppressWarnings("deprecation")
253 private boolean isDefaultMessagingApp(NotificationRecord record) {
254 final int userId = record.getUserId();
255 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
256 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
257 Secure.SMS_DEFAULT_APPLICATION, userId);
258 return Objects.equals(defaultApp, record.sbn.getPackageName());
259 }
260
261 private boolean isMessage(NotificationRecord record) {
262 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
263 }
264
John Spurlocka492d1d2015-05-05 18:30:28 -0400265 private static boolean audienceMatches(int source, float contactAffinity) {
266 switch (source) {
John Spurlockb2278d62015-04-07 12:47:12 -0400267 case ZenModeConfig.SOURCE_ANYONE:
268 return true;
269 case ZenModeConfig.SOURCE_CONTACT:
270 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
271 case ZenModeConfig.SOURCE_STAR:
272 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
273 default:
John Spurlocka492d1d2015-05-05 18:30:28 -0400274 Slog.w(TAG, "Encountered unknown source: " + source);
John Spurlockb2278d62015-04-07 12:47:12 -0400275 return true;
276 }
277 }
John Spurlock1d7d2242015-04-10 08:10:22 -0400278
279 private static class RepeatCallers {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400280 // Person : time
John Spurlock1d7d2242015-04-10 08:10:22 -0400281 private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
282 private int mThresholdMinutes;
283
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400284 private synchronized void recordCall(Context context, Bundle extras) {
285 setThresholdMinutes(context);
286 if (mThresholdMinutes <= 0 || extras == null) return;
287 final String peopleString = peopleString(extras);
288 if (peopleString == null) return;
289 final long now = System.currentTimeMillis();
290 cleanUp(mCalls, now);
291 mCalls.put(peopleString, now);
292 }
293
John Spurlock1d7d2242015-04-10 08:10:22 -0400294 private synchronized boolean isRepeat(Context context, Bundle extras) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400295 setThresholdMinutes(context);
John Spurlock1d7d2242015-04-10 08:10:22 -0400296 if (mThresholdMinutes <= 0 || extras == null) return false;
297 final String peopleString = peopleString(extras);
298 if (peopleString == null) return false;
299 final long now = System.currentTimeMillis();
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400300 cleanUp(mCalls, now);
301 return mCalls.containsKey(peopleString);
302 }
303
304 private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) {
305 final int N = calls.size();
John Spurlock1d7d2242015-04-10 08:10:22 -0400306 for (int i = N - 1; i >= 0; i--) {
307 final long time = mCalls.valueAt(i);
308 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400309 calls.removeAt(i);
John Spurlock1d7d2242015-04-10 08:10:22 -0400310 }
311 }
Julia Reynoldsc6b371b2016-06-14 08:31:03 -0400312 }
313
314 private void setThresholdMinutes(Context context) {
315 if (mThresholdMinutes <= 0) {
316 mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
317 .config_zen_repeat_callers_threshold);
318 }
John Spurlock1d7d2242015-04-10 08:10:22 -0400319 }
320
321 private static String peopleString(Bundle extras) {
322 final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
323 if (extraPeople == null || extraPeople.length == 0) return null;
324 final StringBuilder sb = new StringBuilder();
325 for (int i = 0; i < extraPeople.length; i++) {
326 String extraPerson = extraPeople[i];
327 if (extraPerson == null) continue;
328 extraPerson = extraPerson.trim();
329 if (extraPerson.isEmpty()) continue;
330 if (sb.length() > 0) {
331 sb.append('|');
332 }
333 sb.append(extraPerson);
334 }
335 return sb.length() == 0 ? null : sb.toString();
336 }
337 }
338
John Spurlockb2278d62015-04-07 12:47:12 -0400339}