Home | History | Annotate | Download | only in notification
      1 /**
      2  * Copyright (c) 2014, 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 static android.media.AudioAttributes.USAGE_ALARM;
     20 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
     21 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
     22 
     23 import android.app.AppOpsManager;
     24 import android.app.Notification;
     25 import android.content.ComponentName;
     26 import android.content.ContentResolver;
     27 import android.content.Context;
     28 import android.content.res.Resources;
     29 import android.content.res.XmlResourceParser;
     30 import android.database.ContentObserver;
     31 import android.media.AudioAttributes;
     32 import android.media.AudioManager;
     33 import android.media.AudioManagerInternal;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.os.Handler;
     37 import android.os.Looper;
     38 import android.os.Message;
     39 import android.os.UserHandle;
     40 import android.provider.Settings.Global;
     41 import android.provider.Settings.Secure;
     42 import android.service.notification.NotificationListenerService;
     43 import android.service.notification.ZenModeConfig;
     44 import android.telecom.TelecomManager;
     45 import android.util.Log;
     46 import android.util.Slog;
     47 
     48 import com.android.internal.R;
     49 import com.android.server.LocalServices;
     50 
     51 import libcore.io.IoUtils;
     52 
     53 import org.xmlpull.v1.XmlPullParser;
     54 import org.xmlpull.v1.XmlPullParserException;
     55 import org.xmlpull.v1.XmlSerializer;
     56 
     57 import java.io.IOException;
     58 import java.io.PrintWriter;
     59 import java.util.ArrayList;
     60 import java.util.Objects;
     61 
     62 /**
     63  * NotificationManagerService helper for functionality related to zen mode.
     64  */
     65 public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
     66     private static final String TAG = "ZenModeHelper";
     67     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     68 
     69     private final Context mContext;
     70     private final H mHandler;
     71     private final SettingsObserver mSettingsObserver;
     72     private final AppOpsManager mAppOps;
     73     private final ZenModeConfig mDefaultConfig;
     74     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
     75 
     76     private ComponentName mDefaultPhoneApp;
     77     private int mZenMode;
     78     private ZenModeConfig mConfig;
     79     private AudioManagerInternal mAudioManager;
     80     private int mPreviousRingerMode = -1;
     81     private boolean mEffectsSuppressed;
     82 
     83     public ZenModeHelper(Context context, Looper looper) {
     84         mContext = context;
     85         mHandler = new H(looper);
     86         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     87         mDefaultConfig = readDefaultConfig(context.getResources());
     88         mConfig = mDefaultConfig;
     89         mSettingsObserver = new SettingsObserver(mHandler);
     90         mSettingsObserver.observe();
     91     }
     92 
     93     public static ZenModeConfig readDefaultConfig(Resources resources) {
     94         XmlResourceParser parser = null;
     95         try {
     96             parser = resources.getXml(R.xml.default_zen_mode_config);
     97             while (parser.next() != XmlPullParser.END_DOCUMENT) {
     98                 final ZenModeConfig config = ZenModeConfig.readXml(parser);
     99                 if (config != null) return config;
    100             }
    101         } catch (Exception e) {
    102             Slog.w(TAG, "Error reading default zen mode config from resource", e);
    103         } finally {
    104             IoUtils.closeQuietly(parser);
    105         }
    106         return new ZenModeConfig();
    107     }
    108 
    109     public void addCallback(Callback callback) {
    110         mCallbacks.add(callback);
    111     }
    112 
    113     public void removeCallback(Callback callback) {
    114         mCallbacks.remove(callback);
    115     }
    116 
    117     public void onSystemReady() {
    118         mAudioManager = LocalServices.getService(AudioManagerInternal.class);
    119         if (mAudioManager != null) {
    120             mAudioManager.setRingerModeDelegate(this);
    121         }
    122     }
    123 
    124     public int getZenModeListenerInterruptionFilter() {
    125         switch (mZenMode) {
    126             case Global.ZEN_MODE_OFF:
    127                 return NotificationListenerService.INTERRUPTION_FILTER_ALL;
    128             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    129                 return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
    130             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    131                 return NotificationListenerService.INTERRUPTION_FILTER_NONE;
    132             default:
    133                 return 0;
    134         }
    135     }
    136 
    137     private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
    138             int defValue) {
    139         switch (listenerInterruptionFilter) {
    140             case NotificationListenerService.INTERRUPTION_FILTER_ALL:
    141                 return Global.ZEN_MODE_OFF;
    142             case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
    143                 return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
    144             case NotificationListenerService.INTERRUPTION_FILTER_NONE:
    145                 return Global.ZEN_MODE_NO_INTERRUPTIONS;
    146             default:
    147                 return defValue;
    148         }
    149     }
    150 
    151     public void requestFromListener(ComponentName name, int interruptionFilter) {
    152         final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
    153         if (newZen != -1) {
    154             setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
    155         }
    156     }
    157 
    158     public void setEffectsSuppressed(boolean effectsSuppressed) {
    159         if (mEffectsSuppressed == effectsSuppressed) return;
    160         mEffectsSuppressed = effectsSuppressed;
    161         applyRestrictions();
    162     }
    163 
    164     public boolean shouldIntercept(NotificationRecord record) {
    165         if (isSystem(record)) {
    166             return false;
    167         }
    168         switch (mZenMode) {
    169             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    170                 // #notevenalarms
    171                 ZenLog.traceIntercepted(record, "none");
    172                 return true;
    173             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    174                 if (isAlarm(record)) {
    175                     // Alarms are always priority
    176                     return false;
    177                 }
    178                 // allow user-prioritized packages through in priority mode
    179                 if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
    180                     ZenLog.traceNotIntercepted(record, "priorityApp");
    181                     return false;
    182                 }
    183                 if (isCall(record)) {
    184                     if (!mConfig.allowCalls) {
    185                         ZenLog.traceIntercepted(record, "!allowCalls");
    186                         return true;
    187                     }
    188                     return shouldInterceptAudience(record);
    189                 }
    190                 if (isMessage(record)) {
    191                     if (!mConfig.allowMessages) {
    192                         ZenLog.traceIntercepted(record, "!allowMessages");
    193                         return true;
    194                     }
    195                     return shouldInterceptAudience(record);
    196                 }
    197                 if (isEvent(record)) {
    198                     if (!mConfig.allowEvents) {
    199                         ZenLog.traceIntercepted(record, "!allowEvents");
    200                         return true;
    201                     }
    202                     return false;
    203                 }
    204                 ZenLog.traceIntercepted(record, "!priority");
    205                 return true;
    206             default:
    207                 return false;
    208         }
    209     }
    210 
    211     private boolean shouldInterceptAudience(NotificationRecord record) {
    212         if (!audienceMatches(record.getContactAffinity())) {
    213             ZenLog.traceIntercepted(record, "!audienceMatches");
    214             return true;
    215         }
    216         return false;
    217     }
    218 
    219     public int getZenMode() {
    220         return mZenMode;
    221     }
    222 
    223     public void setZenMode(int zenMode, String reason) {
    224         setZenMode(zenMode, reason, true);
    225     }
    226 
    227     private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
    228         ZenLog.traceSetZenMode(zenMode, reason);
    229         if (mZenMode == zenMode) return;
    230         ZenLog.traceUpdateZenMode(mZenMode, zenMode);
    231         mZenMode = zenMode;
    232         Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
    233         if (setRingerMode) {
    234             applyZenToRingerMode();
    235         }
    236         applyRestrictions();
    237         mHandler.postDispatchOnZenModeChanged();
    238     }
    239 
    240     public void readZenModeFromSetting() {
    241         final int newMode = Global.getInt(mContext.getContentResolver(),
    242                 Global.ZEN_MODE, Global.ZEN_MODE_OFF);
    243         setZenMode(newMode, "setting");
    244     }
    245 
    246     private void applyRestrictions() {
    247         final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
    248 
    249         // notification restrictions
    250         final boolean muteNotifications = mEffectsSuppressed;
    251         applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
    252 
    253         // call restrictions
    254         final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed;
    255         applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
    256 
    257         // alarm restrictions
    258         final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
    259         applyRestrictions(muteAlarms, USAGE_ALARM);
    260     }
    261 
    262     private void applyRestrictions(boolean mute, int usage) {
    263         final String[] exceptionPackages = null; // none (for now)
    264         mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
    265                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
    266                 exceptionPackages);
    267         mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, usage,
    268                 mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
    269                 exceptionPackages);
    270     }
    271 
    272     public void dump(PrintWriter pw, String prefix) {
    273         pw.print(prefix); pw.print("mZenMode=");
    274         pw.println(Global.zenModeToString(mZenMode));
    275         pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
    276         pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
    277         pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
    278         pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
    279         pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
    280     }
    281 
    282     public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
    283         final ZenModeConfig config = ZenModeConfig.readXml(parser);
    284         if (config != null) {
    285             setConfig(config);
    286         }
    287     }
    288 
    289     public void writeXml(XmlSerializer out) throws IOException {
    290         mConfig.writeXml(out);
    291     }
    292 
    293     public ZenModeConfig getConfig() {
    294         return mConfig;
    295     }
    296 
    297     public boolean setConfig(ZenModeConfig config) {
    298         if (config == null || !config.isValid()) return false;
    299         if (config.equals(mConfig)) return true;
    300         ZenLog.traceConfig(mConfig, config);
    301         mConfig = config;
    302         dispatchOnConfigChanged();
    303         final String val = Integer.toString(mConfig.hashCode());
    304         Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
    305         applyRestrictions();
    306         return true;
    307     }
    308 
    309     private void applyZenToRingerMode() {
    310         if (mAudioManager == null) return;
    311         // force the ringer mode into compliance
    312         final int ringerModeInternal = mAudioManager.getRingerModeInternal();
    313         int newRingerModeInternal = ringerModeInternal;
    314         switch (mZenMode) {
    315             case Global.ZEN_MODE_NO_INTERRUPTIONS:
    316                 if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) {
    317                     mPreviousRingerMode = ringerModeInternal;
    318                     newRingerModeInternal = AudioManager.RINGER_MODE_SILENT;
    319                 }
    320                 break;
    321             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
    322             case Global.ZEN_MODE_OFF:
    323                 if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
    324                     newRingerModeInternal = mPreviousRingerMode != -1 ? mPreviousRingerMode
    325                             : AudioManager.RINGER_MODE_NORMAL;
    326                     mPreviousRingerMode = -1;
    327                 }
    328                 break;
    329         }
    330         if (newRingerModeInternal != -1) {
    331             mAudioManager.setRingerModeInternal(newRingerModeInternal, TAG);
    332         }
    333     }
    334 
    335     @Override  // RingerModeDelegate
    336     public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
    337             int ringerModeExternal) {
    338         final boolean isChange = ringerModeOld != ringerModeNew;
    339 
    340         int ringerModeExternalOut = ringerModeNew;
    341 
    342         int newZen = -1;
    343         switch (ringerModeNew) {
    344             case AudioManager.RINGER_MODE_SILENT:
    345                 if (isChange) {
    346                     if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS) {
    347                         newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
    348                     }
    349                 }
    350                 break;
    351             case AudioManager.RINGER_MODE_VIBRATE:
    352             case AudioManager.RINGER_MODE_NORMAL:
    353                 if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
    354                         && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
    355                     newZen = Global.ZEN_MODE_OFF;
    356                 } else if (mZenMode != Global.ZEN_MODE_OFF) {
    357                     ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
    358                 }
    359                 break;
    360         }
    361         if (newZen != -1) {
    362             setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
    363         }
    364 
    365         if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
    366             ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
    367                     ringerModeExternal, ringerModeExternalOut);
    368         }
    369         return ringerModeExternalOut;
    370     }
    371 
    372     @Override  // RingerModeDelegate
    373     public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
    374             int ringerModeInternal) {
    375         int ringerModeInternalOut = ringerModeNew;
    376         final boolean isChange = ringerModeOld != ringerModeNew;
    377         final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
    378 
    379         int newZen = -1;
    380         switch (ringerModeNew) {
    381             case AudioManager.RINGER_MODE_SILENT:
    382                 if (isChange) {
    383                     if (mZenMode == Global.ZEN_MODE_OFF) {
    384                         newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
    385                     }
    386                     ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
    387                             : AudioManager.RINGER_MODE_NORMAL;
    388                 } else {
    389                     ringerModeInternalOut = ringerModeInternal;
    390                 }
    391                 break;
    392             case AudioManager.RINGER_MODE_VIBRATE:
    393             case AudioManager.RINGER_MODE_NORMAL:
    394                 if (mZenMode != Global.ZEN_MODE_OFF) {
    395                     newZen = Global.ZEN_MODE_OFF;
    396                 }
    397                 break;
    398         }
    399         if (newZen != -1) {
    400             setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
    401         }
    402 
    403         ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
    404                 ringerModeInternalOut);
    405         return ringerModeInternalOut;
    406     }
    407 
    408     private void dispatchOnConfigChanged() {
    409         for (Callback callback : mCallbacks) {
    410             callback.onConfigChanged();
    411         }
    412     }
    413 
    414     private void dispatchOnZenModeChanged() {
    415         for (Callback callback : mCallbacks) {
    416             callback.onZenModeChanged();
    417         }
    418     }
    419 
    420     private static boolean isSystem(NotificationRecord record) {
    421         return record.isCategory(Notification.CATEGORY_SYSTEM);
    422     }
    423 
    424     private static boolean isAlarm(NotificationRecord record) {
    425         return record.isCategory(Notification.CATEGORY_ALARM)
    426                 || record.isAudioStream(AudioManager.STREAM_ALARM)
    427                 || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
    428     }
    429 
    430     private static boolean isEvent(NotificationRecord record) {
    431         return record.isCategory(Notification.CATEGORY_EVENT);
    432     }
    433 
    434     public boolean isCall(NotificationRecord record) {
    435         return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
    436                 || record.isCategory(Notification.CATEGORY_CALL));
    437     }
    438 
    439     private boolean isDefaultPhoneApp(String pkg) {
    440         if (mDefaultPhoneApp == null) {
    441             final TelecomManager telecomm =
    442                     (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
    443             mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
    444             if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
    445         }
    446         return pkg != null && mDefaultPhoneApp != null
    447                 && pkg.equals(mDefaultPhoneApp.getPackageName());
    448     }
    449 
    450     private boolean isDefaultMessagingApp(NotificationRecord record) {
    451         final int userId = record.getUserId();
    452         if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
    453         final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
    454                 Secure.SMS_DEFAULT_APPLICATION, userId);
    455         return Objects.equals(defaultApp, record.sbn.getPackageName());
    456     }
    457 
    458     private boolean isMessage(NotificationRecord record) {
    459         return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
    460     }
    461 
    462     /**
    463      * @param extras extras of the notification with EXTRA_PEOPLE populated
    464      * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
    465      * @param timeoutAffinity affinity to return when the timeout specified via
    466      *                        <code>contactsTimeoutMs</code> is hit
    467      */
    468     public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
    469             ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
    470         final int zen = mZenMode;
    471         if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
    472         if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
    473             if (!mConfig.allowCalls) return false; // no calls get through
    474             if (validator != null) {
    475                 final float contactAffinity = validator.getContactAffinity(userHandle, extras,
    476                         contactsTimeoutMs, timeoutAffinity);
    477                 return audienceMatches(contactAffinity);
    478             }
    479         }
    480         return true;
    481     }
    482 
    483     @Override
    484     public String toString() {
    485         return TAG;
    486     }
    487 
    488     private boolean audienceMatches(float contactAffinity) {
    489         switch (mConfig.allowFrom) {
    490             case ZenModeConfig.SOURCE_ANYONE:
    491                 return true;
    492             case ZenModeConfig.SOURCE_CONTACT:
    493                 return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
    494             case ZenModeConfig.SOURCE_STAR:
    495                 return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
    496             default:
    497                 Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
    498                 return true;
    499         }
    500     }
    501 
    502     private class SettingsObserver extends ContentObserver {
    503         private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
    504 
    505         public SettingsObserver(Handler handler) {
    506             super(handler);
    507         }
    508 
    509         public void observe() {
    510             final ContentResolver resolver = mContext.getContentResolver();
    511             resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
    512             update(null);
    513         }
    514 
    515         @Override
    516         public void onChange(boolean selfChange, Uri uri) {
    517             update(uri);
    518         }
    519 
    520         public void update(Uri uri) {
    521             if (ZEN_MODE.equals(uri)) {
    522                 readZenModeFromSetting();
    523             }
    524         }
    525     }
    526 
    527     private class H extends Handler {
    528         private static final int MSG_DISPATCH = 1;
    529 
    530         private H(Looper looper) {
    531             super(looper);
    532         }
    533 
    534         private void postDispatchOnZenModeChanged() {
    535             removeMessages(MSG_DISPATCH);
    536             sendEmptyMessage(MSG_DISPATCH);
    537         }
    538 
    539         @Override
    540         public void handleMessage(Message msg) {
    541             switch (msg.what) {
    542                 case MSG_DISPATCH:
    543                     dispatchOnZenModeChanged();
    544                     break;
    545             }
    546         }
    547     }
    548 
    549     public static class Callback {
    550         void onConfigChanged() {}
    551         void onZenModeChanged() {}
    552     }
    553 }
    554