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