1 /** 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 17 package com.android.server.notification; 18 19 import android.app.Notification; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.media.AudioAttributes; 23 import android.media.AudioManager; 24 import android.os.Bundle; 25 import android.os.UserHandle; 26 import android.provider.Settings.Global; 27 import android.provider.Settings.Secure; 28 import android.service.notification.ZenModeConfig; 29 import android.telecom.TelecomManager; 30 import android.util.ArrayMap; 31 import android.util.Slog; 32 33 import java.io.PrintWriter; 34 import java.util.Date; 35 import java.util.Objects; 36 37 public class ZenModeFiltering { 38 private static final String TAG = ZenModeHelper.TAG; 39 private static final boolean DEBUG = ZenModeHelper.DEBUG; 40 41 static final RepeatCallers REPEAT_CALLERS = new RepeatCallers(); 42 43 private final Context mContext; 44 45 private ComponentName mDefaultPhoneApp; 46 47 public ZenModeFiltering(Context context) { 48 mContext = context; 49 } 50 51 public void dump(PrintWriter pw, String prefix) { 52 pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp); 53 pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes="); 54 pw.println(REPEAT_CALLERS.mThresholdMinutes); 55 synchronized (REPEAT_CALLERS) { 56 if (!REPEAT_CALLERS.mCalls.isEmpty()) { 57 pw.print(prefix); pw.println("RepeatCallers.mCalls="); 58 for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) { 59 pw.print(prefix); pw.print(" "); 60 pw.print(REPEAT_CALLERS.mCalls.keyAt(i)); 61 pw.print(" at "); 62 pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i))); 63 } 64 } 65 } 66 } 67 68 private static String ts(long time) { 69 return new Date(time) + " (" + time + ")"; 70 } 71 72 /** 73 * @param extras extras of the notification with EXTRA_PEOPLE populated 74 * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response 75 * @param timeoutAffinity affinity to return when the timeout specified via 76 * <code>contactsTimeoutMs</code> is hit 77 */ 78 public static boolean matchesCallFilter(Context context, int zen, ZenModeConfig config, 79 UserHandle userHandle, Bundle extras, ValidateNotificationPeople validator, 80 int contactsTimeoutMs, float timeoutAffinity) { 81 if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through 82 if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm 83 if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { 84 if (config.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) { 85 return true; 86 } 87 if (!config.allowCalls) return false; // no other calls get through 88 if (validator != null) { 89 final float contactAffinity = validator.getContactAffinity(userHandle, extras, 90 contactsTimeoutMs, timeoutAffinity); 91 return audienceMatches(config.allowCallsFrom, contactAffinity); 92 } 93 } 94 return true; 95 } 96 97 private static Bundle extras(NotificationRecord record) { 98 return record != null && record.sbn != null && record.sbn.getNotification() != null 99 ? record.sbn.getNotification().extras : null; 100 } 101 102 protected void recordCall(NotificationRecord record) { 103 REPEAT_CALLERS.recordCall(mContext, extras(record)); 104 } 105 106 public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) { 107 if (isSystem(record)) { 108 return false; 109 } 110 switch (zen) { 111 case Global.ZEN_MODE_NO_INTERRUPTIONS: 112 // #notevenalarms 113 ZenLog.traceIntercepted(record, "none"); 114 return true; 115 case Global.ZEN_MODE_ALARMS: 116 if (isAlarm(record)) { 117 // Alarms only 118 return false; 119 } 120 ZenLog.traceIntercepted(record, "alarmsOnly"); 121 return true; 122 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 123 if (isAlarm(record)) { 124 // Alarms are always priority 125 return false; 126 } 127 // allow user-prioritized packages through in priority mode 128 if (record.getPackagePriority() == Notification.PRIORITY_MAX) { 129 ZenLog.traceNotIntercepted(record, "priorityApp"); 130 return false; 131 } 132 if (isCall(record)) { 133 if (config.allowRepeatCallers 134 && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { 135 ZenLog.traceNotIntercepted(record, "repeatCaller"); 136 return false; 137 } 138 if (!config.allowCalls) { 139 ZenLog.traceIntercepted(record, "!allowCalls"); 140 return true; 141 } 142 return shouldInterceptAudience(config.allowCallsFrom, record); 143 } 144 if (isMessage(record)) { 145 if (!config.allowMessages) { 146 ZenLog.traceIntercepted(record, "!allowMessages"); 147 return true; 148 } 149 return shouldInterceptAudience(config.allowMessagesFrom, record); 150 } 151 if (isEvent(record)) { 152 if (!config.allowEvents) { 153 ZenLog.traceIntercepted(record, "!allowEvents"); 154 return true; 155 } 156 return false; 157 } 158 if (isReminder(record)) { 159 if (!config.allowReminders) { 160 ZenLog.traceIntercepted(record, "!allowReminders"); 161 return true; 162 } 163 return false; 164 } 165 ZenLog.traceIntercepted(record, "!priority"); 166 return true; 167 default: 168 return false; 169 } 170 } 171 172 private static boolean shouldInterceptAudience(int source, NotificationRecord record) { 173 if (!audienceMatches(source, record.getContactAffinity())) { 174 ZenLog.traceIntercepted(record, "!audienceMatches"); 175 return true; 176 } 177 return false; 178 } 179 180 private static boolean isSystem(NotificationRecord record) { 181 return record.isCategory(Notification.CATEGORY_SYSTEM); 182 } 183 184 private static boolean isAlarm(NotificationRecord record) { 185 return record.isCategory(Notification.CATEGORY_ALARM) 186 || record.isAudioStream(AudioManager.STREAM_ALARM) 187 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 188 } 189 190 private static boolean isEvent(NotificationRecord record) { 191 return record.isCategory(Notification.CATEGORY_EVENT); 192 } 193 194 private static boolean isReminder(NotificationRecord record) { 195 return record.isCategory(Notification.CATEGORY_REMINDER); 196 } 197 198 public boolean isCall(NotificationRecord record) { 199 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) 200 || record.isCategory(Notification.CATEGORY_CALL)); 201 } 202 203 private boolean isDefaultPhoneApp(String pkg) { 204 if (mDefaultPhoneApp == null) { 205 final TelecomManager telecomm = 206 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 207 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 208 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 209 } 210 return pkg != null && mDefaultPhoneApp != null 211 && pkg.equals(mDefaultPhoneApp.getPackageName()); 212 } 213 214 @SuppressWarnings("deprecation") 215 private boolean isDefaultMessagingApp(NotificationRecord record) { 216 final int userId = record.getUserId(); 217 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 218 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 219 Secure.SMS_DEFAULT_APPLICATION, userId); 220 return Objects.equals(defaultApp, record.sbn.getPackageName()); 221 } 222 223 private boolean isMessage(NotificationRecord record) { 224 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 225 } 226 227 private static boolean audienceMatches(int source, float contactAffinity) { 228 switch (source) { 229 case ZenModeConfig.SOURCE_ANYONE: 230 return true; 231 case ZenModeConfig.SOURCE_CONTACT: 232 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; 233 case ZenModeConfig.SOURCE_STAR: 234 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; 235 default: 236 Slog.w(TAG, "Encountered unknown source: " + source); 237 return true; 238 } 239 } 240 241 private static class RepeatCallers { 242 // Person : time 243 private final ArrayMap<String, Long> mCalls = new ArrayMap<>(); 244 private int mThresholdMinutes; 245 246 private synchronized void recordCall(Context context, Bundle extras) { 247 setThresholdMinutes(context); 248 if (mThresholdMinutes <= 0 || extras == null) return; 249 final String peopleString = peopleString(extras); 250 if (peopleString == null) return; 251 final long now = System.currentTimeMillis(); 252 cleanUp(mCalls, now); 253 mCalls.put(peopleString, now); 254 } 255 256 private synchronized boolean isRepeat(Context context, Bundle extras) { 257 setThresholdMinutes(context); 258 if (mThresholdMinutes <= 0 || extras == null) return false; 259 final String peopleString = peopleString(extras); 260 if (peopleString == null) return false; 261 final long now = System.currentTimeMillis(); 262 cleanUp(mCalls, now); 263 return mCalls.containsKey(peopleString); 264 } 265 266 private synchronized void cleanUp(ArrayMap<String, Long> calls, long now) { 267 final int N = calls.size(); 268 for (int i = N - 1; i >= 0; i--) { 269 final long time = mCalls.valueAt(i); 270 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) { 271 calls.removeAt(i); 272 } 273 } 274 } 275 276 private void setThresholdMinutes(Context context) { 277 if (mThresholdMinutes <= 0) { 278 mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer 279 .config_zen_repeat_callers_threshold); 280 } 281 } 282 283 private static String peopleString(Bundle extras) { 284 final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); 285 if (extraPeople == null || extraPeople.length == 0) return null; 286 final StringBuilder sb = new StringBuilder(); 287 for (int i = 0; i < extraPeople.length; i++) { 288 String extraPerson = extraPeople[i]; 289 if (extraPerson == null) continue; 290 extraPerson = extraPerson.trim(); 291 if (extraPerson.isEmpty()) continue; 292 if (sb.length() > 0) { 293 sb.append('|'); 294 } 295 sb.append(extraPerson); 296 } 297 return sb.length() == 0 ? null : sb.toString(); 298 } 299 } 300 301 } 302