From 056c519df1dfb8fdc57daddfdf09bc0e1ffddac4 Mon Sep 17 00:00:00 2001 From: John Spurlock Date: Sun, 20 Apr 2014 21:52:01 -0400 Subject: Do not disturb: persist user config. Load and store user configuration for do not disturb. Separate out service-related aspects into new helper. Make config availble over NoMan for settings. Implement phone + message based filtering (package whitelist for now). Implement automatic enter/exit zen mode overnight scheduler. Bug:14211946 Change-Id: Ib28aab0e4c5c9a5fd0b950b2884b1ab618fdfeca --- .../notification/NotificationManagerService.java | 227 +++++++-------- .../android/server/notification/ZenModeHelper.java | 312 +++++++++++++++++++++ 2 files changed, 419 insertions(+), 120 deletions(-) create mode 100644 services/core/java/com/android/server/notification/ZenModeHelper.java (limited to 'services') diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5b597a3..c8bdb4c 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -52,6 +52,7 @@ import android.media.IRingtonePlayer; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -64,6 +65,7 @@ import android.provider.Settings; import android.service.notification.INotificationListener; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenModeConfig; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -78,6 +80,7 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.notification.NotificationScorer; +import com.android.internal.util.FastXmlSerializer; import com.android.server.EventLogTags; import com.android.server.notification.NotificationUsageStats.SingleNotificationStats; import com.android.server.statusbar.StatusBarManagerInternal; @@ -87,11 +90,13 @@ import com.android.server.lights.LightsManager; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Array; @@ -115,6 +120,7 @@ public class NotificationManagerService extends SystemService { // message codes static final int MESSAGE_TIMEOUT = 2; + static final int MESSAGE_SAVE_POLICY_FILE = 3; static final int LONG_DELAY = 3500; // 3.5 seconds static final int SHORT_DELAY = 2000; // 2 seconds @@ -209,15 +215,6 @@ public class NotificationManagerService extends SystemService { private final NotificationUsageStats mUsageStats = new NotificationUsageStats(); - private int mZenMode; - // temporary, until we update apps to provide metadata - private static final Set CALL_PACKAGES = new HashSet(Arrays.asList( - "com.google.android.dialer", - "com.android.phone" - )); - private static final Set ALARM_PACKAGES = new HashSet(Arrays.asList( - "com.google.android.deskclock" - )); private static final String EXTRA_INTERCEPT = "android.intercept"; // Profiles of the current user. @@ -421,53 +418,82 @@ public class NotificationManagerService extends SystemService { Archive mArchive = new Archive(); - private void loadBlockDb() { - synchronized(mBlockedPackages) { - if (mPolicyFile == null) { - File dir = new File("/data/system"); - mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml")); + private void loadPolicyFile() { + synchronized(mPolicyFile) { + mBlockedPackages.clear(); - mBlockedPackages.clear(); - - FileInputStream infile = null; - try { - infile = mPolicyFile.openRead(); - final XmlPullParser parser = Xml.newPullParser(); - parser.setInput(infile, null); - - int type; - String tag; - int version = DB_VERSION; - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (type == START_TAG) { - if (TAG_BODY.equals(tag)) { - version = Integer.parseInt( - parser.getAttributeValue(null, ATTR_VERSION)); - } else if (TAG_BLOCKED_PKGS.equals(tag)) { - while ((type = parser.next()) != END_DOCUMENT) { - tag = parser.getName(); - if (TAG_PACKAGE.equals(tag)) { - mBlockedPackages.add( - parser.getAttributeValue(null, ATTR_NAME)); - } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { - break; - } + FileInputStream infile = null; + try { + infile = mPolicyFile.openRead(); + final XmlPullParser parser = Xml.newPullParser(); + parser.setInput(infile, null); + + int type; + String tag; + int version = DB_VERSION; + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (type == START_TAG) { + if (TAG_BODY.equals(tag)) { + version = Integer.parseInt( + parser.getAttributeValue(null, ATTR_VERSION)); + } else if (TAG_BLOCKED_PKGS.equals(tag)) { + while ((type = parser.next()) != END_DOCUMENT) { + tag = parser.getName(); + if (TAG_PACKAGE.equals(tag)) { + mBlockedPackages.add( + parser.getAttributeValue(null, ATTR_NAME)); + } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) { + break; } } } } - } catch (FileNotFoundException e) { - // No data yet - } catch (IOException e) { - Log.wtf(TAG, "Unable to read blocked notifications database", e); - } catch (NumberFormatException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } catch (XmlPullParserException e) { - Log.wtf(TAG, "Unable to parse blocked notifications database", e); - } finally { - IoUtils.closeQuietly(infile); + mZenModeHelper.readXml(parser); } + } catch (FileNotFoundException e) { + // No data yet + } catch (IOException e) { + Log.wtf(TAG, "Unable to read notification policy", e); + } catch (NumberFormatException e) { + Log.wtf(TAG, "Unable to parse notification policy", e); + } catch (XmlPullParserException e) { + Log.wtf(TAG, "Unable to parse notification policy", e); + } finally { + IoUtils.closeQuietly(infile); + } + } + } + + public void savePolicyFile() { + mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE); + mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE); + } + + private void handleSavePolicyFile() { + Slog.d(TAG, "handleSavePolicyFile"); + synchronized (mPolicyFile) { + final FileOutputStream stream; + try { + stream = mPolicyFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file", e); + return; + } + + try { + final XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + out.startTag(null, TAG_BODY); + out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION)); + mZenModeHelper.writeXml(out); + out.endTag(null, TAG_BODY); + out.endDocument(); + mPolicyFile.finishWrite(stream); + } catch (IOException e) { + Slog.w(TAG, "Failed to save policy file, restoring backup", e); + mPolicyFile.failWrite(stream); } } } @@ -1066,10 +1092,7 @@ public class NotificationManagerService extends SystemService { @Override public boolean allowDisable(int what, IBinder token, String pkg) { - if (isCall(pkg, null)) { - return mZenMode == Settings.Global.ZEN_MODE_OFF; - } - return true; + return mZenModeHelper.allowDisable(what, token, pkg); } @Override @@ -1194,9 +1217,6 @@ public class NotificationManagerService extends SystemService { private final Uri ENABLED_NOTIFICATION_LISTENERS_URI = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - private final Uri ZEN_MODE - = Settings.Global.getUriFor(Settings.Global.ZEN_MODE); - SettingsObserver(Handler handler) { super(handler); } @@ -1207,8 +1227,6 @@ public class NotificationManagerService extends SystemService { false, this, UserHandle.USER_ALL); resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI, false, this, UserHandle.USER_ALL); - resolver.registerContentObserver(ZEN_MODE, - false, this); update(null); } @@ -1229,13 +1247,11 @@ public class NotificationManagerService extends SystemService { if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) { rebindListenerServices(); } - if (ZEN_MODE.equals(uri)) { - updateZenMode(); - } } } private SettingsObserver mSettingsObserver; + private ZenModeHelper mZenModeHelper; static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) { int[] ar = r.getIntArray(resid); @@ -1261,6 +1277,15 @@ public class NotificationManagerService extends SystemService { mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mHandler = new WorkerHandler(); + mZenModeHelper = new ZenModeHelper(getContext(), mHandler); + mZenModeHelper.setCallback(new ZenModeHelper.Callback() { + @Override + public void onConfigChanged() { + savePolicyFile(); + } + }); + final File systemDir = new File(Environment.getDataDirectory(), "system"); + mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml")); importOldBlockDb(); @@ -1297,7 +1322,7 @@ public class NotificationManagerService extends SystemService { Settings.Global.DEVICE_PROVISIONED, 0)) { mDisableNotificationAlerts = true; } - updateZenMode(); + mZenModeHelper.updateZenMode(); updateCurrentProfilesCache(getContext()); @@ -1350,7 +1375,7 @@ public class NotificationManagerService extends SystemService { * Read the old XML-based app block database and import those blockages into the AppOps system. */ private void importOldBlockDb() { - loadBlockDb(); + loadPolicyFile(); PackageManager pm = getContext().getPackageManager(); for (String pkg : mBlockedPackages) { @@ -1363,9 +1388,6 @@ public class NotificationManagerService extends SystemService { } } mBlockedPackages.clear(); - if (mPolicyFile != null) { - mPolicyFile.delete(); - } } @Override @@ -1745,6 +1767,18 @@ public class NotificationManagerService extends SystemService { } @Override + public ZenModeConfig getZenModeConfig() { + checkCallerIsSystem(); + return mZenModeHelper.getConfig(); + } + + @Override + public boolean setZenModeConfig(ZenModeConfig config) { + checkCallerIsSystem(); + return mZenModeHelper.setConfig(config); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -1825,7 +1859,6 @@ public class NotificationManagerService extends SystemService { pw.println(" mSoundNotification=" + mSoundNotification); pw.println(" mVibrateNotification=" + mVibrateNotification); pw.println(" mDisableNotificationAlerts=" + mDisableNotificationAlerts); - pw.println(" mZenMode=" + Settings.Global.zenModeToString(mZenMode)); pw.println(" mSystemReady=" + mSystemReady); pw.println(" mArchive=" + mArchive.toString()); Iterator iter = mArchive.descendingIterator(); @@ -1841,6 +1874,8 @@ public class NotificationManagerService extends SystemService { pw.println("\n Usage Stats:"); mUsageStats.dump(pw, " "); + pw.println("\n Zen Mode:"); + mZenModeHelper.dump(pw, " "); } } @@ -1973,7 +2008,7 @@ public class NotificationManagerService extends SystemService { } // Is this notification intercepted by zen mode? - final boolean intercept = shouldIntercept(pkg, notification); + final boolean intercept = mZenModeHelper.shouldIntercept(pkg, notification); notification.extras.putBoolean(EXTRA_INTERCEPT, intercept); // Should this notification make noise, vibe, or use the LED? @@ -2358,6 +2393,9 @@ public class NotificationManagerService extends SystemService { case MESSAGE_TIMEOUT: handleTimeout((ToastRecord)msg.obj); break; + case MESSAGE_SAVE_POLICY_FILE: + handleSavePolicyFile(); + break; } } } @@ -2722,42 +2760,6 @@ public class NotificationManagerService extends SystemService { } } - private void updateZenMode() { - final int mode = Settings.Global.getInt(getContext().getContentResolver(), - Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); - if (mode != mZenMode) { - Slog.d(TAG, String.format("updateZenMode: %s -> %s", - Settings.Global.zenModeToString(mZenMode), - Settings.Global.zenModeToString(mode))); - } - mZenMode = mode; - - final String[] exceptionPackages = null; // none (for now) - - // call restrictions - final boolean muteCalls = mZenMode != Settings.Global.ZEN_MODE_OFF; - mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING, - muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING, - muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - - // alarm restrictions - final boolean muteAlarms = false; // TODO until we save user config - mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_ALARM, - muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_ALARM, - muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - - // restrict vibrations with no hints - mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE, - (muteAlarms || muteCalls) ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, - exceptionPackages); - } - private void updateCurrentProfilesCache(Context context) { UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); if (userManager != null) { @@ -2788,19 +2790,4 @@ public class NotificationManagerService extends SystemService { return mCurrentProfiles.get(userId) != null; } } - - private boolean isCall(String pkg, Notification n) { - return CALL_PACKAGES.contains(pkg); - } - - private boolean isAlarm(String pkg, Notification n) { - return ALARM_PACKAGES.contains(pkg); - } - - private boolean shouldIntercept(String pkg, Notification n) { - if (mZenMode != Settings.Global.ZEN_MODE_OFF) { - return !isAlarm(pkg, n); - } - return false; - } } diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java new file mode 100644 index 0000000..80f5b5c --- /dev/null +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -0,0 +1,312 @@ +/** + * Copyright (c) 2014, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.database.ContentObserver; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.provider.Settings.Global; +import android.service.notification.ZenModeConfig; +import android.util.Slog; + +import com.android.internal.R; + +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +/** + * NotificationManagerService helper for functionality related to zen mode. + */ +public class ZenModeHelper { + private static final String TAG = "ZenModeHelper"; + + private static final String ACTION_ENTER_ZEN = "enter_zen"; + private static final int REQUEST_CODE_ENTER = 100; + private static final String ACTION_EXIT_ZEN = "exit_zen"; + private static final int REQUEST_CODE_EXIT = 101; + private static final String EXTRA_TIME = "time"; + + private final Context mContext; + private final Handler mHandler; + private final SettingsObserver mSettingsObserver; + private final AppOpsManager mAppOps; + private final ZenModeConfig mDefaultConfig; + + private Callback mCallback; + private int mZenMode; + private ZenModeConfig mConfig; + + // temporary, until we update apps to provide metadata + private static final Set CALL_PACKAGES = new HashSet(Arrays.asList( + "com.google.android.dialer", + "com.android.phone" + )); + private static final Set MESSAGE_PACKAGES = new HashSet(Arrays.asList( + "com.google.android.talk", + "com.android.mms" + )); + + public ZenModeHelper(Context context, Handler handler) { + mContext = context; + mHandler = handler; + mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mDefaultConfig = readDefaultConfig(context.getResources()); + mConfig = mDefaultConfig; + mSettingsObserver = new SettingsObserver(mHandler); + mSettingsObserver.observe(); + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_ENTER_ZEN); + filter.addAction(ACTION_EXIT_ZEN); + mContext.registerReceiver(new ZenBroadcastReceiver(), filter); + } + + public static ZenModeConfig readDefaultConfig(Resources resources) { + XmlResourceParser parser = null; + try { + parser = resources.getXml(R.xml.default_zen_mode_config); + while (parser.next() != XmlPullParser.END_DOCUMENT) { + final ZenModeConfig config = ZenModeConfig.readXml(parser); + if (config != null) return config; + } + } catch (Exception e) { + Slog.w(TAG, "Error reading default zen mode config from resource", e); + } finally { + IoUtils.closeQuietly(parser); + } + return new ZenModeConfig(); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + public boolean shouldIntercept(String pkg, Notification n) { + if (mZenMode != Global.ZEN_MODE_OFF) { + if (isCall(pkg, n)) { + return !mConfig.allowCalls; + } + if (isMessage(pkg, n)) { + return !mConfig.allowMessages; + } + return true; + } + return false; + } + + public void updateZenMode() { + final int mode = Global.getInt(mContext.getContentResolver(), + Global.ZEN_MODE, Global.ZEN_MODE_OFF); + if (mode != mZenMode) { + Slog.d(TAG, String.format("updateZenMode: %s -> %s", + Global.zenModeToString(mZenMode), + Global.zenModeToString(mode))); + } + mZenMode = mode; + final boolean zen = mZenMode != Global.ZEN_MODE_OFF; + final String[] exceptionPackages = null; // none (for now) + + // call restrictions + final boolean muteCalls = zen && !mConfig.allowCalls; + mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING, + muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, + exceptionPackages); + mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING, + muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, + exceptionPackages); + + // restrict vibrations with no hints + mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE, + zen ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED, + exceptionPackages); + } + + public boolean allowDisable(int what, IBinder token, String pkg) { + if (isCall(pkg, null)) { + return mZenMode == Global.ZEN_MODE_OFF || mConfig.allowCalls; + } + return true; + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mZenMode="); + pw.println(Global.zenModeToString(mZenMode)); + pw.print(prefix); pw.print("mConfig="); pw.println(mConfig); + pw.print(prefix); pw.print("mDefaultConfig="); pw.println(mDefaultConfig); + } + + public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + final ZenModeConfig config = ZenModeConfig.readXml(parser); + if (config != null) { + setConfig(config); + } + } + + public void writeXml(XmlSerializer out) throws IOException { + mConfig.writeXml(out); + } + + public ZenModeConfig getConfig() { + return mConfig; + } + + public boolean setConfig(ZenModeConfig config) { + if (config == null || !config.isValid()) return false; + if (config.equals(mConfig)) return true; + mConfig = config; + Slog.d(TAG, "mConfig=" + mConfig); + if (mCallback != null) mCallback.onConfigChanged(); + final String val = Integer.toString(mConfig.hashCode()); + Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); + updateAlarms(); + updateZenMode(); + return true; + } + + private boolean isCall(String pkg, Notification n) { + return CALL_PACKAGES.contains(pkg); + } + + private boolean isMessage(String pkg, Notification n) { + return MESSAGE_PACKAGES.contains(pkg); + } + + private void updateAlarms() { + updateAlarm(ACTION_ENTER_ZEN, REQUEST_CODE_ENTER, + mConfig.sleepStartHour, mConfig.sleepStartMinute); + updateAlarm(ACTION_EXIT_ZEN, REQUEST_CODE_EXIT, + mConfig.sleepEndHour, mConfig.sleepEndMinute); + } + + private void updateAlarm(String action, int requestCode, int hr, int min) { + final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + final long now = System.currentTimeMillis(); + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(now); + c.set(Calendar.HOUR_OF_DAY, hr); + c.set(Calendar.MINUTE, min); + c.set(Calendar.SECOND, 0); + c.set(Calendar.MILLISECOND, 0); + if (c.getTimeInMillis() <= now) { + c.add(Calendar.DATE, 1); + } + final long time = c.getTimeInMillis(); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode, + new Intent(action).putExtra(EXTRA_TIME, time), PendingIntent.FLAG_UPDATE_CURRENT); + alarms.cancel(pendingIntent); + if (mConfig.sleepMode != null) { + Slog.d(TAG, String.format("Scheduling %s for %s, %s in the future, now=%s", + action, ts(time), time - now, ts(now))); + alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent); + } + } + + private static String ts(long time) { + return new Date(time) + " (" + time + ")"; + } + + public static boolean isWeekend(long time, int offsetDays) { + final Calendar c = Calendar.getInstance(); + c.setTimeInMillis(time); + if (offsetDays != 0) { + c.add(Calendar.DATE, offsetDays); + } + final int day = c.get(Calendar.DAY_OF_WEEK); + return day == Calendar.SATURDAY || day == Calendar.SUNDAY; + } + + private class SettingsObserver extends ContentObserver { + private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE); + + public SettingsObserver(Handler handler) { + super(handler); + } + + public void observe() { + final ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(ZEN_MODE, false /*notifyForDescendents*/, this); + update(null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + update(uri); + } + + public void update(Uri uri) { + if (ZEN_MODE.equals(uri)) { + updateZenMode(); + } + } + } + + private class ZenBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_ENTER_ZEN.equals(intent.getAction())) { + setZenMode(intent, 1, Global.ZEN_MODE_ON); + } else if (ACTION_EXIT_ZEN.equals(intent.getAction())) { + setZenMode(intent, 0, Global.ZEN_MODE_OFF); + } + } + + private void setZenMode(Intent intent, int wkendOffsetDays, int zenModeValue) { + final long schTime = intent.getLongExtra(EXTRA_TIME, 0); + final long now = System.currentTimeMillis(); + Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s", + intent.getAction(), ts(schTime), ts(now), now - schTime)); + + final boolean skip = ZenModeConfig.SLEEP_MODE_WEEKNIGHTS.equals(mConfig.sleepMode) && + isWeekend(schTime, wkendOffsetDays); + + if (skip) { + Slog.d(TAG, "Skipping zen mode update for the weekend"); + } else { + Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zenModeValue); + } + updateAlarms(); + } + } + + public interface Callback { + void onConfigChanged(); + } +} -- cgit v1.1