ZenModeHelper.java revision 1b8b22b1a412539020f78a132cff7c8fa7fae258
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_NOTIFICATION;
20import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
21
22import android.app.AppOpsManager;
23import android.app.NotificationManager;
24import android.app.NotificationManager.Policy;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.res.Resources;
29import android.content.res.XmlResourceParser;
30import android.database.ContentObserver;
31import android.media.AudioManager;
32import android.media.AudioManagerInternal;
33import android.media.AudioSystem;
34import android.media.VolumePolicy;
35import android.net.Uri;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
40import android.os.UserHandle;
41import android.provider.Settings.Global;
42import android.service.notification.IConditionListener;
43import android.service.notification.ZenModeConfig;
44import android.service.notification.ZenModeConfig.EventInfo;
45import android.service.notification.ZenModeConfig.ScheduleInfo;
46import android.service.notification.ZenModeConfig.ZenRule;
47import android.util.ArraySet;
48import android.util.Log;
49
50import com.android.internal.R;
51import com.android.server.LocalServices;
52
53import libcore.io.IoUtils;
54
55import org.xmlpull.v1.XmlPullParser;
56import org.xmlpull.v1.XmlPullParserException;
57import org.xmlpull.v1.XmlSerializer;
58
59import java.io.IOException;
60import java.io.PrintWriter;
61import java.util.ArrayList;
62import java.util.Objects;
63
64/**
65 * NotificationManagerService helper for functionality related to zen mode.
66 */
67public class ZenModeHelper {
68    static final String TAG = "ZenModeHelper";
69    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
70
71    private final Context mContext;
72    private final H mHandler;
73    private final SettingsObserver mSettingsObserver;
74    private final AppOpsManager mAppOps;
75    private final ZenModeConfig mDefaultConfig;
76    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
77    private final ZenModeFiltering mFiltering;
78    private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
79    private final ZenModeConditions mConditions;
80
81    private int mZenMode;
82    private ZenModeConfig mConfig;
83    private AudioManagerInternal mAudioManager;
84    private int mPreviousRingerMode = -1;
85    private boolean mEffectsSuppressed;
86
87    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
88        mContext = context;
89        mHandler = new H(looper);
90        mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
91        mDefaultConfig = readDefaultConfig(context.getResources());
92        appendDefaultScheduleRules(mDefaultConfig);
93        appendDefaultEventRules(mDefaultConfig);
94        mConfig = mDefaultConfig;
95        mSettingsObserver = new SettingsObserver(mHandler);
96        mSettingsObserver.observe();
97        mFiltering = new ZenModeFiltering(mContext);
98        mConditions = new ZenModeConditions(this, conditionProviders);
99    }
100
101    public Looper getLooper() {
102        return mHandler.getLooper();
103    }
104
105    @Override
106    public String toString() {
107        return TAG;
108    }
109
110    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
111            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
112        return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
113                validator, contactsTimeoutMs, timeoutAffinity);
114    }
115
116    public boolean isCall(NotificationRecord record) {
117        return mFiltering.isCall(record);
118    }
119
120    public boolean shouldIntercept(NotificationRecord record) {
121        return mFiltering.shouldIntercept(mZenMode, mConfig, record);
122    }
123
124    public void addCallback(Callback callback) {
125        mCallbacks.add(callback);
126    }
127
128    public void removeCallback(Callback callback) {
129        mCallbacks.remove(callback);
130    }
131
132    public void initZenMode() {
133        if (DEBUG) Log.d(TAG, "initZenMode");
134        evaluateZenMode("init", true /*setRingerMode*/);
135    }
136
137    public void onSystemReady() {
138        if (DEBUG) Log.d(TAG, "onSystemReady");
139        mAudioManager = LocalServices.getService(AudioManagerInternal.class);
140        if (mAudioManager != null) {
141            mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
142        }
143    }
144
145    public void requestZenModeConditions(IConditionListener callback, int relevance) {
146        mConditions.requestConditions(callback, relevance);
147    }
148
149    public int getZenModeListenerInterruptionFilter() {
150        return NotificationManager.zenModeToInterruptionFilter(mZenMode);
151    }
152
153    public void requestFromListener(ComponentName name, int filter) {
154        final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
155        if (newZen != -1) {
156            setManualZenMode(newZen, null,
157                    "listener:" + (name != null ? name.flattenToShortString() : null));
158        }
159    }
160
161    public void setEffectsSuppressed(boolean effectsSuppressed) {
162        if (mEffectsSuppressed == effectsSuppressed) return;
163        mEffectsSuppressed = effectsSuppressed;
164        applyRestrictions();
165    }
166
167    public int getZenMode() {
168        return mZenMode;
169    }
170
171    public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
172        setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
173    }
174
175    private void setManualZenMode(int zenMode, Uri conditionId, String reason,
176            boolean setRingerMode) {
177        if (mConfig == null) return;
178        if (!Global.isValidZenMode(zenMode)) return;
179        if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
180                + " conditionId=" + conditionId + " reason=" + reason
181                + " setRingerMode=" + setRingerMode);
182        final ZenModeConfig newConfig = mConfig.copy();
183        if (zenMode == Global.ZEN_MODE_OFF) {
184            newConfig.manualRule = null;
185            for (ZenRule automaticRule : newConfig.automaticRules.values()) {
186                if (automaticRule.isAutomaticActive()) {
187                    automaticRule.snoozing = true;
188                }
189            }
190        } else {
191            final ZenRule newRule = new ZenRule();
192            newRule.enabled = true;
193            newRule.zenMode = zenMode;
194            newRule.conditionId = conditionId;
195            newConfig.manualRule = newRule;
196        }
197        setConfig(newConfig, reason, setRingerMode);
198    }
199
200    public void dump(PrintWriter pw, String prefix) {
201        pw.print(prefix); pw.print("mZenMode=");
202        pw.println(Global.zenModeToString(mZenMode));
203        dump(pw, prefix, "mConfig", mConfig);
204        dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
205        pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
206        pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
207        mFiltering.dump(pw, prefix);
208        mConditions.dump(pw, prefix);
209    }
210
211    private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
212        pw.print(prefix); pw.print(var); pw.print('=');
213        if (config == null) {
214            pw.println(config);
215            return;
216        }
217        pw.printf("allow(calls=%s,callsFrom=%s,repeatCallers=%s,messages=%s,messagesFrom=%s,"
218                + "events=%s,reminders=%s)\n",
219                config.allowCalls, config.allowCallsFrom, config.allowRepeatCallers,
220                config.allowMessages, config.allowMessagesFrom,
221                config.allowEvents, config.allowReminders);
222        pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
223        if (config.automaticRules.isEmpty()) return;
224        final int N = config.automaticRules.size();
225        for (int i = 0; i < N; i++) {
226            pw.print(prefix); pw.print(i == 0 ? "  automaticRules=" : "                 ");
227            pw.println(config.automaticRules.valueAt(i));
228        }
229    }
230
231    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
232        final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
233        if (config != null) {
234            if (DEBUG) Log.d(TAG, "readXml");
235            setConfig(config, "readXml");
236        }
237    }
238
239    public void writeXml(XmlSerializer out) throws IOException {
240        mConfig.writeXml(out);
241    }
242
243    public Policy getNotificationPolicy() {
244        return getNotificationPolicy(mConfig);
245    }
246
247    private static Policy getNotificationPolicy(ZenModeConfig config) {
248        return config == null ? null : config.toNotificationPolicy();
249    }
250
251    public void setNotificationPolicy(Policy policy) {
252        if (policy == null || mConfig == null) return;
253        final ZenModeConfig newConfig = mConfig.copy();
254        newConfig.applyNotificationPolicy(policy);
255        setConfig(newConfig, "setNotificationPolicy");
256    }
257
258    public ZenModeConfig getConfig() {
259        return mConfig;
260    }
261
262    public boolean setConfig(ZenModeConfig config, String reason) {
263        return setConfig(config, reason, true /*setRingerMode*/);
264    }
265
266    private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
267        if (config == null || !config.isValid()) {
268            Log.w(TAG, "Invalid config in setConfig; " + config);
269            return false;
270        }
271        mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
272        if (config.equals(mConfig)) return true;
273        if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
274        ZenLog.traceConfig(reason, config);
275        final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
276                getNotificationPolicy(config));
277        mConfig = config;
278        dispatchOnConfigChanged();
279        if (policyChanged){
280            dispatchOnPolicyChanged();
281        }
282        final String val = Integer.toString(mConfig.hashCode());
283        Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
284        if (!evaluateZenMode(reason, setRingerMode)) {
285            applyRestrictions();  // evaluateZenMode will also apply restrictions if changed
286        }
287        mConditions.evaluateConfig(config, true /*processSubscriptions*/);
288        return true;
289    }
290
291    private int getZenModeSetting() {
292        return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
293    }
294
295    private void setZenModeSetting(int zen) {
296        Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
297    }
298
299    private boolean evaluateZenMode(String reason, boolean setRingerMode) {
300        if (DEBUG) Log.d(TAG, "evaluateZenMode");
301        final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
302        final int zen = computeZenMode(automaticRules);
303        if (zen == mZenMode) return false;
304        ZenLog.traceSetZenMode(zen, reason);
305        mZenMode = zen;
306        updateRingerModeAffectedStreams();
307        setZenModeSetting(mZenMode);
308        if (setRingerMode) {
309            applyZenToRingerMode();
310        }
311        applyRestrictions();
312        mHandler.postDispatchOnZenModeChanged();
313        return true;
314    }
315
316    private void updateRingerModeAffectedStreams() {
317        if (mAudioManager != null) {
318            mAudioManager.updateRingerModeAffectedStreamsInternal();
319        }
320    }
321
322    private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
323        if (mConfig == null) return Global.ZEN_MODE_OFF;
324        if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
325        int zen = Global.ZEN_MODE_OFF;
326        for (ZenRule automaticRule : mConfig.automaticRules.values()) {
327            if (automaticRule.isAutomaticActive()) {
328                if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
329                    zen = automaticRule.zenMode;
330                }
331            }
332        }
333        return zen;
334    }
335
336    private void applyRestrictions() {
337        final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
338
339        // notification restrictions
340        final boolean muteNotifications = mEffectsSuppressed;
341        applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
342
343        // call restrictions
344        final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
345                || mEffectsSuppressed;
346        applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
347    }
348
349    private void applyRestrictions(boolean mute, int usage) {
350        final String[] exceptionPackages = null; // none (for now)
351        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
352                mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
353                exceptionPackages);
354        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, usage,
355                mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
356                exceptionPackages);
357    }
358
359    private void applyZenToRingerMode() {
360        if (mAudioManager == null) return;
361        // force the ringer mode into compliance
362        final int ringerModeInternal = mAudioManager.getRingerModeInternal();
363        int newRingerModeInternal = ringerModeInternal;
364        switch (mZenMode) {
365            case Global.ZEN_MODE_NO_INTERRUPTIONS:
366            case Global.ZEN_MODE_ALARMS:
367                if (ringerModeInternal != AudioManager.RINGER_MODE_SILENT) {
368                    mPreviousRingerMode = ringerModeInternal;
369                    newRingerModeInternal = AudioManager.RINGER_MODE_SILENT;
370                }
371                break;
372            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
373            case Global.ZEN_MODE_OFF:
374                if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
375                    newRingerModeInternal = mPreviousRingerMode != -1 ? mPreviousRingerMode
376                            : AudioManager.RINGER_MODE_NORMAL;
377                    mPreviousRingerMode = -1;
378                }
379                break;
380        }
381        if (newRingerModeInternal != -1) {
382            mAudioManager.setRingerModeInternal(newRingerModeInternal, TAG);
383        }
384    }
385
386    private void dispatchOnConfigChanged() {
387        for (Callback callback : mCallbacks) {
388            callback.onConfigChanged();
389        }
390    }
391
392    private void dispatchOnPolicyChanged() {
393        for (Callback callback : mCallbacks) {
394            callback.onPolicyChanged();
395        }
396    }
397
398    private void dispatchOnZenModeChanged() {
399        for (Callback callback : mCallbacks) {
400            callback.onZenModeChanged();
401        }
402    }
403
404    private ZenModeConfig readDefaultConfig(Resources resources) {
405        XmlResourceParser parser = null;
406        try {
407            parser = resources.getXml(R.xml.default_zen_mode_config);
408            while (parser.next() != XmlPullParser.END_DOCUMENT) {
409                final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
410                if (config != null) return config;
411            }
412        } catch (Exception e) {
413            Log.w(TAG, "Error reading default zen mode config from resource", e);
414        } finally {
415            IoUtils.closeQuietly(parser);
416        }
417        return new ZenModeConfig();
418    }
419
420    private void appendDefaultScheduleRules(ZenModeConfig config) {
421        if (config == null) return;
422
423        final ScheduleInfo weeknights = new ScheduleInfo();
424        weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
425        weeknights.startHour = 22;
426        weeknights.endHour = 7;
427        final ZenRule rule1 = new ZenRule();
428        rule1.enabled = false;
429        rule1.name = mContext.getResources()
430                .getString(R.string.zen_mode_default_weeknights_name);
431        rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
432        rule1.zenMode = Global.ZEN_MODE_ALARMS;
433        config.automaticRules.put(config.newRuleId(), rule1);
434
435        final ScheduleInfo weekends = new ScheduleInfo();
436        weekends.days = ZenModeConfig.WEEKEND_DAYS;
437        weekends.startHour = 23;
438        weekends.startMinute = 30;
439        weekends.endHour = 10;
440        final ZenRule rule2 = new ZenRule();
441        rule2.enabled = false;
442        rule2.name = mContext.getResources()
443                .getString(R.string.zen_mode_default_weekends_name);
444        rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
445        rule2.zenMode = Global.ZEN_MODE_ALARMS;
446        config.automaticRules.put(config.newRuleId(), rule2);
447    }
448
449    private void appendDefaultEventRules(ZenModeConfig config) {
450        if (config == null) return;
451
452        final EventInfo events = new EventInfo();
453        events.calendar = EventInfo.ANY_CALENDAR;
454        events.reply = EventInfo.REPLY_YES_OR_MAYBE;
455        final ZenRule rule = new ZenRule();
456        rule.enabled = false;
457        rule.name = mContext.getResources().getString(R.string.zen_mode_default_events_name);
458        rule.conditionId = ZenModeConfig.toEventConditionId(events);
459        rule.zenMode = Global.ZEN_MODE_ALARMS;
460        config.automaticRules.put(config.newRuleId(), rule);
461    }
462
463    private static int zenSeverity(int zen) {
464        switch (zen) {
465            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
466            case Global.ZEN_MODE_ALARMS: return 2;
467            case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
468            default: return 0;
469        }
470    }
471
472    private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
473        @Override
474        public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
475            if (v1 == null) return null;
476            final ZenModeConfig rt = new ZenModeConfig();
477            rt.allowCalls = v1.allowCalls;
478            rt.allowEvents = v1.allowEvents;
479            rt.allowCallsFrom = v1.allowFrom;
480            rt.allowMessages = v1.allowMessages;
481            rt.allowMessagesFrom = v1.allowFrom;
482            rt.allowReminders = v1.allowReminders;
483            // don't migrate current exit condition
484            final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
485            if (days != null && days.length > 0) {
486                Log.i(TAG, "Migrating existing V1 downtime to single schedule");
487                final ScheduleInfo schedule = new ScheduleInfo();
488                schedule.days = days;
489                schedule.startHour = v1.sleepStartHour;
490                schedule.startMinute = v1.sleepStartMinute;
491                schedule.endHour = v1.sleepEndHour;
492                schedule.endMinute = v1.sleepEndMinute;
493                final ZenRule rule = new ZenRule();
494                rule.enabled = true;
495                rule.name = mContext.getResources()
496                        .getString(R.string.zen_mode_downtime_feature_name);
497                rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
498                rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
499                        : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
500                rt.automaticRules.put(rt.newRuleId(), rule);
501            } else {
502                Log.i(TAG, "No existing V1 downtime found, generating default schedules");
503                appendDefaultScheduleRules(rt);
504            }
505            appendDefaultEventRules(rt);
506            return rt;
507        }
508    };
509
510    private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
511        @Override
512        public String toString() {
513            return TAG;
514        }
515
516        @Override
517        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
518                int ringerModeExternal, VolumePolicy policy) {
519            final boolean isChange = ringerModeOld != ringerModeNew;
520
521            int ringerModeExternalOut = ringerModeNew;
522
523            int newZen = -1;
524            switch (ringerModeNew) {
525                case AudioManager.RINGER_MODE_SILENT:
526                    if (isChange && policy.doNotDisturbWhenSilent) {
527                        if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
528                                && mZenMode != Global.ZEN_MODE_ALARMS) {
529                            newZen = Global.ZEN_MODE_ALARMS;
530                        }
531                        mPreviousRingerMode = ringerModeOld;
532                    }
533                    break;
534                case AudioManager.RINGER_MODE_VIBRATE:
535                case AudioManager.RINGER_MODE_NORMAL:
536                    if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
537                            && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
538                                    || mZenMode == Global.ZEN_MODE_ALARMS)) {
539                        newZen = Global.ZEN_MODE_OFF;
540                    } else if (mZenMode != Global.ZEN_MODE_OFF) {
541                        ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
542                    }
543                    break;
544            }
545            if (newZen != -1) {
546                setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
547            }
548
549            if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
550                ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
551                        ringerModeExternal, ringerModeExternalOut);
552            }
553            return ringerModeExternalOut;
554        }
555
556        @Override
557        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
558                int ringerModeInternal, VolumePolicy policy) {
559            int ringerModeInternalOut = ringerModeNew;
560            final boolean isChange = ringerModeOld != ringerModeNew;
561            final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
562
563            int newZen = -1;
564            switch (ringerModeNew) {
565                case AudioManager.RINGER_MODE_SILENT:
566                    if (isChange) {
567                        if (mZenMode == Global.ZEN_MODE_OFF) {
568                            newZen = Global.ZEN_MODE_ALARMS;
569                        }
570                        ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
571                                : AudioManager.RINGER_MODE_SILENT;
572                    } else {
573                        ringerModeInternalOut = ringerModeInternal;
574                    }
575                    break;
576                case AudioManager.RINGER_MODE_VIBRATE:
577                case AudioManager.RINGER_MODE_NORMAL:
578                    if (mZenMode != Global.ZEN_MODE_OFF) {
579                        newZen = Global.ZEN_MODE_OFF;
580                    }
581                    break;
582            }
583            if (newZen != -1) {
584                setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
585            }
586
587            ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
588                    ringerModeInternal, ringerModeInternalOut);
589            return ringerModeInternalOut;
590        }
591
592        @Override
593        public boolean canVolumeDownEnterSilent() {
594            return mZenMode == Global.ZEN_MODE_OFF;
595        }
596
597        @Override
598        public int getRingerModeAffectedStreams(int streams) {
599            // ringtone, notification and system streams are always affected by ringer mode
600            streams |= (1 << AudioSystem.STREAM_RING) |
601                       (1 << AudioSystem.STREAM_NOTIFICATION) |
602                       (1 << AudioSystem.STREAM_SYSTEM);
603
604            // alarm and music streams are only affected by ringer mode when in total silence
605            if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
606                streams |= (1 << AudioSystem.STREAM_ALARM) |
607                           (1 << AudioSystem.STREAM_MUSIC);
608            } else {
609                streams &= ~((1 << AudioSystem.STREAM_ALARM) |
610                             (1 << AudioSystem.STREAM_MUSIC));
611            }
612            return streams;
613        }
614    }
615
616    private final class SettingsObserver extends ContentObserver {
617        private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
618
619        public SettingsObserver(Handler handler) {
620            super(handler);
621        }
622
623        public void observe() {
624            final ContentResolver resolver = mContext.getContentResolver();
625            resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
626            update(null);
627        }
628
629        @Override
630        public void onChange(boolean selfChange, Uri uri) {
631            update(uri);
632        }
633
634        public void update(Uri uri) {
635            if (ZEN_MODE.equals(uri)) {
636                if (mZenMode != getZenModeSetting()) {
637                    if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
638                    setZenModeSetting(mZenMode);
639                }
640            }
641        }
642    }
643
644    private final class H extends Handler {
645        private static final int MSG_DISPATCH = 1;
646
647        private H(Looper looper) {
648            super(looper);
649        }
650
651        private void postDispatchOnZenModeChanged() {
652            removeMessages(MSG_DISPATCH);
653            sendEmptyMessage(MSG_DISPATCH);
654        }
655
656        @Override
657        public void handleMessage(Message msg) {
658            switch (msg.what) {
659                case MSG_DISPATCH:
660                    dispatchOnZenModeChanged();
661                    break;
662            }
663        }
664    }
665
666    public static class Callback {
667        void onConfigChanged() {}
668        void onZenModeChanged() {}
669        void onPolicyChanged() {}
670    }
671
672}
673