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