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)) return true; 85 if (!config.allowCalls) return false; // no other calls get through 86 if (validator != null) { 87 final float contactAffinity = validator.getContactAffinity(userHandle, extras, 88 contactsTimeoutMs, timeoutAffinity); 89 return audienceMatches(config.allowCallsFrom, contactAffinity); 90 } 91 } 92 return true; 93 } 94 95 private static Bundle extras(NotificationRecord record) { 96 return record != null && record.sbn != null && record.sbn.getNotification() != null 97 ? record.sbn.getNotification().extras : null; 98 } 99 100 public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) { 101 if (isSystem(record)) { 102 return false; 103 } 104 switch (zen) { 105 case Global.ZEN_MODE_NO_INTERRUPTIONS: 106 // #notevenalarms 107 ZenLog.traceIntercepted(record, "none"); 108 return true; 109 case Global.ZEN_MODE_ALARMS: 110 if (isAlarm(record)) { 111 // Alarms only 112 return false; 113 } 114 ZenLog.traceIntercepted(record, "alarmsOnly"); 115 return true; 116 case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: 117 if (isAlarm(record)) { 118 // Alarms are always priority 119 return false; 120 } 121 // allow user-prioritized packages through in priority mode 122 if (record.getPackagePriority() == Notification.PRIORITY_MAX) { 123 ZenLog.traceNotIntercepted(record, "priorityApp"); 124 return false; 125 } 126 if (isCall(record)) { 127 if (config.allowRepeatCallers 128 && REPEAT_CALLERS.isRepeat(mContext, extras(record))) { 129 ZenLog.traceNotIntercepted(record, "repeatCaller"); 130 return false; 131 } 132 if (!config.allowCalls) { 133 ZenLog.traceIntercepted(record, "!allowCalls"); 134 return true; 135 } 136 return shouldInterceptAudience(config.allowCallsFrom, record); 137 } 138 if (isMessage(record)) { 139 if (!config.allowMessages) { 140 ZenLog.traceIntercepted(record, "!allowMessages"); 141 return true; 142 } 143 return shouldInterceptAudience(config.allowMessagesFrom, record); 144 } 145 if (isEvent(record)) { 146 if (!config.allowEvents) { 147 ZenLog.traceIntercepted(record, "!allowEvents"); 148 return true; 149 } 150 return false; 151 } 152 if (isReminder(record)) { 153 if (!config.allowReminders) { 154 ZenLog.traceIntercepted(record, "!allowReminders"); 155 return true; 156 } 157 return false; 158 } 159 ZenLog.traceIntercepted(record, "!priority"); 160 return true; 161 default: 162 return false; 163 } 164 } 165 166 private static boolean shouldInterceptAudience(int source, NotificationRecord record) { 167 if (!audienceMatches(source, record.getContactAffinity())) { 168 ZenLog.traceIntercepted(record, "!audienceMatches"); 169 return true; 170 } 171 return false; 172 } 173 174 private static boolean isSystem(NotificationRecord record) { 175 return record.isCategory(Notification.CATEGORY_SYSTEM); 176 } 177 178 private static boolean isAlarm(NotificationRecord record) { 179 return record.isCategory(Notification.CATEGORY_ALARM) 180 || record.isAudioStream(AudioManager.STREAM_ALARM) 181 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM); 182 } 183 184 private static boolean isEvent(NotificationRecord record) { 185 return record.isCategory(Notification.CATEGORY_EVENT); 186 } 187 188 private static boolean isReminder(NotificationRecord record) { 189 return record.isCategory(Notification.CATEGORY_REMINDER); 190 } 191 192 public boolean isCall(NotificationRecord record) { 193 return record != null && (isDefaultPhoneApp(record.sbn.getPackageName()) 194 || record.isCategory(Notification.CATEGORY_CALL)); 195 } 196 197 private boolean isDefaultPhoneApp(String pkg) { 198 if (mDefaultPhoneApp == null) { 199 final TelecomManager telecomm = 200 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 201 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null; 202 if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp); 203 } 204 return pkg != null && mDefaultPhoneApp != null 205 && pkg.equals(mDefaultPhoneApp.getPackageName()); 206 } 207 208 @SuppressWarnings("deprecation") 209 private boolean isDefaultMessagingApp(NotificationRecord record) { 210 final int userId = record.getUserId(); 211 if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false; 212 final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(), 213 Secure.SMS_DEFAULT_APPLICATION, userId); 214 return Objects.equals(defaultApp, record.sbn.getPackageName()); 215 } 216 217 private boolean isMessage(NotificationRecord record) { 218 return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record); 219 } 220 221 private static boolean audienceMatches(int source, float contactAffinity) { 222 switch (source) { 223 case ZenModeConfig.SOURCE_ANYONE: 224 return true; 225 case ZenModeConfig.SOURCE_CONTACT: 226 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT; 227 case ZenModeConfig.SOURCE_STAR: 228 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT; 229 default: 230 Slog.w(TAG, "Encountered unknown source: " + source); 231 return true; 232 } 233 } 234 235 private static class RepeatCallers { 236 private final ArrayMap<String, Long> mCalls = new ArrayMap<>(); 237 private int mThresholdMinutes; 238 239 private synchronized boolean isRepeat(Context context, Bundle extras) { 240 if (mThresholdMinutes <= 0) { 241 mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer 242 .config_zen_repeat_callers_threshold); 243 } 244 if (mThresholdMinutes <= 0 || extras == null) return false; 245 final String peopleString = peopleString(extras); 246 if (peopleString == null) return false; 247 final long now = System.currentTimeMillis(); 248 final int N = mCalls.size(); 249 for (int i = N - 1; i >= 0; i--) { 250 final long time = mCalls.valueAt(i); 251 if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) { 252 mCalls.removeAt(i); 253 } 254 } 255 final boolean isRepeat = mCalls.containsKey(peopleString); 256 mCalls.put(peopleString, now); 257 return isRepeat; 258 } 259 260 private static String peopleString(Bundle extras) { 261 final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras); 262 if (extraPeople == null || extraPeople.length == 0) return null; 263 final StringBuilder sb = new StringBuilder(); 264 for (int i = 0; i < extraPeople.length; i++) { 265 String extraPerson = extraPeople[i]; 266 if (extraPerson == null) continue; 267 extraPerson = extraPerson.trim(); 268 if (extraPerson.isEmpty()) continue; 269 if (sb.length() > 0) { 270 sb.append('|'); 271 } 272 sb.append(extraPerson); 273 } 274 return sb.length() == 0 ? null : sb.toString(); 275 } 276 } 277 278 } 279