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)) {
     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