Home | History | Annotate | Download | only in notification
      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