ZenModeHelper.java revision 806dbae9e12f3875cf0def3da2f3c3c2b768a844
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.telecomm.TelecommManager;
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 (mZenMode != Global.ZEN_MODE_OFF) {
154            if (isSystem(record)) {
155                return false;
156            }
157            if (isAlarm(record)) {
158                if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
159                    ZenLog.traceIntercepted(record, "alarm");
160                    return true;
161                }
162                return false;
163            }
164            // allow user-prioritized packages through in priority mode
165            if (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
166                if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
167                    ZenLog.traceNotIntercepted(record, "priorityApp");
168                    return false;
169                }
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            ZenLog.traceIntercepted(record, "!allowed");
186            return true;
187        }
188        return false;
189    }
190
191    private boolean shouldInterceptAudience(NotificationRecord record) {
192        if (!audienceMatches(record.getContactAffinity())) {
193            ZenLog.traceIntercepted(record, "!audienceMatches");
194            return true;
195        }
196        return false;
197    }
198
199    public int getZenMode() {
200        return mZenMode;
201    }
202
203    public void setZenMode(int zenModeValue, String reason) {
204        ZenLog.traceSetZenMode(zenModeValue, reason);
205        Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue);
206    }
207
208    public void updateZenMode() {
209        final int mode = Global.getInt(mContext.getContentResolver(),
210                Global.ZEN_MODE, Global.ZEN_MODE_OFF);
211        if (mode != mZenMode) {
212            ZenLog.traceUpdateZenMode(mZenMode, mode);
213        }
214        mZenMode = mode;
215        final boolean zen = mZenMode != Global.ZEN_MODE_OFF;
216        final String[] exceptionPackages = null; // none (for now)
217
218        // call restrictions
219        final boolean muteCalls = zen && !mConfig.allowCalls;
220        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_NOTIFICATION_RINGTONE,
221                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
222                exceptionPackages);
223        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_NOTIFICATION_RINGTONE,
224                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
225                exceptionPackages);
226
227        // alarm restrictions
228        final boolean muteAlarms = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
229        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, USAGE_ALARM,
230                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
231                exceptionPackages);
232        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, USAGE_ALARM,
233                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
234                exceptionPackages);
235
236        // force ringer mode into compliance
237        if (mAudioManager != null) {
238            int ringerMode = mAudioManager.getRingerMode();
239            int forcedRingerMode = -1;
240            if (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
241                if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
242                    mPreviousRingerMode = ringerMode;
243                    if (DEBUG) Slog.d(TAG, "Silencing ringer");
244                    forcedRingerMode = AudioManager.RINGER_MODE_SILENT;
245                }
246            } else {
247                if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
248                    if (DEBUG) Slog.d(TAG, "Unsilencing ringer");
249                    forcedRingerMode = mPreviousRingerMode != -1 ? mPreviousRingerMode
250                            : AudioManager.RINGER_MODE_NORMAL;
251                    mPreviousRingerMode = -1;
252                }
253            }
254            if (forcedRingerMode != -1) {
255                mAudioManager.setRingerMode(forcedRingerMode);
256                ZenLog.traceSetRingerMode(forcedRingerMode);
257            }
258        }
259        dispatchOnZenModeChanged();
260    }
261
262    public void dump(PrintWriter pw, String prefix) {
263        pw.print(prefix); pw.print("mZenMode=");
264        pw.println(Global.zenModeToString(mZenMode));
265        pw.print(prefix); pw.print("mConfig="); pw.println(mConfig);
266        pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig);
267        pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
268        pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
269    }
270
271    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
272        final ZenModeConfig config = ZenModeConfig.readXml(parser);
273        if (config != null) {
274            setConfig(config);
275        }
276    }
277
278    public void writeXml(XmlSerializer out) throws IOException {
279        mConfig.writeXml(out);
280    }
281
282    public ZenModeConfig getConfig() {
283        return mConfig;
284    }
285
286    public boolean setConfig(ZenModeConfig config) {
287        if (config == null || !config.isValid()) return false;
288        if (config.equals(mConfig)) return true;
289        ZenLog.traceConfig(mConfig, config);
290        mConfig = config;
291        dispatchOnConfigChanged();
292        final String val = Integer.toString(mConfig.hashCode());
293        Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
294        updateZenMode();
295        return true;
296    }
297
298    private void handleRingerModeChanged() {
299        if (mAudioManager != null) {
300            // follow ringer mode if necessary
301            final int ringerMode = mAudioManager.getRingerMode();
302            int newZen = -1;
303            if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
304                if (mZenMode == Global.ZEN_MODE_OFF) {
305                    newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
306                }
307            } else if ((ringerMode == AudioManager.RINGER_MODE_NORMAL
308                    || ringerMode == AudioManager.RINGER_MODE_VIBRATE)
309                    && mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) {
310                newZen = Global.ZEN_MODE_OFF;
311            }
312            if (newZen != -1) {
313                ZenLog.traceFollowRingerMode(ringerMode, mZenMode, newZen);
314                setZenMode(newZen, "ringerMode");
315            }
316        }
317    }
318
319    private void dispatchOnConfigChanged() {
320        for (Callback callback : mCallbacks) {
321            callback.onConfigChanged();
322        }
323    }
324
325    private void dispatchOnZenModeChanged() {
326        for (Callback callback : mCallbacks) {
327            callback.onZenModeChanged();
328        }
329    }
330
331    private boolean isSystem(NotificationRecord record) {
332        return record.isCategory(Notification.CATEGORY_SYSTEM);
333    }
334
335    private boolean isAlarm(NotificationRecord record) {
336        return record.isCategory(Notification.CATEGORY_ALARM)
337                || record.isCategory(Notification.CATEGORY_EVENT)
338                || record.isAudioStream(AudioManager.STREAM_ALARM)
339                || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
340    }
341
342    private boolean isCall(NotificationRecord record) {
343        return isDefaultPhoneApp(record.sbn.getPackageName())
344                || record.isCategory(Notification.CATEGORY_CALL);
345    }
346
347    private boolean isDefaultPhoneApp(String pkg) {
348        if (mDefaultPhoneApp == null) {
349            final TelecommManager telecomm =
350                    (TelecommManager) mContext.getSystemService(Context.TELECOMM_SERVICE);
351            mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
352            if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
353        }
354        return pkg != null && mDefaultPhoneApp != null
355                && pkg.equals(mDefaultPhoneApp.getPackageName());
356    }
357
358    private boolean isDefaultMessagingApp(NotificationRecord record) {
359        final int userId = record.getUserId();
360        if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
361        final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
362                Secure.SMS_DEFAULT_APPLICATION, userId);
363        return Objects.equals(defaultApp, record.sbn.getPackageName());
364    }
365
366    private boolean isMessage(NotificationRecord record) {
367        return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
368    }
369
370    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
371            ValidateNotificationPeople validator) {
372        final int zen = mZenMode;
373        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
374        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
375            if (!mConfig.allowCalls) return false; // no calls get through
376            if (validator != null) {
377                final float contactAffinity = validator.getContactAffinity(userHandle, extras);
378                return audienceMatches(contactAffinity);
379            }
380        }
381        return true;
382    }
383
384    private boolean audienceMatches(float contactAffinity) {
385        switch (mConfig.allowFrom) {
386            case ZenModeConfig.SOURCE_ANYONE:
387                return true;
388            case ZenModeConfig.SOURCE_CONTACT:
389                return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
390            case ZenModeConfig.SOURCE_STAR:
391                return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
392            default:
393                Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
394                return true;
395        }
396    }
397
398    private final Runnable mRingerModeChanged = new Runnable() {
399        @Override
400        public void run() {
401            handleRingerModeChanged();
402        }
403    };
404
405    private class SettingsObserver extends ContentObserver {
406        private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
407
408        public SettingsObserver(Handler handler) {
409            super(handler);
410        }
411
412        public void observe() {
413            final ContentResolver resolver = mContext.getContentResolver();
414            resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this);
415            update(null);
416        }
417
418        @Override
419        public void onChange(boolean selfChange, Uri uri) {
420            update(uri);
421        }
422
423        public void update(Uri uri) {
424            if (ZEN_MODE.equals(uri)) {
425                updateZenMode();
426            }
427        }
428    }
429
430    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
431        @Override
432        public void onReceive(Context context, Intent intent) {
433            mHandler.post(mRingerModeChanged);
434        }
435    };
436
437    public static class Callback {
438        void onConfigChanged() {}
439        void onZenModeChanged() {}
440    }
441}
442