An update on Downtime.

The update is that Downtime is obsolete.  Replaced by the
ability to define multiple named schedule calendars.

 - Make changes to ZenModeConfig to properly model manual
   and automatic rules.
 - Refactor the zen mode helper (and supporting classes) to
   properly handle / report multiple claims on zen mode.
   The "manual" rule (specified by the user in the UI) vs
   one or more automatic rules.
 - Automatic rules are still backed by condition providers,
   but the layering is now cleaner.  ConditionProviders is now
   completely generic, has no ties to zen mode.
 - Specifically, the new layering for zen mode (below noman) is:
   ZenModeHelper: Source of truth for zen state
     ZenModeFiltering: Subhelper dedicated to filtering rules.
     ZenModeConditions: Subhelper dedicated to managing automatic rules.
       ConditionProviders:  Underlying engine for reporting named boolean state.
 - Migration story for users with existing downtime config, migrated
   to a single new calendar named downtime.
 - For users with no existing downtime, two default calendars are created
   for weeknights + weekends (icu4j for all locales will be done in a followup).
 - Remove obsolete DowntimeConditionProvider/NextAlarmConditionProvider and tracking.
 - Clean up obsolete resources.
 - Add common zen summary description string computation.
 - Add proper noman wrappers for the new model.
 - Change the semantics of the global zen setting.  It is now read-only.  Setters
   must call noman, added a "reason" to all calls for better attribution.
 - Update zenmodepanel + volumedialog to the new model.
 - Display the one or more automatic rules in the new zen footer summary.
 - "Snooze" the automatic rules when the user explicitly turns zen off.

Bug: 20064962
Change-Id: Idd9deb865a6035ad0cfae660198dccb517e6d7cc
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 33262b3..e2230da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -76,12 +76,10 @@
     boolean matchesCallFilter(in Bundle extras);
     boolean isSystemConditionProviderEnabled(String path);
 
+    int getZenMode();
     ZenModeConfig getZenModeConfig();
-    boolean setZenModeConfig(in ZenModeConfig config);
-    oneway void setZenMode(int mode);
+    boolean setZenModeConfig(in ZenModeConfig config, String reason);
+    oneway void setZenMode(int mode, in Uri conditionId, String reason);
     oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
     oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
-    oneway void setZenModeCondition(in Condition condition);
-    oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
-    Condition[] getAutomaticZenModeConditions();
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 479327d..fa61e18 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@
 import android.app.Notification.Builder;
 import android.content.ComponentName;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -27,7 +28,7 @@
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.UserHandle;
-import android.service.notification.Condition;
+import android.provider.Settings.Global;
 import android.service.notification.IConditionListener;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
@@ -282,10 +283,10 @@
     /**
      * @hide
      */
-    public void setZenMode(int mode) {
+    public void setZenMode(int mode, Uri conditionId, String reason) {
         INotificationManager service = getService();
         try {
-            service.setZenMode(mode);
+            service.setZenMode(mode, conditionId, reason);
         } catch (RemoteException e) {
         }
     }
@@ -293,6 +294,18 @@
     /**
      * @hide
      */
+    public boolean setZenModeConfig(ZenModeConfig config, String reason) {
+        INotificationManager service = getService();
+        try {
+            return service.setZenModeConfig(config, reason);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     */
     public void requestZenModeConditions(IConditionListener listener, int relevance) {
         INotificationManager service = getService();
         try {
@@ -304,24 +317,22 @@
     /**
      * @hide
      */
-    public void setZenModeCondition(Condition exitCondition) {
+    public int getZenMode() {
         INotificationManager service = getService();
         try {
-            service.setZenModeCondition(exitCondition);
+            return service.getZenMode();
         } catch (RemoteException e) {
         }
+        return Global.ZEN_MODE_OFF;
     }
 
     /**
      * @hide
      */
-    public Condition getZenModeCondition() {
+    public ZenModeConfig getZenModeConfig() {
         INotificationManager service = getService();
         try {
-            final ZenModeConfig config = service.getZenModeConfig();
-            if (config != null) {
-                return config.exitCondition;
-            }
+            return service.getZenModeConfig();
         } catch (RemoteException e) {
         }
         return null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ee8fb3..3087e1d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -880,6 +880,15 @@
             "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
 
     /**
+     * Activity Action: Show Zen Mode schedule rule configuration settings.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS
+            = "android.settings.ZEN_MODE_SCHEDULE_RULE_SETTINGS";
+
+    /**
      * Activity Action: Show the regulatory information screen for the device.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -7218,6 +7227,18 @@
             return "ZEN_MODE_OFF";
         }
 
+        /** @hide */ public static boolean isValidZenMode(int value) {
+            switch (value) {
+                case Global.ZEN_MODE_OFF:
+                case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                case Global.ZEN_MODE_ALARMS:
+                case Global.ZEN_MODE_NO_INTERRUPTIONS:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
         /**
          * Opaque value, changes when persisted zen mode configuration changes.
          *
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 2702457..5aaf2e7 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -22,22 +22,25 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.provider.Settings.Global;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.Objects;
-
-import com.android.internal.R;
+import java.util.UUID;
 
 /**
  * Persisted configuration for zen mode.
@@ -47,10 +50,6 @@
 public class ZenModeConfig implements Parcelable {
     private static String TAG = "ZenModeConfig";
 
-    public static final String SLEEP_MODE_NIGHTS = "nights";
-    public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
-    public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
-
     public static final int SOURCE_ANYONE = 0;
     public static final int SOURCE_CONTACT = 1;
     public static final int SOURCE_STAR = 2;
@@ -60,6 +59,7 @@
             Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
     public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
             Calendar.WEDNESDAY, Calendar.THURSDAY };
+    public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY };
 
     public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
     private static final int SECONDS_MS = 1000;
@@ -69,7 +69,7 @@
     private static final boolean DEFAULT_ALLOW_REMINDERS = true;
     private static final boolean DEFAULT_ALLOW_EVENTS = true;
 
-    private static final int XML_VERSION = 1;
+    private static final int XML_VERSION = 2;
     private static final String ZEN_TAG = "zen";
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ALLOW_TAG = "allow";
@@ -78,14 +78,6 @@
     private static final String ALLOW_ATT_FROM = "from";
     private static final String ALLOW_ATT_REMINDERS = "reminders";
     private static final String ALLOW_ATT_EVENTS = "events";
-    private static final String SLEEP_TAG = "sleep";
-    private static final String SLEEP_ATT_MODE = "mode";
-    private static final String SLEEP_ATT_NONE = "none";
-
-    private static final String SLEEP_ATT_START_HR = "startHour";
-    private static final String SLEEP_ATT_START_MIN = "startMin";
-    private static final String SLEEP_ATT_END_HR = "endHour";
-    private static final String SLEEP_ATT_END_MIN = "endMin";
 
     private static final String CONDITION_TAG = "condition";
     private static final String CONDITION_ATT_COMPONENT = "component";
@@ -97,8 +89,16 @@
     private static final String CONDITION_ATT_STATE = "state";
     private static final String CONDITION_ATT_FLAGS = "flags";
 
-    private static final String EXIT_CONDITION_TAG = "exitCondition";
-    private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+    private static final String MANUAL_TAG = "manual";
+    private static final String AUTOMATIC_TAG = "automatic";
+
+    private static final String RULE_ATT_ID = "id";
+    private static final String RULE_ATT_ENABLED = "enabled";
+    private static final String RULE_ATT_SNOOZING = "snoozing";
+    private static final String RULE_ATT_NAME = "name";
+    private static final String RULE_ATT_COMPONENT = "component";
+    private static final String RULE_ATT_ZEN = "zen";
+    private static final String RULE_ATT_CONDITION_ID = "conditionId";
 
     public boolean allowCalls;
     public boolean allowMessages;
@@ -106,16 +106,8 @@
     public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
     public int allowFrom = SOURCE_ANYONE;
 
-    public String sleepMode;
-    public int sleepStartHour;   // 0-23
-    public int sleepStartMinute; // 0-59
-    public int sleepEndHour;
-    public int sleepEndMinute;
-    public boolean sleepNone;    // false = priority, true = none
-    public ComponentName[] conditionComponents;
-    public Uri[] conditionIds;
-    public Condition exitCondition;
-    public ComponentName exitConditionComponent;
+    public ZenRule manualRule;
+    public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
 
     public ZenModeConfig() { }
 
@@ -124,27 +116,18 @@
         allowMessages = source.readInt() == 1;
         allowReminders = source.readInt() == 1;
         allowEvents = source.readInt() == 1;
-        if (source.readInt() == 1) {
-            sleepMode = source.readString();
-        }
-        sleepStartHour = source.readInt();
-        sleepStartMinute = source.readInt();
-        sleepEndHour = source.readInt();
-        sleepEndMinute = source.readInt();
-        sleepNone = source.readInt() == 1;
-        int len = source.readInt();
-        if (len > 0) {
-            conditionComponents = new ComponentName[len];
-            source.readTypedArray(conditionComponents, ComponentName.CREATOR);
-        }
-        len = source.readInt();
-        if (len > 0) {
-            conditionIds = new Uri[len];
-            source.readTypedArray(conditionIds, Uri.CREATOR);
-        }
         allowFrom = source.readInt();
-        exitCondition = source.readParcelable(null);
-        exitConditionComponent = source.readParcelable(null);
+        manualRule = source.readParcelable(null);
+        final int len = source.readInt();
+        if (len > 0) {
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            source.readStringArray(ids);
+            source.readTypedArray(rules, ZenRule.CREATOR);
+            for (int i = 0; i < len; i++) {
+                automaticRules.put(ids[i], rules[i]);
+            }
+        }
     }
 
     @Override
@@ -153,32 +136,22 @@
         dest.writeInt(allowMessages ? 1 : 0);
         dest.writeInt(allowReminders ? 1 : 0);
         dest.writeInt(allowEvents ? 1 : 0);
-        if (sleepMode != null) {
-            dest.writeInt(1);
-            dest.writeString(sleepMode);
-        } else {
-            dest.writeInt(0);
-        }
-        dest.writeInt(sleepStartHour);
-        dest.writeInt(sleepStartMinute);
-        dest.writeInt(sleepEndHour);
-        dest.writeInt(sleepEndMinute);
-        dest.writeInt(sleepNone ? 1 : 0);
-        if (conditionComponents != null && conditionComponents.length > 0) {
-            dest.writeInt(conditionComponents.length);
-            dest.writeTypedArray(conditionComponents, 0);
-        } else {
-            dest.writeInt(0);
-        }
-        if (conditionIds != null && conditionIds.length > 0) {
-            dest.writeInt(conditionIds.length);
-            dest.writeTypedArray(conditionIds, 0);
-        } else {
-            dest.writeInt(0);
-        }
         dest.writeInt(allowFrom);
-        dest.writeParcelable(exitCondition, 0);
-        dest.writeParcelable(exitConditionComponent, 0);
+        dest.writeParcelable(manualRule, 0);
+        if (!automaticRules.isEmpty()) {
+            final int len = automaticRules.size();
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            for (int i = 0; i < len; i++) {
+                ids[i] = automaticRules.keyAt(i);
+                rules[i] = automaticRules.valueAt(i);
+            }
+            dest.writeInt(len);
+            dest.writeStringArray(ids);
+            dest.writeTypedArray(rules, 0);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     @Override
@@ -189,19 +162,38 @@
             .append(",allowFrom=").append(sourceToString(allowFrom))
             .append(",allowReminders=").append(allowReminders)
             .append(",allowEvents=").append(allowEvents)
-            .append(",sleepMode=").append(sleepMode)
-            .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
-            .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
-            .append(",sleepNone=").append(sleepNone)
-            .append(",conditionComponents=")
-            .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
-            .append(",conditionIds=")
-            .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
-            .append(",exitCondition=").append(exitCondition)
-            .append(",exitConditionComponent=").append(exitConditionComponent)
+            .append(",automaticRules=").append(automaticRules)
+            .append(",manualRule=").append(manualRule)
             .append(']').toString();
     }
 
+    public boolean isValid() {
+        if (!isValidManualRule(manualRule)) return false;
+        final int N = automaticRules.size();
+        for (int i = 0; i < N; i++) {
+            if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
+        }
+        return true;
+    }
+
+    private static boolean isValidManualRule(ZenRule rule) {
+        return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
+    }
+
+    private static boolean isValidAutomaticRule(ZenRule rule) {
+        return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
+                && rule.conditionId != null && sameCondition(rule);
+    }
+
+    private static boolean sameCondition(ZenRule rule) {
+        if (rule == null) return false;
+        if (rule.conditionId == null) {
+            return rule.condition == null;
+        } else {
+            return rule.condition == null || rule.conditionId.equals(rule.condition.id);
+        }
+    }
+
     public static String sourceToString(int source) {
         switch (source) {
             case SOURCE_ANYONE:
@@ -225,45 +217,29 @@
                 && other.allowFrom == allowFrom
                 && other.allowReminders == allowReminders
                 && other.allowEvents == allowEvents
-                && Objects.equals(other.sleepMode, sleepMode)
-                && other.sleepNone == sleepNone
-                && other.sleepStartHour == sleepStartHour
-                && other.sleepStartMinute == sleepStartMinute
-                && other.sleepEndHour == sleepEndHour
-                && other.sleepEndMinute == sleepEndMinute
-                && Objects.deepEquals(other.conditionComponents, conditionComponents)
-                && Objects.deepEquals(other.conditionIds, conditionIds)
-                && Objects.equals(other.exitCondition, exitCondition)
-                && Objects.equals(other.exitConditionComponent, exitConditionComponent);
+                && Objects.equals(other.automaticRules, automaticRules)
+                && Objects.equals(other.manualRule, manualRule);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents,
-                sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour,
-                sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
-                exitCondition, exitConditionComponent);
+                automaticRules, manualRule);
     }
 
-    public boolean isValid() {
-        return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
-                && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
-                && isValidSleepMode(sleepMode);
+    private static String toDayList(int[] days) {
+        if (days == null || days.length == 0) return "";
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < days.length; i++) {
+            if (i > 0) sb.append('.');
+            sb.append(days[i]);
+        }
+        return sb.toString();
     }
 
-    public static boolean isValidSleepMode(String sleepMode) {
-        return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
-                || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
-    }
-
-    public static int[] tryParseDays(String sleepMode) {
-        if (sleepMode == null) return null;
-        sleepMode = sleepMode.trim();
-        if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
-        if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
-        if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
-        if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
-        final String[] tokens = sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()).split(",");
+    private static int[] tryParseDayList(String dayList, String sep) {
+        if (dayList == null) return null;
+        final String[] tokens = dayList.split(sep);
         if (tokens.length == 0) return null;
         final int[] rt = new int[tokens.length];
         for (int i = 0; i < tokens.length; i++) {
@@ -283,7 +259,7 @@
         }
     }
 
-    public static ZenModeConfig readXml(XmlPullParser parser)
+    public static ZenModeConfig readXml(XmlPullParser parser, Migration migration)
             throws XmlPullParserException, IOException {
         int type = parser.getEventType();
         if (type != XmlPullParser.START_TAG) return null;
@@ -291,16 +267,13 @@
         if (!ZEN_TAG.equals(tag)) return null;
         final ZenModeConfig rt = new ZenModeConfig();
         final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
-        final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
-        final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+        if (version == 1) {
+            final XmlV1 v1 = XmlV1.readXml(parser);
+            return migration.migrate(v1);
+        }
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
             if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
-                if (!conditionComponents.isEmpty()) {
-                    rt.conditionComponents = conditionComponents
-                            .toArray(new ComponentName[conditionComponents.size()]);
-                    rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
-                }
                 return rt;
             }
             if (type == XmlPullParser.START_TAG) {
@@ -314,31 +287,13 @@
                     if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
                         throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
                     }
-                } else if (SLEEP_TAG.equals(tag)) {
-                    final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
-                    rt.sleepMode = isValidSleepMode(mode)? mode : null;
-                    rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
-                    final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
-                    final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
-                    final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
-                    final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
-                    rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
-                    rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
-                    rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
-                    rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
-                } else if (CONDITION_TAG.equals(tag)) {
-                    final ComponentName component =
-                            safeComponentName(parser, CONDITION_ATT_COMPONENT);
-                    final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
-                    if (component != null && conditionId != null) {
-                        conditionComponents.add(component);
-                        conditionIds.add(conditionId);
-                    }
-                } else if (EXIT_CONDITION_TAG.equals(tag)) {
-                    rt.exitCondition = readConditionXml(parser);
-                    if (rt.exitCondition != null) {
-                        rt.exitConditionComponent =
-                                safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+                } else if (MANUAL_TAG.equals(tag)) {
+                    rt.manualRule = readRuleXml(parser);
+                } else if (AUTOMATIC_TAG.equals(tag)) {
+                    final String id = parser.getAttributeValue(null, RULE_ATT_ID);
+                    final ZenRule automaticRule = readRuleXml(parser);
+                    if (id != null && automaticRule != null) {
+                        rt.automaticRules.put(id, automaticRule);
                     }
                 }
             }
@@ -358,39 +313,61 @@
         out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
         out.endTag(null, ALLOW_TAG);
 
-        out.startTag(null, SLEEP_TAG);
-        if (sleepMode != null) {
-            out.attribute(null, SLEEP_ATT_MODE, sleepMode);
+        if (manualRule != null) {
+            out.startTag(null, MANUAL_TAG);
+            writeRuleXml(manualRule, out);
+            out.endTag(null, MANUAL_TAG);
         }
-        out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone));
-        out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
-        out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
-        out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
-        out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
-        out.endTag(null, SLEEP_TAG);
-
-        if (conditionComponents != null && conditionIds != null
-                && conditionComponents.length == conditionIds.length) {
-            for (int i = 0; i < conditionComponents.length; i++) {
-                out.startTag(null, CONDITION_TAG);
-                out.attribute(null, CONDITION_ATT_COMPONENT,
-                        conditionComponents[i].flattenToString());
-                out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
-                out.endTag(null, CONDITION_TAG);
-            }
-        }
-        if (exitCondition != null && exitConditionComponent != null) {
-            out.startTag(null, EXIT_CONDITION_TAG);
-            out.attribute(null, EXIT_CONDITION_ATT_COMPONENT,
-                    exitConditionComponent.flattenToString());
-            writeConditionXml(exitCondition, out);
-            out.endTag(null, EXIT_CONDITION_TAG);
+        final int N = automaticRules.size();
+        for (int i = 0; i < N; i++) {
+            final String id = automaticRules.keyAt(i);
+            final ZenRule automaticRule = automaticRules.valueAt(i);
+            out.startTag(null, AUTOMATIC_TAG);
+            out.attribute(null, RULE_ATT_ID, id);
+            writeRuleXml(automaticRule, out);
+            out.endTag(null, AUTOMATIC_TAG);
         }
         out.endTag(null, ZEN_TAG);
     }
 
+    public static ZenRule readRuleXml(XmlPullParser parser) {
+        final ZenRule rt = new ZenRule();
+        rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
+        rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
+        rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
+        final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
+        rt.zenMode = tryParseZenMode(zen, -1);
+        if (rt.zenMode == -1) {
+            Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
+            return null;
+        }
+        rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
+        rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+        rt.condition = readConditionXml(parser);
+        return rt.condition != null ? rt : null;
+    }
+
+    public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
+        out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
+        out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
+        if (rule.name != null) {
+            out.attribute(null, RULE_ATT_NAME, rule.name);
+        }
+        out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
+        if (rule.component != null) {
+            out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
+        }
+        if (rule.conditionId != null) {
+            out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
+        }
+        if (rule.condition != null) {
+            writeConditionXml(rule.condition, out);
+        }
+    }
+
     public static Condition readConditionXml(XmlPullParser parser) {
         final Uri id = safeUri(parser, CONDITION_ATT_ID);
+        if (id == null) return null;
         final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
         final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
         final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
@@ -446,6 +423,14 @@
         return Uri.parse(val);
     }
 
+    public ArraySet<String> getAutomaticRuleNames() {
+        final ArraySet<String> rt = new ArraySet<String>();
+        for (int i = 0; i < automaticRules.size(); i++) {
+            rt.add(automaticRules.valueAt(i).name);
+        }
+        return rt;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -475,17 +460,6 @@
         }
     };
 
-    public DowntimeInfo toDowntimeInfo() {
-        final DowntimeInfo downtime = new DowntimeInfo();
-        downtime.startHour = sleepStartHour;
-        downtime.startMinute = sleepStartMinute;
-        downtime.endHour = sleepEndHour;
-        downtime.endMinute = sleepEndMinute;
-        downtime.mode = sleepMode;
-        downtime.none = sleepNone;
-        return downtime;
-    }
-
     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
         final long now = System.currentTimeMillis();
         final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
@@ -548,38 +522,77 @@
         return tryParseCountdownConditionId(conditionId) != 0;
     }
 
-    // Built-in downtime conditions
-    // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
-    public static final String DOWNTIME_PATH = "downtime";
+    // built-in schedule conditions
+    public static final String SCHEDULE_PATH = "schedule";
 
-    public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
+    public static class ScheduleInfo {
+        public int[] days;
+        public int startHour;
+        public int startMinute;
+        public int endHour;
+        public int endMinute;
+
+        @Override
+        public int hashCode() {
+            return 0;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof ScheduleInfo)) return false;
+            final ScheduleInfo other = (ScheduleInfo) o;
+            return toDayList(days).equals(toDayList(other.days))
+                    && startHour == other.startHour
+                    && startMinute == other.startMinute
+                    && endHour == other.endHour
+                    && endMinute == other.endMinute;
+        }
+
+        public ScheduleInfo copy() {
+            final ScheduleInfo rt = new ScheduleInfo();
+            if (days != null) {
+                rt.days = new int[days.length];
+                System.arraycopy(days, 0, rt.days, 0, days.length);
+            }
+            rt.startHour = startHour;
+            rt.startMinute = startMinute;
+            rt.endHour = endHour;
+            rt.endMinute = endMinute;
+            return rt;
+        }
+    }
+
+    public static Uri toScheduleConditionId(ScheduleInfo schedule) {
         return new Uri.Builder().scheme(Condition.SCHEME)
                 .authority(SYSTEM_AUTHORITY)
-                .appendPath(DOWNTIME_PATH)
-                .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
-                .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
-                .appendQueryParameter("mode", downtime.mode)
-                .appendQueryParameter("none", Boolean.toString(downtime.none))
+                .appendPath(SCHEDULE_PATH)
+                .appendQueryParameter("days", toDayList(schedule.days))
+                .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
+                .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
                 .build();
     }
 
-    public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) {
-        if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)
-                || conditionId.getPathSegments().size() != 1
-                || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) {
-            return null;
-        }
+    public static boolean isValidScheduleConditionId(Uri conditionId) {
+        return tryParseScheduleConditionId(conditionId) != null;
+    }
+
+    public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
+        final boolean isSchedule =  conditionId != null
+                && conditionId.getScheme().equals(Condition.SCHEME)
+                && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
+                && conditionId.getPathSegments().size() == 1
+                && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH);
+        if (!isSchedule) return null;
         final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
         final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
         if (start == null || end == null) return null;
-        final DowntimeInfo downtime = new DowntimeInfo();
-        downtime.startHour = start[0];
-        downtime.startMinute = start[1];
-        downtime.endHour = end[0];
-        downtime.endMinute = end[1];
-        downtime.mode = conditionId.getQueryParameter("mode");
-        downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
-        return downtime;
+        final ScheduleInfo rt = new ScheduleInfo();
+        rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
+        rt.startHour = start[0];
+        rt.startMinute = start[1];
+        rt.endHour = end[0];
+        rt.endMinute = end[1];
+        return rt;
     }
 
     private static int[] tryParseHourAndMinute(String value) {
@@ -591,36 +604,268 @@
         return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
     }
 
-    public static boolean isValidDowntimeConditionId(Uri conditionId) {
-        return tryParseDowntimeConditionId(conditionId) != null;
+    private static int tryParseZenMode(String value, int defValue) {
+        final int rt = tryParseInt(value, defValue);
+        return Global.isValidZenMode(rt) ? rt : defValue;
     }
 
-    public static class DowntimeInfo {
-        public int startHour;   // 0-23
-        public int startMinute; // 0-59
-        public int endHour;
-        public int endMinute;
-        public String mode;
-        public boolean none;
+    public String newRuleId() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    public static String getConditionLine1(Context context, ZenModeConfig config,
+            int userHandle) {
+        return getConditionLine(context, config, userHandle, true /*useLine1*/);
+    }
+
+    public static String getConditionSummary(Context context, ZenModeConfig config,
+            int userHandle) {
+        return getConditionLine(context, config, userHandle, false /*useLine1*/);
+    }
+
+    private static String getConditionLine(Context context, ZenModeConfig config,
+            int userHandle, boolean useLine1) {
+        if (config == null) return "";
+        if (config.manualRule != null) {
+            final Uri id = config.manualRule.conditionId;
+            if (id == null) {
+                return context.getString(com.android.internal.R.string.zen_mode_forever);
+            }
+            final long time = tryParseCountdownConditionId(id);
+            Condition c = config.manualRule.condition;
+            if (time > 0) {
+                final long now = System.currentTimeMillis();
+                final long span = time - now;
+                c = toTimeCondition(context,
+                        time, Math.round(span / (float) MINUTES_MS), now, userHandle);
+            }
+            final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
+            return TextUtils.isEmpty(rt) ? "" : rt;
+        }
+        String summary = "";
+        for (ZenRule automaticRule : config.automaticRules.values()) {
+            if (automaticRule.enabled && !automaticRule.snoozing
+                    && automaticRule.isTrueOrUnknown()) {
+                if (summary.isEmpty()) {
+                    summary = automaticRule.name;
+                } else {
+                    summary = context.getResources()
+                            .getString(R.string.zen_mode_rule_name_combination, summary,
+                                    automaticRule.name);
+                }
+            }
+        }
+        return summary;
+    }
+
+    public static class ZenRule implements Parcelable {
+        public boolean enabled;
+        public boolean snoozing;         // user manually disabled this instance
+        public String name;              // required for automatic (unique)
+        public int zenMode;
+        public Uri conditionId;          // required for automatic
+        public Condition condition;      // optional
+        public ComponentName component;  // optional
+
+        public ZenRule() { }
+
+        public ZenRule(Parcel source) {
+            enabled = source.readInt() == 1;
+            snoozing = source.readInt() == 1;
+            if (source.readInt() == 1) {
+                name = source.readString();
+            }
+            zenMode = source.readInt();
+            conditionId = source.readParcelable(null);
+            condition = source.readParcelable(null);
+            component = source.readParcelable(null);
+        }
 
         @Override
-        public int hashCode() {
+        public int describeContents() {
             return 0;
         }
 
         @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(enabled ? 1 : 0);
+            dest.writeInt(snoozing ? 1 : 0);
+            if (name != null) {
+                dest.writeInt(1);
+                dest.writeString(name);
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeInt(zenMode);
+            dest.writeParcelable(conditionId, 0);
+            dest.writeParcelable(condition, 0);
+            dest.writeParcelable(component, 0);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
+                    .append("enabled=").append(enabled)
+                    .append(",snoozing=").append(snoozing)
+                    .append(",name=").append(name)
+                    .append(",zenMode=").append(Global.zenModeToString(zenMode))
+                    .append(",conditionId=").append(conditionId)
+                    .append(",condition=").append(condition)
+                    .append(",component=").append(component)
+                    .append(']').toString();
+        }
+
+        @Override
         public boolean equals(Object o) {
-            if (!(o instanceof DowntimeInfo)) return false;
-            final DowntimeInfo other = (DowntimeInfo) o;
-            return startHour == other.startHour
-                    && startMinute == other.startMinute
-                    && endHour == other.endHour
-                    && endMinute == other.endMinute
-                    && Objects.equals(mode, other.mode)
-                    && none == other.none;
+            if (!(o instanceof ZenRule)) return false;
+            if (o == this) return true;
+            final ZenRule other = (ZenRule) o;
+            return other.enabled == enabled
+                    && other.snoozing == snoozing
+                    && Objects.equals(other.name, name)
+                    && other.zenMode == zenMode
+                    && Objects.equals(other.conditionId, conditionId)
+                    && Objects.equals(other.condition, condition)
+                    && Objects.equals(other.component, component);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
+                    component);
+        }
+
+        public boolean isTrueOrUnknown() {
+            return condition == null || condition.state == Condition.STATE_TRUE
+                    || condition.state == Condition.STATE_UNKNOWN;
+        }
+
+        public static final Parcelable.Creator<ZenRule> CREATOR
+                = new Parcelable.Creator<ZenRule>() {
+            @Override
+            public ZenRule createFromParcel(Parcel source) {
+                return new ZenRule(source);
+            }
+            @Override
+            public ZenRule[] newArray(int size) {
+                return new ZenRule[size];
+            }
+        };
+    }
+
+    // Legacy config
+    public static final class XmlV1 {
+        public static final String SLEEP_MODE_NIGHTS = "nights";
+        public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+        public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
+
+        private static final String EXIT_CONDITION_TAG = "exitCondition";
+        private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+        private static final String SLEEP_TAG = "sleep";
+        private static final String SLEEP_ATT_MODE = "mode";
+        private static final String SLEEP_ATT_NONE = "none";
+
+        private static final String SLEEP_ATT_START_HR = "startHour";
+        private static final String SLEEP_ATT_START_MIN = "startMin";
+        private static final String SLEEP_ATT_END_HR = "endHour";
+        private static final String SLEEP_ATT_END_MIN = "endMin";
+
+        public boolean allowCalls;
+        public boolean allowMessages;
+        public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
+        public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
+        public int allowFrom = SOURCE_ANYONE;
+
+        public String sleepMode;     // nights, weeknights, days:1,2,3  Calendar.days
+        public int sleepStartHour;   // 0-23
+        public int sleepStartMinute; // 0-59
+        public int sleepEndHour;
+        public int sleepEndMinute;
+        public boolean sleepNone;    // false = priority, true = none
+        public ComponentName[] conditionComponents;
+        public Uri[] conditionIds;
+        public Condition exitCondition;  // manual exit condition
+        public ComponentName exitConditionComponent;  // manual exit condition component
+
+        private static boolean isValidSleepMode(String sleepMode) {
+            return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+                    || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
+        }
+
+        public static int[] tryParseDays(String sleepMode) {
+            if (sleepMode == null) return null;
+            sleepMode = sleepMode.trim();
+            if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
+            if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
+            if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
+            if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
+            return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ",");
+        }
+
+        public static XmlV1 readXml(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int type;
+            String tag;
+            XmlV1 rt = new XmlV1();
+            final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+            final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                tag = parser.getName();
+                if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+                    if (!conditionComponents.isEmpty()) {
+                        rt.conditionComponents = conditionComponents
+                                .toArray(new ComponentName[conditionComponents.size()]);
+                        rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+                    }
+                    return rt;
+                }
+                if (type == XmlPullParser.START_TAG) {
+                    if (ALLOW_TAG.equals(tag)) {
+                        rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+                        rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+                        rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
+                                DEFAULT_ALLOW_REMINDERS);
+                        rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS,
+                                DEFAULT_ALLOW_EVENTS);
+                        rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+                        if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+                            throw new IndexOutOfBoundsException("bad source in config:"
+                                    + rt.allowFrom);
+                        }
+                    } else if (SLEEP_TAG.equals(tag)) {
+                        final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+                        rt.sleepMode = isValidSleepMode(mode)? mode : null;
+                        rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
+                        final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+                        final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+                        final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+                        final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+                        rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+                        rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+                        rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+                        rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+                    } else if (CONDITION_TAG.equals(tag)) {
+                        final ComponentName component =
+                                safeComponentName(parser, CONDITION_ATT_COMPONENT);
+                        final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+                        if (component != null && conditionId != null) {
+                            conditionComponents.add(component);
+                            conditionIds.add(conditionId);
+                        }
+                    } else if (EXIT_CONDITION_TAG.equals(tag)) {
+                        rt.exitCondition = readConditionXml(parser);
+                        if (rt.exitCondition != null) {
+                            rt.exitConditionComponent =
+                                    safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+                        }
+                    }
+                }
+            }
+            throw new IllegalStateException("Failed to reach END_DOCUMENT");
         }
     }
 
-    // built-in next alarm conditions
-    public static final String NEXT_ALARM_PATH = "next_alarm";
+    public interface Migration {
+        ZenModeConfig migrate(XmlV1 v1);
+    }
 }
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 0d5db77..f38229a 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -26,7 +26,9 @@
  */
 public class MetricsLogger implements MetricsConstants {
     // These constants are temporary, they should migrate to MetricsConstants.
-    // next value is 144;
+    // next value is 145;
+
+    public static final int NOTIFICATION_ZEN_MODE_SCHEDULE_RULE = 144;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e879244..1b9d133 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2044,16 +2044,9 @@
     <!-- Enabled built-in zen mode condition providers -->
     <string-array translatable="false" name="config_system_condition_providers">
         <item>countdown</item>
-        <item>downtime</item>
-        <item>next_alarm</item>
+        <item>schedule</item>
     </string-array>
 
-    <!-- Show the next-alarm as a zen exit condition if it occurs in the next n hours. -->
-    <integer name="config_next_alarm_condition_lookahead_threshold_hrs">12</integer>
-
-    <!-- Show downtime as a zen exit condition if it starts in the next n hours. -->
-    <integer name="config_downtime_condition_lookahead_threshold_hrs">4</integer>
-
     <!-- Flags enabling default window features. See Window.java -->
     <bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
     <bool name="config_defaultWindowFeatureContextMenu">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a48e964..4285ea1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5170,12 +5170,6 @@
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
     <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
 
-    <!-- [CHAR_LIMIT=NONE] Zen mode: Condition summary for built-in downtime condition, if active -->
-    <string name="downtime_condition_summary">Until your downtime ends at <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
-
-    <!-- [CHAR_LIMIT=NONE] Zen mode: Condition line one for built-in downtime condition, if active -->
-    <string name="downtime_condition_line_one">Until your downtime ends</string>
-
     <!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes_summary">
         <item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
@@ -5206,14 +5200,23 @@
     <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_forever">Until you turn this off</string>
 
+    <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
+
     <!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
     <string name="toolbar_collapse_description">Collapse</string>
 
-    <!-- Zen mode condition - summary: until next alarm. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_next_alarm_summary">Until next alarm at <xliff:g id="formattedTime" example="7:30 AM">%1$s</xliff:g></string>
+    <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
+    <string name="zen_mode_feature_name">Block interruptions</string>
 
-    <!-- Zen mode condition - line one: until next alarm. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_next_alarm_line_one">Until next alarm</string>
+    <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
+    <string name="zen_mode_downtime_feature_name">Downtime</string>
+
+    <!-- Zen mode - name of default automatic schedule for weeknights. [CHAR LIMIT=40] -->
+    <string name="zen_mode_default_weeknights_name">Weeknights</string>
+
+    <!-- Zen mode - name of default automatic schedule for weekends. [CHAR LIMIT=40] -->
+    <string name="zen_mode_default_weekends_name">Weekends</string>
 
     <!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
     <string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c2c00b5..524a8c3 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2037,19 +2037,18 @@
   <java-symbol type="dimen" name="timepicker_text_size_normal" />
   <java-symbol type="dimen" name="timepicker_text_size_inner" />
   <java-symbol type="string" name="battery_saver_description" />
-  <java-symbol type="string" name="downtime_condition_summary" />
-  <java-symbol type="string" name="downtime_condition_line_one" />
   <java-symbol type="string" name="zen_mode_forever" />
+  <java-symbol type="string" name="zen_mode_rule_name_combination" />
   <java-symbol type="plurals" name="zen_mode_duration_minutes" />
   <java-symbol type="plurals" name="zen_mode_duration_hours" />
   <java-symbol type="plurals" name="zen_mode_duration_minutes_summary" />
   <java-symbol type="plurals" name="zen_mode_duration_hours_summary" />
   <java-symbol type="string" name="zen_mode_until" />
-  <java-symbol type="string" name="zen_mode_next_alarm_summary" />
-  <java-symbol type="string" name="zen_mode_next_alarm_line_one" />
+  <java-symbol type="string" name="zen_mode_feature_name" />
+  <java-symbol type="string" name="zen_mode_downtime_feature_name" />
+  <java-symbol type="string" name="zen_mode_default_weeknights_name" />
+  <java-symbol type="string" name="zen_mode_default_weekends_name" />
   <java-symbol type="array" name="config_system_condition_providers" />
-  <java-symbol type="integer" name="config_next_alarm_condition_lookahead_threshold_hrs" />
-  <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
   <java-symbol type="string" name="muted_by" />
 
   <java-symbol type="string" name="select_day" />
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 1bdc1ec..5f4199a 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,7 +18,6 @@
 -->
 
 <!-- Default configuration for zen mode.  See android.service.notification.ZenModeConfig. -->
-<zen version="1">
-    <allow calls="false" messages="false" />
-    <sleep startHour="22" startMin="0" endHour="7" endMin="0" />
+<zen version="2">
+    <allow calls="true" messages="false" reminders="true" events="true" />
 </zen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3705157..779b55e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -981,5 +981,5 @@
     <string name="volumeui_notification_text">Touch to restore the original.</string>
 
     <!-- Volume dialog zen toggle switch title -->
-    <string name="volume_zen_switch_text">Block interruptions</string>
+    <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 64730c2..8aa0d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -85,9 +85,9 @@
     @Override
     public void handleClick() {
         if (mState.value) {
-            mController.setZen(Global.ZEN_MODE_OFF);
+            mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
         } else {
-            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
             showDetail(true);
         }
     }
@@ -199,7 +199,7 @@
         @Override
         public void setToggleState(boolean state) {
             if (!state) {
-                mController.setZen(Global.ZEN_MODE_OFF);
+                mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
                 showDetail(false);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 0e21457..67cc788 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -17,16 +17,19 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.ComponentName;
+import android.net.Uri;
 import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 
 public interface ZenModeController {
     void addCallback(Callback callback);
     void removeCallback(Callback callback);
-    void setZen(int zen);
+    void setZen(int zen, Uri conditionId, String reason);
     int getZen();
     void requestConditions(boolean request);
-    void setExitCondition(Condition exitCondition);
-    Condition getExitCondition();
+    ZenRule getManualRule();
+    ZenModeConfig getConfig();
     long getNextAlarm();
     void setUserId(int userId);
     boolean isZenAvailable();
@@ -35,10 +38,11 @@
 
     public static class Callback {
         public void onZenChanged(int zen) {}
-        public void onExitConditionChanged(Condition exitCondition) {}
         public void onConditionsChanged(Condition[] conditions) {}
         public void onNextAlarmChanged() {}
         public void onZenAvailableChanged(boolean available) {}
         public void onEffectsSupressorChanged() {}
+        public void onManualRuleChanged(ZenRule rule) {}
+        public void onConfigChanged(ZenModeConfig config) {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index bea0c86..830a197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -33,6 +33,7 @@
 import android.service.notification.Condition;
 import android.service.notification.IConditionListener;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 import android.util.Log;
 import android.util.Slog;
 
@@ -40,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.Objects;
 
 /** Platform implementation of the zen mode controller. **/
 public class ZenModeControllerImpl implements ZenModeController {
@@ -58,6 +60,7 @@
     private int mUserId;
     private boolean mRequesting;
     private boolean mRegistered;
+    private ZenModeConfig mConfig;
 
     public ZenModeControllerImpl(Context context, Handler handler) {
         mContext = context;
@@ -70,12 +73,13 @@
         mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
             @Override
             protected void handleValueChanged(int value) {
-                fireExitConditionChanged();
+                updateZenModeConfig();
             }
         };
+        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        mConfig = mNoMan.getZenModeConfig();
         mModeSetting.setListening(true);
         mConfigSetting.setListening(true);
-        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
@@ -97,8 +101,8 @@
     }
 
     @Override
-    public void setZen(int zen) {
-        mModeSetting.setValue(zen);
+    public void setZen(int zen, Uri conditionId, String reason) {
+        mNoMan.setZenMode(zen, conditionId, reason);
     }
 
     @Override
@@ -116,13 +120,13 @@
     }
 
     @Override
-    public void setExitCondition(Condition exitCondition) {
-        mNoMan.setZenModeCondition(exitCondition);
+    public ZenRule getManualRule() {
+        return mConfig == null ? null : mConfig.manualRule;
     }
 
     @Override
-    public Condition getExitCondition() {
-        return mNoMan.getZenModeCondition();
+    public ZenModeConfig getConfig() {
+        return mConfig;
     }
 
     @Override
@@ -185,11 +189,15 @@
         }
     }
 
-    private void fireExitConditionChanged() {
-        final Condition exitCondition = getExitCondition();
-        if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition);
+    private void fireManualRuleChanged(ZenRule rule) {
         for (Callback cb : mCallbacks) {
-            cb.onExitConditionChanged(exitCondition);
+            cb.onManualRuleChanged(rule);
+        }
+    }
+
+    private void fireConfigChanged(ZenModeConfig config) {
+        for (Callback cb : mCallbacks) {
+            cb.onConfigChanged(config);
         }
     }
 
@@ -203,6 +211,17 @@
                 mConditions.values().toArray(new Condition[mConditions.values().size()]));
     }
 
+    private void updateZenModeConfig() {
+        final ZenModeConfig config = mNoMan.getZenModeConfig();
+        if (Objects.equals(config, mConfig)) return;
+        final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null;
+        mConfig = config;
+        fireConfigChanged(config);
+        final ZenRule newRule = config != null ? config.manualRule : null;
+        if (Objects.equals(oldRule, newRule)) return;
+        fireManualRuleChanged(newRule);
+    }
+
     private final IConditionListener mListener = new IConditionListener.Stub() {
         @Override
         public void onConditionsReceived(Condition[] conditions) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 78baf67..216a4da 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -21,7 +21,6 @@
 import android.media.VolumeProvider;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.PlaybackState;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
 import android.view.View;
 import android.widget.TextView;
 
@@ -145,10 +144,6 @@
         return HMMAA.format(new Date(millis));
     }
 
-    public static String getShortTime(DowntimeInfo info) {
-        return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
-    }
-
     public static void setText(TextView tv, CharSequence text) {
         if (Objects.equals(tv.getText(), text)) return;
         tv.setText(text);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d8b3965..8d336ab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -38,7 +38,6 @@
 import android.os.SystemClock;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseBooleanArray;
@@ -606,14 +605,6 @@
                     text = mContext.getString(R.string.volume_dnd_ends_at,
                             Util.getShortTime(countdown));
                     action = mContext.getString(R.string.volume_end_now);
-                } else {
-                    final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
-                            exitCondition.id);
-                    if (info != null) {
-                        text = mContext.getString(R.string.volume_dnd_ends_at,
-                                Util.getShortTime(info));
-                        action = mContext.getString(R.string.volume_end_now);
-                    }
                 }
             }
             if (text == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 265e2c6..5bc8c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -41,6 +41,7 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -393,8 +394,15 @@
         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
     }
 
+    private Condition getExitCondition() {
+        final ZenModeConfig config = mNoMan.getZenModeConfig();
+        return config == null ? null
+                : config.manualRule == null ? null
+                : config.manualRule.condition;
+    }
+
     private boolean updateExitConditionW() {
-        final Condition exitCondition = mNoMan.getZenModeCondition();
+        final Condition exitCondition = getExitCondition();
         if (Objects.equals(mState.exitCondition, exitCondition)) return false;
         mState.exitCondition = exitCondition;
         return true;
@@ -476,12 +484,12 @@
     }
 
     private void onSetExitConditionW(Condition condition) {
-        mNoMan.setZenModeCondition(condition);
+        mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
     }
 
     private void onSetZenModeW(int mode) {
         if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
-        mNoMan.setZenMode(mode);
+        mNoMan.setZenMode(mode, null, TAG);
     }
 
     private void onDismissRequestedW(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f99eb6d..ef8257c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -17,10 +17,11 @@
 
 import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.provider.Settings.Global;
-import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -34,6 +35,8 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
+import java.util.Objects;
+
 /**
  * Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
  */
@@ -57,6 +60,7 @@
     private TextView mSummaryLine2;
     private boolean mFooterExpanded;
     private int mZen = -1;
+    private ZenModeConfig mConfig;
     private Callback mCallback;
 
     public ZenFooter(Context context, AttributeSet attrs) {
@@ -102,8 +106,8 @@
                 setZen(zen);
             }
             @Override
-            public void onExitConditionChanged(Condition exitCondition) {
-                update();
+            public void onConfigChanged(ZenModeConfig config) {
+                setConfig(config);
             }
         });
         mSwitchBar.setOnClickListener(new OnClickListener() {
@@ -129,6 +133,7 @@
             }
         });
         mZen = mController.getZen();
+        mConfig = mController.getConfig();
         update();
     }
 
@@ -138,6 +143,12 @@
         update();
     }
 
+    private void setConfig(ZenModeConfig config) {
+        if (Objects.equals(mConfig, config)) return;
+        mConfig = config;
+        update();
+    }
+
     public boolean isZen() {
         return isZenPriority() || isZenAlarms() || isZenNone();
     }
@@ -196,7 +207,9 @@
                 : isZenNone() ? mContext.getString(R.string.interruption_level_none)
                 : null;
         Util.setText(mSummaryLine1, line1);
-        Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
+        final String line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
+                ActivityManager.getCurrentUser());
+        Util.setText(mSummaryLine2, line2);
     }
 
     private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
@@ -208,7 +221,7 @@
                         : Global.ZEN_MODE_OFF;
                 mZen = newZen;  // this one's optimistic
                 setFooterExpanded(isChecked);
-                mController.setZen(newZen);
+                mController.setZen(newZen, null, TAG);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index cb6c29f..f6d4c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -32,6 +32,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -157,7 +158,6 @@
         }
         mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
         mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
-        setExpanded(mEmbedded);
         updateWidgets();
     }
 
@@ -278,7 +278,7 @@
         if (expanded == mExpanded) return;
         if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
         mExpanded = expanded;
-        if (mExpanded) {
+        if (mExpanded && isShown()) {
             ensureSelection();
         }
         updateWidgets();
@@ -299,7 +299,7 @@
             });
         }
         if (mRequestingConditions) {
-            mTimeCondition = parseExistingTimeCondition(mExitCondition);
+            mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
             if (mTimeCondition != null) {
                 mBucketIndex = -1;
             } else {
@@ -327,10 +327,9 @@
         for (int i = 0; i < mMaxConditions; i++) {
             mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
         }
-        setExitCondition(mController.getExitCondition());
         refreshExitConditionText();
         mSessionZen = getSelectedZen(-1);
-        handleUpdateZen(mController.getZen());
+        handleUpdateManualRule(mController.getManualRule());
         if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
         hideAllConditions();
         mController.addCallback(mZenCallback);
@@ -352,6 +351,10 @@
         return condition != null ? condition.id : null;
     }
 
+    private Uri getRealConditionId(Condition condition) {
+        return isForever(condition) ? null : getConditionId(condition);
+    }
+
     private static boolean sameConditionId(Condition lhs, Condition rhs) {
         return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
     }
@@ -360,18 +363,18 @@
         return condition == null ? null : condition.copy();
     }
 
-    public String getExitConditionText() {
-        return mExitConditionText;
+    private void refreshExitConditionText() {
+        mExitConditionText = getExitConditionText(mContext, mExitCondition);
     }
 
-    private void refreshExitConditionText() {
-        if (mExitCondition == null) {
-            mExitConditionText = foreverSummary();
-        } else if (isCountdown(mExitCondition)) {
-            final Condition condition = parseExistingTimeCondition(mExitCondition);
-            mExitConditionText = condition != null ? condition.summary : foreverSummary();
+    public static String getExitConditionText(Context context, Condition exitCondition) {
+        if (exitCondition == null) {
+            return foreverSummary(context);
+        } else if (isCountdown(exitCondition)) {
+            final Condition condition = parseExistingTimeCondition(context, exitCondition);
+            return condition != null ? condition.summary : foreverSummary(context);
         } else {
-            mExitConditionText = mExitCondition.summary;
+            return exitCondition.summary;
         }
     }
 
@@ -386,9 +389,16 @@
         mIconPulser.start(noneButton);
     }
 
+    private void handleUpdateManualRule(ZenRule rule) {
+        final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
+        handleUpdateZen(zen);
+        final Condition c = rule != null ? rule.condition : null;
+        handleExitConditionChanged(c);
+    }
+
     private void handleUpdateZen(int zen) {
         if (mSessionZen != -1 && mSessionZen != zen) {
-            setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF);
+            setExpanded(mEmbedded && isShown() || !mEmbedded && zen != Global.ZEN_MODE_OFF);
             mSessionZen = zen;
         }
         mZenButtons.setSelectedValue(zen);
@@ -402,6 +412,20 @@
         }
     }
 
+    private void handleExitConditionChanged(Condition exitCondition) {
+        setExitCondition(exitCondition);
+        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
+        final int N = getVisibleConditions();
+        for (int i = 0; i < N; i++) {
+            final ConditionTag tag = getConditionTagAt(i);
+            if (tag != null) {
+                if (sameConditionId(tag.condition, mExitCondition)) {
+                    bind(exitCondition, mZenConditions.getChildAt(i));
+                }
+            }
+        }
+    }
+
     private Condition getSelectedCondition() {
         final int N = getVisibleConditions();
         for (int i = 0; i < N; i++) {
@@ -447,14 +471,14 @@
                 ? mSubheadWarningColor : mSubheadColor);
     }
 
-    private Condition parseExistingTimeCondition(Condition condition) {
+    private static Condition parseExistingTimeCondition(Context context, Condition condition) {
         if (condition == null) return null;
         final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
         if (time == 0) return null;
         final long now = System.currentTimeMillis();
         final long span = time - now;
         if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
-        return ZenModeConfig.toTimeCondition(mContext,
+        return ZenModeConfig.toTimeCondition(context,
                 time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
     }
 
@@ -514,18 +538,18 @@
             mZenConditions.getChildAt(i).setVisibility(GONE);
         }
         // ensure something is selected
-        if (mExpanded) {
+        if (mExpanded && isShown()) {
             ensureSelection();
         }
     }
 
     private Condition forever() {
-        return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
-                0 /*flags*/);
+        return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+                Condition.STATE_TRUE, 0 /*flags*/);
     }
 
-    private String foreverSummary() {
-        return mContext.getString(com.android.internal.R.string.zen_mode_forever);
+    private static String foreverSummary(Context context) {
+        return context.getString(com.android.internal.R.string.zen_mode_forever);
     }
 
     private ConditionTag getConditionTagAt(int index) {
@@ -574,21 +598,7 @@
         }
     }
 
-    private void handleExitConditionChanged(Condition exitCondition) {
-        setExitCondition(exitCondition);
-        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
-        final int N = getVisibleConditions();
-        for (int i = 0; i < N; i++) {
-            final ConditionTag tag = getConditionTagAt(i);
-            if (tag != null) {
-                if (sameConditionId(tag.condition, mExitCondition)) {
-                    bind(exitCondition, mZenConditions.getChildAt(i));
-                }
-            }
-        }
-    }
-
-    private boolean isCountdown(Condition c) {
+    private static boolean isCountdown(Condition c) {
         return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
     }
 
@@ -770,17 +780,21 @@
 
     private void select(final Condition condition) {
         if (DEBUG) Log.d(mTag, "select " + condition);
-        final boolean isForever = isForever(condition);
+        if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
+            if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
+            return;
+        }
+        final Uri realConditionId = getRealConditionId(condition);
         if (mController != null) {
             AsyncTask.execute(new Runnable() {
                 @Override
                 public void run() {
-                    mController.setExitCondition(isForever ? null : condition);
+                    mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
                 }
             });
         }
         setExitCondition(condition);
-        if (isForever) {
+        if (realConditionId == null) {
             mPrefs.setMinuteIndex(-1);
         } else if (isCountdown(condition) && mBucketIndex != -1) {
             mPrefs.setMinuteIndex(mBucketIndex);
@@ -808,24 +822,19 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         @Override
-        public void onZenChanged(int zen) {
-            mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
-        }
-        @Override
         public void onConditionsChanged(Condition[] conditions) {
             mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
         }
 
         @Override
-        public void onExitConditionChanged(Condition exitCondition) {
-            mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
+        public void onManualRuleChanged(ZenRule rule) {
+            mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
         }
     };
 
     private final class H extends Handler {
         private static final int UPDATE_CONDITIONS = 1;
-        private static final int EXIT_CONDITION_CHANGED = 2;
-        private static final int UPDATE_ZEN = 3;
+        private static final int MANUAL_RULE_CHANGED = 2;
 
         private H() {
             super(Looper.getMainLooper());
@@ -835,10 +844,8 @@
         public void handleMessage(Message msg) {
             if (msg.what == UPDATE_CONDITIONS) {
                 handleUpdateConditions((Condition[]) msg.obj);
-            } else if (msg.what == EXIT_CONDITION_CHANGED) {
-                handleExitConditionChanged((Condition) msg.obj);
-            } else if (msg.what == UPDATE_ZEN) {
-                handleUpdateZen(msg.arg1);
+            } else if (msg.what == MANUAL_RULE_CHANGED) {
+                handleUpdateManualRule((ZenRule) msg.obj);
             }
         }
     }
@@ -930,12 +937,13 @@
     private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
         @Override
         public void onSelected(final Object value) {
-            if (value != null && mZenButtons.isShown()) {
+            if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
                 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
+                final Uri realConditionId = getRealConditionId(mSessionExitCondition);
                 AsyncTask.execute(new Runnable() {
                     @Override
                     public void run() {
-                        mController.setZen((Integer) value);
+                        mController.setZen((Integer) value, realConditionId, TAG + ".selectZen");
                     }
                 });
             }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index ab53fbc..fc2eced 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -25,12 +25,10 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionListener;
 import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -41,50 +39,44 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Objects;
 
 public class ConditionProviders extends ManagedServices {
-    private static final Condition[] NO_CONDITIONS = new Condition[0];
+    private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
+    private final ArrayMap<IBinder, IConditionListener> mListeners = new ArrayMap<>();
+    private final ArraySet<String> mSystemConditionProviderNames;
+    private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
+            = new ArraySet<>();
 
-    private final ZenModeHelper mZenModeHelper;
-    private final ArrayMap<IBinder, IConditionListener> mListeners
-            = new ArrayMap<IBinder, IConditionListener>();
-    private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
-    private final ArraySet<String> mSystemConditionProviders;
-    private final CountdownConditionProvider mCountdown;
-    private final DowntimeConditionProvider mDowntime;
-    private final NextAlarmConditionProvider mNextAlarm;
-    private final NextAlarmTracker mNextAlarmTracker;
+    private Callback mCallback;
 
-    private Condition mExitCondition;
-    private ComponentName mExitConditionComponent;
-
-    public ConditionProviders(Context context, Handler handler,
-            UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
+    public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
         super(context, handler, new Object(), userProfiles);
-        mZenModeHelper = zenModeHelper;
-        mZenModeHelper.addCallback(new ZenModeHelperCallback());
-        mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
+        mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
                 "system.condition.providers",
                 R.array.config_system_condition_providers));
-        final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
-        final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
-        final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
-        mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
-        mCountdown = countdown ? new CountdownConditionProvider() : null;
-        mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
-                mZenModeHelper) : null;
-        mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
-        loadZenConfig();
     }
 
-    public boolean isSystemConditionProviderEnabled(String path) {
-        return mSystemConditionProviders.contains(path);
+    public void setCallback(Callback callback) {
+        mCallback = callback;
+    }
+
+    public boolean isSystemProviderEnabled(String path) {
+        return mSystemConditionProviderNames.contains(path);
+    }
+
+    public void addSystemProvider(SystemConditionProviderService service) {
+        mSystemConditionProviders.add(service);
+        service.attachBase(mContext);
+        registerService(service.asInterface(), service.getComponent(), UserHandle.USER_OWNER);
+    }
+
+    public Iterable<SystemConditionProviderService> getSystemProviders() {
+        return mSystemConditionProviders;
     }
 
     @Override
     protected Config getConfig() {
-        Config c = new Config();
+        final Config c = new Config();
         c.caption = "condition provider";
         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
         c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
@@ -98,12 +90,6 @@
     public void dump(PrintWriter pw, DumpFilter filter) {
         super.dump(pw, filter);
         synchronized(mMutex) {
-            if (filter == null) {
-                pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
-                for (int i = 0; i < mListeners.size(); i++) {
-                    pw.print("      "); pw.println(mListeners.keyAt(i));
-                }
-            }
             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
             for (int i = 0; i < mRecords.size(); i++) {
                 final ConditionRecord r = mRecords.get(i);
@@ -115,18 +101,15 @@
                 }
             }
         }
-        pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
-        if (mCountdown != null) {
-            mCountdown.dump(pw, filter);
+        if (filter == null) {
+            pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
+            for (int i = 0; i < mListeners.size(); i++) {
+                pw.print("      "); pw.println(mListeners.keyAt(i));
+            }
         }
-        if (mDowntime != null) {
-            mDowntime.dump(pw, filter);
-        }
-        if (mNextAlarm != null) {
-            mNextAlarm.dump(pw, filter);
-        }
-        if (mNextAlarmTracker != null) {
-            mNextAlarmTracker.dump(pw, filter);
+        pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
+        for (int i = 0; i < mSystemConditionProviders.size(); i++) {
+            mSystemConditionProviders.valueAt(i).dump(pw, filter);
         }
     }
 
@@ -138,31 +121,16 @@
     @Override
     public void onBootPhaseAppsCanStart() {
         super.onBootPhaseAppsCanStart();
-        if (mNextAlarmTracker != null) {
-            mNextAlarmTracker.init();
-        }
-        if (mCountdown != null) {
-            mCountdown.attachBase(mContext);
-            registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
-                    UserHandle.USER_OWNER);
-        }
-        if (mDowntime != null) {
-            mDowntime.attachBase(mContext);
-            registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
-                    UserHandle.USER_OWNER);
-        }
-        if (mNextAlarm != null) {
-            mNextAlarm.attachBase(mContext);
-            registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
-                    UserHandle.USER_OWNER);
+        if (mCallback != null) {
+            mCallback.onBootComplete();
         }
     }
 
     @Override
     public void onUserSwitched() {
         super.onUserSwitched();
-        if (mNextAlarmTracker != null) {
-            mNextAlarmTracker.onUserSwitched();
+        if (mCallback != null) {
+            mCallback.onUserSwitched();
         }
     }
 
@@ -174,24 +142,6 @@
         } catch (RemoteException e) {
             // we tried
         }
-        synchronized (mMutex) {
-            if (info.component.equals(mExitConditionComponent)) {
-                // ensure record exists, we'll wire it up and subscribe below
-                final ConditionRecord manualRecord =
-                        getRecordLocked(mExitCondition.id, mExitConditionComponent);
-                manualRecord.isManual = true;
-            }
-            final int N = mRecords.size();
-            for(int i = 0; i < N; i++) {
-                final ConditionRecord r = mRecords.get(i);
-                if (!r.component.equals(info.component)) continue;
-                r.info = info;
-                // if automatic or manual, auto-subscribe
-                if (r.isAutomatic || r.isManual) {
-                    subscribeLocked(r);
-                }
-            }
-        }
     }
 
     @Override
@@ -200,15 +150,6 @@
         for (int i = mRecords.size() - 1; i >= 0; i--) {
             final ConditionRecord r = mRecords.get(i);
             if (!r.component.equals(removed.component)) continue;
-            if (r.isManual) {
-                // removing the current manual condition, exit zen
-                onManualConditionClearing();
-                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
-            }
-            if (r.isAutomatic) {
-                // removing an automatic condition, exit zen
-                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
-            }
             mRecords.remove(i);
         }
     }
@@ -219,9 +160,9 @@
         }
     }
 
-    public void requestZenModeConditions(IConditionListener callback, int relevance) {
+    public void requestConditions(IConditionListener callback, int relevance) {
         synchronized(mMutex) {
-            if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
+            if (DEBUG) Slog.d(TAG, "requestConditions callback=" + callback
                     + " relevance=" + Condition.relevanceToString(relevance));
             if (callback == null) return;
             relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
@@ -262,7 +203,8 @@
         return rt;
     }
 
-    private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+    private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
+        if (id == null || component == null) return null;
         final int N = mRecords.size();
         for (int i = 0; i < N; i++) {
             final ConditionRecord r = mRecords.get(i);
@@ -270,9 +212,12 @@
                 return r;
             }
         }
-        final ConditionRecord r = new ConditionRecord(id, component);
-        mRecords.add(r);
-        return r;
+        if (create) {
+            final ConditionRecord r = new ConditionRecord(id, component);
+            mRecords.add(r);
+            return r;
+        }
+        return null;
     }
 
     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
@@ -291,99 +236,48 @@
             }
             for (int i = 0; i < N; i++) {
                 final Condition c = conditions[i];
-                final ConditionRecord r = getRecordLocked(c.id, info.component);
-                final Condition oldCondition = r.condition;
-                final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
+                final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
                 r.info = info;
                 r.condition = c;
-                // if manual, exit zen if false (or failed), update if true (and changed)
-                if (r.isManual) {
-                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
-                        final boolean failed = c.state == Condition.STATE_ERROR;
-                        if (failed) {
-                            Slog.w(TAG, "Exit zen: manual condition failed: " + c);
-                        } else if (DEBUG) {
-                            Slog.d(TAG, "Exit zen: manual condition false: " + c);
-                        }
-                        onManualConditionClearing();
-                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
-                                "manualConditionExit");
-                        unsubscribeLocked(r);
-                        r.isManual = false;
-                    } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
-                        if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
-                                + oldCondition + " new=" + c);
-                        setZenModeCondition(c, "conditionUpdate");
-                    }
-                }
-                // if automatic, exit zen if false (or failed), enter zen if true
-                if (r.isAutomatic) {
-                    if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
-                        final boolean failed = c.state == Condition.STATE_ERROR;
-                        if (failed) {
-                            Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
-                        } else if (DEBUG) {
-                            Slog.d(TAG, "Exit zen: automatic condition false: " + c);
-                        }
-                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
-                                "automaticConditionExit");
-                    } else if (c.state == Condition.STATE_TRUE) {
-                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
-                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
-                                "automaticConditionEnter");
-                    }
+                if (mCallback != null) {
+                    mCallback.onConditionChanged(c.id, c);
                 }
             }
         }
     }
 
-    private void ensureRecordExists(Condition condition, IConditionProvider provider,
-            ComponentName component) {
+    public void ensureRecordExists(ComponentName component, Uri conditionId,
+            IConditionProvider provider) {
         // constructed by convention, make sure the record exists...
-        final ConditionRecord r = getRecordLocked(condition.id, component);
+        final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
         if (r.info == null) {
             // ... and is associated with the in-process service
             r.info = checkServiceTokenLocked(provider);
         }
     }
 
-    public void setZenModeCondition(Condition condition, String reason) {
-        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
-        synchronized(mMutex) {
-            ComponentName conditionComponent = null;
-            if (condition != null) {
-                if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
-                    ensureRecordExists(condition, mCountdown.asInterface(),
-                            CountdownConditionProvider.COMPONENT);
-                }
-                if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
-                    ensureRecordExists(condition, mDowntime.asInterface(),
-                            DowntimeConditionProvider.COMPONENT);
-                }
+    public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
+        synchronized (mMutex) {
+            final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+            if (r == null) {
+                Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
+                return false;
             }
-            final int N = mRecords.size();
-            for (int i = 0; i < N; i++) {
-                final ConditionRecord r = mRecords.get(i);
-                final boolean idEqual = condition != null && r.id.equals(condition.id);
-                if (r.isManual && !idEqual) {
-                    // was previous manual condition, unsubscribe
-                    unsubscribeLocked(r);
-                    r.isManual = false;
-                } else if (idEqual && !r.isManual) {
-                    // is new manual condition, subscribe
-                    subscribeLocked(r);
-                    r.isManual = true;
-                }
-                if (idEqual) {
-                    conditionComponent = r.component;
-                }
+            if (r.subscribed) return true;
+            subscribeLocked(r);
+            return r.subscribed;
+        }
+    }
+
+    public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
+        synchronized (mMutex) {
+            final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
+            if (r == null) {
+                Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
+                return;
             }
-            if (!Objects.equals(mExitCondition, condition)) {
-                mExitCondition = condition;
-                mExitConditionComponent = conditionComponent;
-                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
-                saveZenConfigLocked();
-            }
+            if (!r.subscribed) return;
+            unsubscribeLocked(r);;
         }
     }
 
@@ -393,8 +287,9 @@
         RemoteException re = null;
         if (provider != null) {
             try {
-                Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
+                Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
                 provider.onSubscribe(r.id);
+                r.subscribed = true;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Error subscribing to " + r, e);
                 re = e;
@@ -417,53 +312,6 @@
         return rt;
     }
 
-    public void setAutomaticZenModeConditions(Uri[] conditionIds) {
-        setAutomaticZenModeConditions(conditionIds, true /*save*/);
-    }
-
-    private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
-        if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
-                + (conditionIds == null ? null : Arrays.asList(conditionIds)));
-        synchronized(mMutex) {
-            final ArraySet<Uri> newIds = safeSet(conditionIds);
-            final int N = mRecords.size();
-            boolean changed = false;
-            for (int i = 0; i < N; i++) {
-                final ConditionRecord r = mRecords.get(i);
-                final boolean automatic = newIds.contains(r.id);
-                if (!r.isAutomatic && automatic) {
-                    // subscribe to new automatic
-                    subscribeLocked(r);
-                    r.isAutomatic = true;
-                    changed = true;
-                } else if (r.isAutomatic && !automatic) {
-                    // unsubscribe from old automatic
-                    unsubscribeLocked(r);
-                    r.isAutomatic = false;
-                    changed = true;
-                }
-            }
-            if (save && changed) {
-                saveZenConfigLocked();
-            }
-        }
-    }
-
-    public Condition[] getAutomaticZenModeConditions() {
-        synchronized(mMutex) {
-            final int N = mRecords.size();
-            ArrayList<Condition> rt = null;
-            for (int i = 0; i < N; i++) {
-                final ConditionRecord r = mRecords.get(i);
-                if (r.isAutomatic && r.condition != null) {
-                    if (rt == null) rt = new ArrayList<Condition>();
-                    rt.add(r.condition);
-                }
-            }
-            return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
-        }
-    }
-
     private void unsubscribeLocked(ConditionRecord r) {
         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
         final IConditionProvider provider = provider(r);
@@ -475,6 +323,7 @@
                 Slog.w(TAG, "Error unsubscribing to " + r, e);
                 re = e;
             }
+            r.subscribed = false;
         }
         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
     }
@@ -495,7 +344,7 @@
             for (int i = mRecords.size() - 1; i >= 0; i--) {
                 final ConditionRecord r = mRecords.get(i);
                 if (r.info != info) continue;
-                if (r.isManual || r.isAutomatic) continue;
+                if (r.subscribed) continue;
                 mRecords.remove(i);
             }
             try {
@@ -506,103 +355,12 @@
         }
     }
 
-    private void loadZenConfig() {
-        final ZenModeConfig config = mZenModeHelper.getConfig();
-        if (config == null) {
-            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
-            return;
-        }
-        synchronized (mMutex) {
-            final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
-            mExitCondition = config.exitCondition;
-            mExitConditionComponent = config.exitConditionComponent;
-            if (changingExit) {
-                ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
-            }
-            if (mDowntime != null) {
-                mDowntime.setConfig(config);
-            }
-            if (config.conditionComponents == null || config.conditionIds == null
-                    || config.conditionComponents.length != config.conditionIds.length) {
-                if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
-                setAutomaticZenModeConditions(null, false /*save*/);
-                return;
-            }
-            final ArraySet<Uri> newIds = new ArraySet<Uri>();
-            final int N = config.conditionComponents.length;
-            for (int i = 0; i < N; i++) {
-                final ComponentName component = config.conditionComponents[i];
-                final Uri id = config.conditionIds[i];
-                if (component != null && id != null) {
-                    getRecordLocked(id, component);  // ensure record exists
-                    newIds.add(id);
-                }
-            }
-            if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
-            setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
-        }
-    }
-
-    private void saveZenConfigLocked() {
-        ZenModeConfig config = mZenModeHelper.getConfig();
-        if (config == null) return;
-        config = config.copy();
-        final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
-        final int automaticN = mRecords.size();
-        for (int i = 0; i < automaticN; i++) {
-            final ConditionRecord r = mRecords.get(i);
-            if (r.isAutomatic) {
-                automatic.add(r);
-            }
-        }
-        if (automatic.isEmpty()) {
-            config.conditionComponents = null;
-            config.conditionIds = null;
-        } else {
-            final int N = automatic.size();
-            config.conditionComponents = new ComponentName[N];
-            config.conditionIds = new Uri[N];
-            for (int i = 0; i < N; i++) {
-                final ConditionRecord r = automatic.get(i);
-                config.conditionComponents[i] = r.component;
-                config.conditionIds[i] = r.id;
-            }
-        }
-        config.exitCondition = mExitCondition;
-        config.exitConditionComponent = mExitConditionComponent;
-        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
-        mZenModeHelper.setConfig(config);
-    }
-
-    private void onManualConditionClearing() {
-        if (mDowntime != null) {
-            mDowntime.onManualConditionClearing();
-        }
-    }
-
-    private class ZenModeHelperCallback extends ZenModeHelper.Callback {
-        @Override
-        void onConfigChanged() {
-            loadZenConfig();
-        }
-
-        @Override
-        void onZenModeChanged() {
-            final int mode = mZenModeHelper.getZenMode();
-            if (mode == Global.ZEN_MODE_OFF) {
-                // ensure any manual condition is cleared
-                setZenModeCondition(null, "zenOff");
-            }
-        }
-    }
-
     private static class ConditionRecord {
         public final Uri id;
         public final ComponentName component;
         public Condition condition;
         public ManagedServiceInfo info;
-        public boolean isAutomatic;
-        public boolean isManual;
+        public boolean subscribed;
 
         private ConditionRecord(Uri id, ComponentName component) {
             this.id = id;
@@ -612,10 +370,16 @@
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
-                    .append(id).append(",component=").append(component);
-            if (isAutomatic) sb.append(",automatic");
-            if (isManual) sb.append(",manual");
+                    .append(id).append(",component=").append(component)
+                    .append(",subscribed=").append(subscribed);
             return sb.append(']').toString();
         }
     }
+
+    public interface Callback {
+        void onBootComplete();
+        void onConditionChanged(Uri id, Condition condition);
+        void onUserSwitched();
+    }
+
 }
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 37aacaa..d223353 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -25,7 +25,6 @@
 import android.content.IntentFilter;
 import android.net.Uri;
 import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
 import android.service.notification.IConditionProvider;
 import android.service.notification.ZenModeConfig;
 import android.text.format.DateUtils;
@@ -38,7 +37,7 @@
 import java.util.Date;
 
 /** Built-in zen condition provider for simple time-based conditions */
-public class CountdownConditionProvider extends ConditionProviderService {
+public class CountdownConditionProvider extends SystemConditionProviderService {
     private static final String TAG = "CountdownConditions";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -59,6 +58,27 @@
         if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
     }
 
+    @Override
+    public ComponentName getComponent() {
+        return COMPONENT;
+    }
+
+    @Override
+    public boolean isValidConditionid(Uri id) {
+        return ZenModeConfig.isValidCountdownConditionId(id);
+    }
+
+    @Override
+    public void attachBase(Context base) {
+        attachBaseContext(base);
+    }
+
+    @Override
+    public IConditionProvider asInterface() {
+        return (IConditionProvider) onBind(null);
+    }
+
+    @Override
     public void dump(PrintWriter pw, DumpFilter filter) {
         pw.println("    CountdownConditionProvider:");
         pw.print("      mConnected="); pw.println(mConnected);
@@ -154,11 +174,4 @@
         return new Date(time) + " (" + time + ")";
     }
 
-    public void attachBase(Context base) {
-        attachBaseContext(base);
-    }
-
-    public IConditionProvider asInterface() {
-        return (IConditionProvider) onBind(null);
-    }
 }
diff --git a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java b/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
deleted file mode 100644
index df4ecfd..0000000
--- a/services/core/java/com/android/server/notification/DowntimeConditionProvider.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/**
- * 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.PendingIntent;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.provider.Settings.Global;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.text.format.DateFormat;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/** Built-in zen condition provider for managing downtime */
-public class DowntimeConditionProvider extends ConditionProviderService {
-    private static final String TAG = "DowntimeConditions";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    public static final ComponentName COMPONENT =
-            new ComponentName("android", DowntimeConditionProvider.class.getName());
-
-    private static final String ENTER_ACTION = TAG + ".enter";
-    private static final int ENTER_CODE = 100;
-    private static final String EXIT_ACTION = TAG + ".exit";
-    private static final int EXIT_CODE = 101;
-    private static final String EXTRA_TIME = "time";
-
-    private static final long SECONDS = 1000;
-    private static final long MINUTES = 60 * SECONDS;
-    private static final long HOURS = 60 * MINUTES;
-
-    private final Context mContext = this;
-    private final DowntimeCalendar mCalendar = new DowntimeCalendar();
-    private final FiredAlarms mFiredAlarms = new FiredAlarms();
-    private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
-    private final ConditionProviders mConditionProviders;
-    private final NextAlarmTracker mTracker;
-    private final ZenModeHelper mZenModeHelper;
-
-    private boolean mConnected;
-    private long mLookaheadThreshold;
-    private ZenModeConfig mConfig;
-    private boolean mDowntimed;
-    private boolean mConditionClearing;
-    private boolean mRequesting;
-
-    public DowntimeConditionProvider(ConditionProviders conditionProviders,
-            NextAlarmTracker tracker, ZenModeHelper zenModeHelper) {
-        if (DEBUG) Slog.d(TAG, "new DowntimeConditionProvider()");
-        mConditionProviders = conditionProviders;
-        mTracker = tracker;
-        mZenModeHelper = zenModeHelper;
-    }
-
-    public void dump(PrintWriter pw, DumpFilter filter) {
-        pw.println("    DowntimeConditionProvider:");
-        pw.print("      mConnected="); pw.println(mConnected);
-        pw.print("      mSubscriptions="); pw.println(mSubscriptions);
-        pw.print("      mLookaheadThreshold="); pw.print(mLookaheadThreshold);
-        pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
-        pw.print("      mCalendar="); pw.println(mCalendar);
-        pw.print("      mFiredAlarms="); pw.println(mFiredAlarms);
-        pw.print("      mDowntimed="); pw.println(mDowntimed);
-        pw.print("      mConditionClearing="); pw.println(mConditionClearing);
-        pw.print("      mRequesting="); pw.println(mRequesting);
-    }
-
-    public void attachBase(Context base) {
-        attachBaseContext(base);
-    }
-
-    public IConditionProvider asInterface() {
-        return (IConditionProvider) onBind(null);
-    }
-
-    @Override
-    public void onConnected() {
-        if (DEBUG) Slog.d(TAG, "onConnected");
-        mConnected = true;
-        mLookaheadThreshold = PropConfig.getInt(mContext, "downtime.condition.lookahead",
-                R.integer.config_downtime_condition_lookahead_threshold_hrs) * HOURS;
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(ENTER_ACTION);
-        filter.addAction(EXIT_ACTION);
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        mContext.registerReceiver(mReceiver, filter);
-        mTracker.addCallback(mTrackerCallback);
-        mZenModeHelper.addCallback(mZenCallback);
-        init();
-    }
-
-    @Override
-    public void onDestroy() {
-        if (DEBUG) Slog.d(TAG, "onDestroy");
-        mTracker.removeCallback(mTrackerCallback);
-        mZenModeHelper.removeCallback(mZenCallback);
-        mConnected = false;
-    }
-
-    @Override
-    public void onRequestConditions(int relevance) {
-        if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
-        if (!mConnected) return;
-        mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
-        evaluateSubscriptions();
-    }
-
-    @Override
-    public void onSubscribe(Uri conditionId) {
-        if (DEBUG) Slog.d(TAG, "onSubscribe conditionId=" + conditionId);
-        final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
-        if (downtime == null) return;
-        mFiredAlarms.clear();
-        mSubscriptions.add(conditionId);
-        notifyCondition(downtime);
-    }
-
-    private boolean shouldShowCondition() {
-        final long now = System.currentTimeMillis();
-        if (DEBUG) Slog.d(TAG, "shouldShowCondition now=" + mCalendar.isInDowntime(now)
-                + " lookahead="
-                + (mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold)));
-        return mCalendar.isInDowntime(now)
-                || mCalendar.nextDowntimeStart(now) <= (now + mLookaheadThreshold);
-    }
-
-    private void notifyCondition(DowntimeInfo downtime) {
-        if (mConfig == null) {
-            // we don't know yet
-            notifyCondition(createCondition(downtime, Condition.STATE_UNKNOWN));
-            return;
-        }
-        if (!downtime.equals(mConfig.toDowntimeInfo())) {
-            // not the configured downtime, consider it false
-            notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
-            return;
-        }
-        if (!shouldShowCondition()) {
-            // configured downtime, but not within the time range
-            notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
-            return;
-        }
-        if (isZenNone() && mFiredAlarms.findBefore(System.currentTimeMillis())) {
-            // within the configured time range, but wake up if none and the next alarm is fired
-            notifyCondition(createCondition(downtime, Condition.STATE_FALSE));
-            return;
-        }
-        // within the configured time range, condition still valid
-        notifyCondition(createCondition(downtime, Condition.STATE_TRUE));
-    }
-
-    private boolean isZenNone() {
-        return mZenModeHelper.getZenMode() == Global.ZEN_MODE_NO_INTERRUPTIONS;
-    }
-
-    private boolean isZenOff() {
-        return mZenModeHelper.getZenMode() == Global.ZEN_MODE_OFF;
-    }
-
-    private void evaluateSubscriptions() {
-        ArraySet<Uri> conditions = mSubscriptions;
-        if (mConfig != null && mRequesting && shouldShowCondition()) {
-            final Uri id = ZenModeConfig.toDowntimeConditionId(mConfig.toDowntimeInfo());
-            if (!conditions.contains(id)) {
-                conditions = new ArraySet<Uri>(conditions);
-                conditions.add(id);
-            }
-        }
-        for (Uri conditionId : conditions) {
-            final DowntimeInfo downtime = ZenModeConfig.tryParseDowntimeConditionId(conditionId);
-            if (downtime != null) {
-                notifyCondition(downtime);
-            }
-        }
-    }
-
-    @Override
-    public void onUnsubscribe(Uri conditionId) {
-        final boolean current = mSubscriptions.contains(conditionId);
-        if (DEBUG) Slog.d(TAG, "onUnsubscribe conditionId=" + conditionId + " current=" + current);
-        mSubscriptions.remove(conditionId);
-        mFiredAlarms.clear();
-    }
-
-    public void setConfig(ZenModeConfig config) {
-        if (Objects.equals(mConfig, config)) return;
-        final boolean downtimeChanged = mConfig == null || config == null
-                || !mConfig.toDowntimeInfo().equals(config.toDowntimeInfo());
-        mConfig = config;
-        if (DEBUG) Slog.d(TAG, "setConfig downtimeChanged=" + downtimeChanged);
-        if (mConnected && downtimeChanged) {
-            mDowntimed = false;
-            init();
-        }
-        // when active, mark downtime as entered for today
-        if (mConfig != null && mConfig.exitCondition != null
-                && ZenModeConfig.isValidDowntimeConditionId(mConfig.exitCondition.id)) {
-            mDowntimed = true;
-        }
-    }
-
-    public void onManualConditionClearing() {
-        mConditionClearing = true;
-    }
-
-    private Condition createCondition(DowntimeInfo downtime, int state) {
-        if (downtime == null) return null;
-        final Uri id = ZenModeConfig.toDowntimeConditionId(downtime);
-        final String skeleton = DateFormat.is24HourFormat(mContext) ? "Hm" : "hma";
-        final Locale locale = Locale.getDefault();
-        final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
-        final long now = System.currentTimeMillis();
-        long endTime = mCalendar.getNextTime(now, downtime.endHour, downtime.endMinute);
-        if (isZenNone()) {
-            final AlarmClockInfo nextAlarm = mTracker.getNextAlarm();
-            final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
-            if (nextAlarmTime > now && nextAlarmTime < endTime) {
-                endTime = nextAlarmTime;
-            }
-        }
-        final String formatted = new SimpleDateFormat(pattern, locale).format(new Date(endTime));
-        final String summary = mContext.getString(R.string.downtime_condition_summary, formatted);
-        final String line1 = mContext.getString(R.string.downtime_condition_line_one);
-        return new Condition(id, summary, line1, formatted, 0, state, Condition.FLAG_RELEVANT_NOW);
-    }
-
-    private void init() {
-        mCalendar.setDowntimeInfo(mConfig != null ? mConfig.toDowntimeInfo() : null);
-        evaluateSubscriptions();
-        updateAlarms();
-        evaluateAutotrigger();
-    }
-
-    private void updateAlarms() {
-        if (mConfig == null) return;
-        updateAlarm(ENTER_ACTION, ENTER_CODE, mConfig.sleepStartHour, mConfig.sleepStartMinute);
-        updateAlarm(EXIT_ACTION, EXIT_CODE, 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 long time = mCalendar.getNextTime(now, hr, min);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, requestCode,
-                new Intent(action)
-                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                    .putExtra(EXTRA_TIME, time),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        alarms.cancel(pendingIntent);
-        if (mConfig.sleepMode != null) {
-            if (DEBUG) Slog.d(TAG, String.format("Scheduling %s for %s, in %s, now=%s",
-                    action, ts(time), NextAlarmTracker.formatDuration(time - now), ts(now)));
-            alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
-        }
-    }
-
-    private static String ts(long time) {
-        return new Date(time) + " (" + time + ")";
-    }
-
-    private void onEvaluateNextAlarm(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
-        if (!booted) return;  // we don't know yet
-        if (DEBUG) Slog.d(TAG, "onEvaluateNextAlarm " + mTracker.formatAlarmDebug(nextAlarm));
-        if (nextAlarm != null && wakeupTime > 0 && System.currentTimeMillis() > wakeupTime) {
-            if (DEBUG) Slog.d(TAG, "Alarm fired: " + mTracker.formatAlarmDebug(wakeupTime));
-            mFiredAlarms.add(wakeupTime);
-        }
-        evaluateSubscriptions();
-    }
-
-    private void evaluateAutotrigger() {
-        String skipReason = null;
-        if (mConfig == null) {
-            skipReason = "no config";
-        } else if (mDowntimed) {
-            skipReason = "already downtimed";
-        } else if (mZenModeHelper.getZenMode() != Global.ZEN_MODE_OFF) {
-            skipReason = "already in zen";
-        } else if (!mCalendar.isInDowntime(System.currentTimeMillis())) {
-            skipReason = "not in downtime";
-        }
-        if (skipReason != null) {
-            ZenLog.traceDowntimeAutotrigger("Autotrigger skipped: " + skipReason);
-            return;
-        }
-        ZenLog.traceDowntimeAutotrigger("Autotrigger fired");
-        mZenModeHelper.setZenMode(mConfig.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
-                : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtime");
-        final Condition condition = createCondition(mConfig.toDowntimeInfo(), Condition.STATE_TRUE);
-        mConditionProviders.setZenModeCondition(condition, "downtime");
-    }
-
-    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            final long now = System.currentTimeMillis();
-            if (ENTER_ACTION.equals(action) || EXIT_ACTION.equals(action)) {
-                final long schTime = intent.getLongExtra(EXTRA_TIME, 0);
-                if (DEBUG) Slog.d(TAG, String.format("%s scheduled for %s, fired at %s, delta=%s",
-                        action, ts(schTime), ts(now), now - schTime));
-                if (ENTER_ACTION.equals(action)) {
-                    evaluateAutotrigger();
-                } else /*EXIT_ACTION*/ {
-                    mDowntimed = false;
-                }
-                mFiredAlarms.clear();
-            } else if (Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
-                if (DEBUG) Slog.d(TAG, "timezone changed to " + TimeZone.getDefault());
-                mCalendar.setTimeZone(TimeZone.getDefault());
-                mFiredAlarms.clear();
-            } else if (Intent.ACTION_TIME_CHANGED.equals(action)) {
-                if (DEBUG) Slog.d(TAG, "time changed to " + now);
-                mFiredAlarms.clear();
-            } else {
-                if (DEBUG) Slog.d(TAG, action + " fired at " + now);
-            }
-            evaluateSubscriptions();
-            updateAlarms();
-        }
-    };
-
-    private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
-        @Override
-        public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
-            DowntimeConditionProvider.this.onEvaluateNextAlarm(nextAlarm, wakeupTime, booted);
-        }
-    };
-
-    private final ZenModeHelper.Callback mZenCallback = new ZenModeHelper.Callback() {
-        @Override
-        void onZenModeChanged() {
-            if (mConditionClearing && isZenOff()) {
-                evaluateAutotrigger();
-            }
-            mConditionClearing = false;
-            evaluateSubscriptions();
-        }
-    };
-
-    private class FiredAlarms {
-        private final ArraySet<Long> mFiredAlarms = new ArraySet<Long>();
-
-        @Override
-        public String toString() {
-            final StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < mFiredAlarms.size(); i++) {
-                if (i > 0) sb.append(',');
-                sb.append(mTracker.formatAlarmDebug(mFiredAlarms.valueAt(i)));
-            }
-            return sb.toString();
-        }
-
-        public void add(long firedAlarm) {
-            mFiredAlarms.add(firedAlarm);
-        }
-
-        public void clear() {
-            mFiredAlarms.clear();
-        }
-
-        public boolean findBefore(long time) {
-            for (int i = 0; i < mFiredAlarms.size(); i++) {
-                if (mFiredAlarms.valueAt(i) < time) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java b/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
deleted file mode 100644
index 1634c65..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmConditionProvider.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.AlarmManager.AlarmClockInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.net.Uri;
-import android.service.notification.Condition;
-import android.service.notification.ConditionProviderService;
-import android.service.notification.IConditionProvider;
-import android.service.notification.ZenModeConfig;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.internal.R;
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-
-/**
- * Built-in zen condition provider for alarm-clock-based conditions.
- *
- * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise
- * it as an exit condition for zen mode.
- *
- * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not
- * survive a reboot.  Maintain the illusion of a consistent next alarm value by holding on to
- * a persisted condition until we receive the first value after reboot, or timeout with no value.
- */
-public class NextAlarmConditionProvider extends ConditionProviderService {
-    private static final String TAG = "NextAlarmConditions";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final long SECONDS = 1000;
-    private static final long MINUTES = 60 * SECONDS;
-    private static final long HOURS = 60 * MINUTES;
-
-    private static final long BAD_CONDITION = -1;
-
-    public static final ComponentName COMPONENT =
-            new ComponentName("android", NextAlarmConditionProvider.class.getName());
-
-    private final Context mContext = this;
-    private final NextAlarmTracker mTracker;
-    private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
-
-    private boolean mConnected;
-    private long mLookaheadThreshold;
-    private boolean mRequesting;
-
-    public NextAlarmConditionProvider(NextAlarmTracker tracker) {
-        if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()");
-        mTracker = tracker;
-    }
-
-    public void dump(PrintWriter pw, DumpFilter filter) {
-        pw.println("    NextAlarmConditionProvider:");
-        pw.print("      mConnected="); pw.println(mConnected);
-        pw.print("      mLookaheadThreshold="); pw.print(mLookaheadThreshold);
-        pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")");
-        pw.print("      mSubscriptions="); pw.println(mSubscriptions);
-        pw.print("      mRequesting="); pw.println(mRequesting);
-    }
-
-    @Override
-    public void onConnected() {
-        if (DEBUG) Slog.d(TAG, "onConnected");
-        mLookaheadThreshold = PropConfig.getInt(mContext, "nextalarm.condition.lookahead",
-                R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS;
-        mConnected = true;
-        mTracker.addCallback(mTrackerCallback);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        if (DEBUG) Slog.d(TAG, "onDestroy");
-        mTracker.removeCallback(mTrackerCallback);
-        mConnected = false;
-    }
-
-    @Override
-    public void onRequestConditions(int relevance) {
-        if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
-        if (!mConnected) return;
-        mRequesting = (relevance & Condition.FLAG_RELEVANT_NOW) != 0;
-        mTracker.evaluate();
-    }
-
-    @Override
-    public void onSubscribe(Uri conditionId) {
-        if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
-        if (tryParseNextAlarmCondition(conditionId) == BAD_CONDITION) {
-            notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition");
-            return;
-        }
-        mSubscriptions.add(conditionId);
-        mTracker.evaluate();
-    }
-
-    @Override
-    public void onUnsubscribe(Uri conditionId) {
-        if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
-        mSubscriptions.remove(conditionId);
-    }
-
-    public void attachBase(Context base) {
-        attachBaseContext(base);
-    }
-
-    public IConditionProvider asInterface() {
-        return (IConditionProvider) onBind(null);
-    }
-
-    private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) {
-        if (alarm == null) return false;
-        final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis();
-        return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold);
-    }
-
-    private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) {
-        final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm);
-        if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
-                + " alarm=" + formattedAlarm + " reason=" + reason);
-        notifyCondition(new Condition(id,
-                mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm),
-                mContext.getString(R.string.zen_mode_next_alarm_line_one),
-                formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW));
-    }
-
-    private Uri newConditionId(AlarmClockInfo nextAlarm) {
-        return new Uri.Builder().scheme(Condition.SCHEME)
-                .authority(ZenModeConfig.SYSTEM_AUTHORITY)
-                .appendPath(ZenModeConfig.NEXT_ALARM_PATH)
-                .appendPath(Integer.toString(mTracker.getCurrentUserId()))
-                .appendPath(Long.toString(nextAlarm.getTriggerTime()))
-                .build();
-    }
-
-    private long tryParseNextAlarmCondition(Uri conditionId) {
-        return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME)
-                && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
-                && conditionId.getPathSegments().size() == 3
-                && conditionId.getPathSegments().get(0).equals(ZenModeConfig.NEXT_ALARM_PATH)
-                && conditionId.getPathSegments().get(1)
-                        .equals(Integer.toString(mTracker.getCurrentUserId()))
-                                ? tryParseLong(conditionId.getPathSegments().get(2), BAD_CONDITION)
-                                : BAD_CONDITION;
-    }
-
-    private static long tryParseLong(String value, long defValue) {
-        if (TextUtils.isEmpty(value)) return defValue;
-        try {
-            return Long.valueOf(value);
-        } catch (NumberFormatException e) {
-            return defValue;
-        }
-    }
-
-    private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
-        final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm);
-        final long nextAlarmTime = nextAlarm != null ? nextAlarm.getTriggerTime() : 0;
-        if (DEBUG) Slog.d(TAG, "onEvaluate mSubscriptions=" + mSubscriptions
-                + " nextAlarmTime=" +  mTracker.formatAlarmDebug(nextAlarmTime)
-                + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime)
-                + " withinThreshold=" + withinThreshold
-                + " booted=" + booted);
-
-        ArraySet<Uri> conditions = mSubscriptions;
-        if (mRequesting && nextAlarm != null && withinThreshold) {
-            final Uri id = newConditionId(nextAlarm);
-            if (!conditions.contains(id)) {
-                conditions = new ArraySet<Uri>(conditions);
-                conditions.add(id);
-            }
-        }
-        for (Uri conditionId : conditions) {
-            final long time = tryParseNextAlarmCondition(conditionId);
-            if (time == BAD_CONDITION) {
-                notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "badCondition");
-            } else if (!booted) {
-                // we don't know yet
-                if (mSubscriptions.contains(conditionId)) {
-                    notifyCondition(conditionId, nextAlarm, Condition.STATE_UNKNOWN, "!booted");
-                }
-            } else if (time != nextAlarmTime) {
-                // next alarm changed since subscription, consider obsolete
-                notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "changed");
-            } else if (!withinThreshold) {
-                // next alarm outside threshold or in the past, condition = false
-                notifyCondition(conditionId, nextAlarm, Condition.STATE_FALSE, "!within");
-            } else {
-                // next alarm within threshold and in the future, condition = true
-                notifyCondition(conditionId, nextAlarm, Condition.STATE_TRUE, "within");
-            }
-        }
-    }
-
-    private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() {
-        @Override
-        public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
-            NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted);
-        }
-    };
-}
diff --git a/services/core/java/com/android/server/notification/NextAlarmTracker.java b/services/core/java/com/android/server/notification/NextAlarmTracker.java
deleted file mode 100644
index 234f545..0000000
--- a/services/core/java/com/android/server/notification/NextAlarmTracker.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * 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.ActivityManager;
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Handler;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.text.format.DateFormat;
-import android.util.Log;
-import android.util.Slog;
-import android.util.TimeUtils;
-
-import com.android.server.notification.NotificationManagerService.DumpFilter;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Locale;
-
-/** Helper for tracking updates to the current user's next alarm. */
-public class NextAlarmTracker {
-    private static final String TAG = "NextAlarmTracker";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String ACTION_TRIGGER = TAG + ".trigger";
-    private static final String EXTRA_TRIGGER = "trigger";
-    private static final int REQUEST_CODE = 100;
-
-    private static final long SECONDS = 1000;
-    private static final long MINUTES = 60 * SECONDS;
-    private static final long NEXT_ALARM_UPDATE_DELAY = 1 * SECONDS;  // treat clear+set as update
-    private static final long EARLY = 5 * SECONDS;  // fire early, ensure alarm stream is unmuted
-    private static final long WAIT_AFTER_INIT = 5 * MINUTES;// for initial alarm re-registration
-    private static final long WAIT_AFTER_BOOT = 20 * SECONDS;  // for initial alarm re-registration
-
-    private final Context mContext;
-    private final H mHandler = new H();
-    private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
-    private long mInit;
-    private boolean mRegistered;
-    private AlarmManager mAlarmManager;
-    private int mCurrentUserId;
-    private long mScheduledAlarmTime;
-    private long mBootCompleted;
-    private PowerManager.WakeLock mWakeLock;
-
-    public NextAlarmTracker(Context context) {
-        mContext = context;
-    }
-
-    public void dump(PrintWriter pw, DumpFilter filter) {
-        pw.println("    NextAlarmTracker:");
-        pw.print("      len(mCallbacks)="); pw.println(mCallbacks.size());
-        pw.print("      mRegistered="); pw.println(mRegistered);
-        pw.print("      mInit="); pw.println(mInit);
-        pw.print("      mBootCompleted="); pw.println(mBootCompleted);
-        pw.print("      mCurrentUserId="); pw.println(mCurrentUserId);
-        pw.print("      mScheduledAlarmTime="); pw.println(formatAlarmDebug(mScheduledAlarmTime));
-        pw.print("      mWakeLock="); pw.println(mWakeLock);
-    }
-
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-    }
-
-    public void removeCallback(Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
-    public int getCurrentUserId() {
-        return mCurrentUserId;
-    }
-
-    public AlarmClockInfo getNextAlarm() {
-        return mAlarmManager.getNextAlarmClock(mCurrentUserId);
-    }
-
-    public void onUserSwitched() {
-        reset();
-    }
-
-    public void init() {
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        final PowerManager p = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = p.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-        mInit = System.currentTimeMillis();
-        reset();
-    }
-
-    public void reset() {
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
-        }
-        mCurrentUserId = ActivityManager.getCurrentUser();
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        filter.addAction(ACTION_TRIGGER);
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
-        mContext.registerReceiverAsUser(mReceiver, new UserHandle(mCurrentUserId), filter, null,
-                null);
-        mRegistered = true;
-        evaluate();
-    }
-
-    public void destroy() {
-        if (mRegistered) {
-            mContext.unregisterReceiver(mReceiver);
-            mRegistered = false;
-        }
-    }
-
-    public void evaluate() {
-        mHandler.postEvaluate(0);
-    }
-
-    private void fireEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) {
-        for (Callback callback : mCallbacks) {
-            callback.onEvaluate(nextAlarm, wakeupTime, booted);
-        }
-    }
-
-    private void handleEvaluate() {
-        final AlarmClockInfo nextAlarm = mAlarmManager.getNextAlarmClock(mCurrentUserId);
-        final long triggerTime = getEarlyTriggerTime(nextAlarm);
-        final long now = System.currentTimeMillis();
-        final boolean alarmUpcoming = triggerTime > now;
-        final boolean booted = isDoneWaitingAfterBoot(now);
-        if (DEBUG) Slog.d(TAG, "handleEvaluate nextAlarm=" + formatAlarmDebug(triggerTime)
-                + " alarmUpcoming=" + alarmUpcoming
-                + " booted=" + booted);
-        fireEvaluate(nextAlarm, triggerTime, booted);
-        if (!booted) {
-            // recheck after boot
-            final long recheckTime = (mBootCompleted > 0 ? mBootCompleted : now) + WAIT_AFTER_BOOT;
-            rescheduleAlarm(recheckTime);
-            return;
-        }
-        if (alarmUpcoming) {
-            // wake up just before the next alarm
-            rescheduleAlarm(triggerTime);
-        }
-    }
-
-    public static long getEarlyTriggerTime(AlarmClockInfo alarm) {
-        return alarm != null ? (alarm.getTriggerTime() - EARLY) : 0;
-    }
-
-    private boolean isDoneWaitingAfterBoot(long time) {
-        if (mBootCompleted > 0) return (time - mBootCompleted) > WAIT_AFTER_BOOT;
-        if (mInit > 0) return (time - mInit) > WAIT_AFTER_INIT;
-        return true;
-    }
-
-    public static String formatDuration(long millis) {
-        final StringBuilder sb = new StringBuilder();
-        TimeUtils.formatDuration(millis, sb);
-        return sb.toString();
-    }
-
-    public String formatAlarm(AlarmClockInfo alarm) {
-        return alarm != null ? formatAlarm(alarm.getTriggerTime()) : null;
-    }
-
-    private String formatAlarm(long time) {
-        return formatAlarm(time, "Hm", "hma");
-    }
-
-    private String formatAlarm(long time, String skeleton24, String skeleton12) {
-        final String skeleton = DateFormat.is24HourFormat(mContext) ? skeleton24 : skeleton12;
-        final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
-        return DateFormat.format(pattern, time).toString();
-    }
-
-    public String formatAlarmDebug(AlarmClockInfo alarm) {
-        return formatAlarmDebug(alarm != null ? alarm.getTriggerTime() : 0);
-    }
-
-    public String formatAlarmDebug(long time) {
-        if (time <= 0) return Long.toString(time);
-        return String.format("%s (%s)", time, formatAlarm(time, "Hms", "hmsa"));
-    }
-
-    private void rescheduleAlarm(long time) {
-        if (DEBUG) Slog.d(TAG, "rescheduleAlarm " + time);
-        final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
-                new Intent(ACTION_TRIGGER)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                        .putExtra(EXTRA_TRIGGER, time),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        alarms.cancel(pendingIntent);
-        mScheduledAlarmTime = time;
-        if (time > 0) {
-            if (DEBUG) Slog.d(TAG, String.format("Scheduling alarm for %s (in %s)",
-                    formatAlarmDebug(time), formatDuration(time - System.currentTimeMillis())));
-            alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
-        }
-    }
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (DEBUG) Slog.d(TAG, "onReceive " + action);
-            long delay = 0;
-            if (action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
-                delay = NEXT_ALARM_UPDATE_DELAY;
-                if (DEBUG) Slog.d(TAG, String.format("  next alarm for user %s: %s",
-                        mCurrentUserId,
-                        formatAlarmDebug(mAlarmManager.getNextAlarmClock(mCurrentUserId))));
-            } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-                mBootCompleted = System.currentTimeMillis();
-            }
-            mHandler.postEvaluate(delay);
-            mWakeLock.acquire(delay + 5000);  // stay awake during evaluate
-        }
-    };
-
-    private class H extends Handler {
-        private static final int MSG_EVALUATE = 1;
-
-        public void postEvaluate(long delay) {
-            removeMessages(MSG_EVALUATE);
-            sendEmptyMessageDelayed(MSG_EVALUATE, delay);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_EVALUATE) {
-                handleEvaluate();
-            }
-        }
-    }
-
-    public interface Callback {
-        void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted);
-    }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c330046..4cf2909 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -879,7 +879,8 @@
         mRankingHelper = new RankingHelper(getContext(),
                 new RankingWorkerHandler(mRankingThread.getLooper()),
                 extractorNames);
-        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper());
+        mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
         mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
             @Override
             public void onConfigChanged() {
@@ -900,8 +901,6 @@
         importOldBlockDb();
 
         mListeners = new NotificationListeners();
-        mConditionProviders = new ConditionProviders(getContext(),
-                mHandler, mUserProfiles, mZenModeHelper);
         mStatusBar = getLocalService(StatusBarManagerInternal.class);
         mStatusBar.setNotificationDelegate(mNotificationDelegate);
 
@@ -936,7 +935,7 @@
                     Settings.Global.DEVICE_PROVISIONED, 0)) {
             mDisableNotificationEffects = true;
         }
-        mZenModeHelper.readZenModeFromSetting();
+        mZenModeHelper.initZenMode();
         mInterruptionFilter = mZenModeHelper.getZenModeListenerInterruptionFilter();
 
         mUserProfiles.updateCache(getContext());
@@ -1490,23 +1489,28 @@
         }
 
         @Override
+        public int getZenMode() {
+            return mZenModeHelper.getZenMode();
+        }
+
+        @Override
         public ZenModeConfig getZenModeConfig() {
             enforceSystemOrSystemUIOrVolume("INotificationManager.getZenModeConfig");
             return mZenModeHelper.getConfig();
         }
 
         @Override
-        public boolean setZenModeConfig(ZenModeConfig config) {
+        public boolean setZenModeConfig(ZenModeConfig config, String reason) {
             checkCallerIsSystem();
-            return mZenModeHelper.setConfig(config);
+            return mZenModeHelper.setConfig(config, reason);
         }
 
         @Override
-        public void setZenMode(int mode) throws RemoteException {
+        public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
             enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
             final long identity = Binder.clearCallingIdentity();
             try {
-                mZenModeHelper.setZenMode(mode, "NotificationManager");
+                mZenModeHelper.setManualZenMode(mode, conditionId, reason);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1528,30 +1532,7 @@
         @Override
         public void requestZenModeConditions(IConditionListener callback, int relevance) {
             enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
-            mConditionProviders.requestZenModeConditions(callback, relevance);
-        }
-
-        @Override
-        public void setZenModeCondition(Condition condition) {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.setZenModeCondition");
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                mConditionProviders.setZenModeCondition(condition, "binderCall");
-            } finally {
-                Binder.restoreCallingIdentity(identity);
-            }
-        }
-
-        @Override
-        public void setAutomaticZenModeConditions(Uri[] conditionIds) {
-            enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
-            mConditionProviders.setAutomaticZenModeConditions(conditionIds);
-        }
-
-        @Override
-        public Condition[] getAutomaticZenModeConditions() {
-            enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
-            return mConditionProviders.getAutomaticZenModeConditions();
+            mZenModeHelper.requestZenModeConditions(callback, relevance);
         }
 
         private void enforceSystemOrSystemUIOrVolume(String message) {
@@ -1603,7 +1584,7 @@
         @Override
         public boolean isSystemConditionProviderEnabled(String path) {
             enforceSystemOrSystemUIOrVolume("INotificationManager.isSystemConditionProviderEnabled");
-            return mConditionProviders.isSystemConditionProviderEnabled(path);
+            return mConditionProviders.isSystemProviderEnabled(path);
         }
     };
 
diff --git a/services/core/java/com/android/server/notification/DowntimeCalendar.java b/services/core/java/com/android/server/notification/ScheduleCalendar.java
similarity index 62%
rename from services/core/java/com/android/server/notification/DowntimeCalendar.java
rename to services/core/java/com/android/server/notification/ScheduleCalendar.java
index d14fd40..cea611d 100644
--- a/services/core/java/com/android/server/notification/DowntimeCalendar.java
+++ b/services/core/java/com/android/server/notification/ScheduleCalendar.java
@@ -16,38 +16,36 @@
 
 package com.android.server.notification;
 
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+
 import java.util.Calendar;
 import java.util.Objects;
 import java.util.TimeZone;
 
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
-import android.util.ArraySet;
-
-public class DowntimeCalendar {
-
+public class ScheduleCalendar {
     private final ArraySet<Integer> mDays = new ArraySet<Integer>();
     private final Calendar mCalendar = Calendar.getInstance();
 
-    private DowntimeInfo mInfo;
+    private ScheduleInfo mSchedule;
 
     @Override
     public String toString() {
-        return "DowntimeCalendar[mDays=" + mDays + "]";
+        return "ScheduleCalendar[mDays=" + mDays + "]";
     }
 
-    public void setDowntimeInfo(DowntimeInfo info) {
-        if (Objects.equals(mInfo, info)) return;
-        mInfo = info;
+    public void setSchedule(ScheduleInfo schedule) {
+        if (Objects.equals(mSchedule, schedule)) return;
+        mSchedule = schedule;
         updateDays();
     }
 
-    public long nextDowntimeStart(long time) {
-        if (mInfo == null || mDays.size() == 0) return Long.MAX_VALUE;
-        final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
+    public long nextScheduleStart(long time) {
+        if (mSchedule == null || mDays.size() == 0) return Long.MAX_VALUE;
+        final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
         for (int i = 0; i < Calendar.SATURDAY; i++) {
             final long t = addDays(start, i);
-            if (t > time && isInDowntime(t)) {
+            if (t > time && isInSchedule(t)) {
                 return t;
             }
         }
@@ -58,7 +56,14 @@
         mCalendar.setTimeZone(tz);
     }
 
-    public long getNextTime(long now, int hr, int min) {
+    public long getNextChangeTime(long now) {
+        if (mSchedule == null) return 0;
+        final long nextStart = getNextTime(now, mSchedule.startHour, mSchedule.startMinute);
+        final long nextEnd = getNextTime(now, mSchedule.endHour, mSchedule.endMinute);
+        return Math.min(nextStart, nextEnd);
+    }
+
+    private long getNextTime(long now, int hr, int min) {
         final long time = getTime(now, hr, min);
         return time <= now ? addDays(time, 1) : time;
     }
@@ -72,17 +77,17 @@
         return mCalendar.getTimeInMillis();
     }
 
-    public boolean isInDowntime(long time) {
-        if (mInfo == null || mDays.size() == 0) return false;
-        final long start = getTime(time, mInfo.startHour, mInfo.startMinute);
-        long end = getTime(time, mInfo.endHour, mInfo.endMinute);
+    public boolean isInSchedule(long time) {
+        if (mSchedule == null || mDays.size() == 0) return false;
+        final long start = getTime(time, mSchedule.startHour, mSchedule.startMinute);
+        long end = getTime(time, mSchedule.endHour, mSchedule.endMinute);
         if (end <= start) {
             end = addDays(end, 1);
         }
-        return isInDowntime(-1, time, start, end) || isInDowntime(0, time, start, end);
+        return isInSchedule(-1, time, start, end) || isInSchedule(0, time, start, end);
     }
 
-    private boolean isInDowntime(int daysOffset, long time, long start, long end) {
+    private boolean isInSchedule(int daysOffset, long time, long start, long end) {
         final int n = Calendar.SATURDAY;
         final int day = ((getDayOfWeek(time) - 1) + (daysOffset % n) + n) % n + 1;
         start = addDays(start, daysOffset);
@@ -97,10 +102,9 @@
 
     private void updateDays() {
         mDays.clear();
-        if (mInfo != null) {
-            final int[] days = ZenModeConfig.tryParseDays(mInfo.mode);
-            for (int i = 0; days != null && i < days.length; i++) {
-                mDays.add(days[i]);
+        if (mSchedule != null && mSchedule.days != null) {
+            for (int i = 0; i < mSchedule.days.length; i++) {
+                mDays.add(mSchedule.days[i]);
             }
         }
     }
@@ -110,4 +114,4 @@
         mCalendar.add(Calendar.DATE, days);
         return mCalendar.getTimeInMillis();
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
new file mode 100644
index 0000000..c997e45
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 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.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Built-in zen condition provider for daily scheduled time-based conditions.
+ */
+public class ScheduleConditionProvider extends SystemConditionProviderService {
+    private static final String TAG = "ScheduleConditions";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    public static final ComponentName COMPONENT =
+            new ComponentName("android", ScheduleConditionProvider.class.getName());
+    private static final String NOT_SHOWN = "...";
+    private static final String ACTION_EVALUATE = TAG + ".EVALUATE";
+    private static final int REQUEST_CODE_EVALUATE = 1;
+    private static final String EXTRA_TIME = "time";
+
+    private final Context mContext = this;
+    private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
+
+    private boolean mConnected;
+    private boolean mRegistered;
+
+    public ScheduleConditionProvider() {
+        if (DEBUG) Slog.d(TAG, "new ScheduleConditionProvider()");
+    }
+
+    @Override
+    public ComponentName getComponent() {
+        return COMPONENT;
+    }
+
+    @Override
+    public boolean isValidConditionid(Uri id) {
+        return ZenModeConfig.isValidScheduleConditionId(id);
+    }
+
+    @Override
+    public void dump(PrintWriter pw, DumpFilter filter) {
+        pw.println("    ScheduleConditionProvider:");
+        pw.print("      mConnected="); pw.println(mConnected);
+        pw.print("      mRegistered="); pw.println(mRegistered);
+        pw.println("      mSubscriptions=");
+        final long now = System.currentTimeMillis();
+        for (Uri conditionId : mSubscriptions) {
+            pw.print("        ");
+            pw.print(meetsSchedule(conditionId, now) ? "* " : "  ");
+            pw.println(conditionId);
+        }
+    }
+
+    @Override
+    public void onConnected() {
+        if (DEBUG) Slog.d(TAG, "onConnected");
+        mConnected = true;
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (DEBUG) Slog.d(TAG, "onDestroy");
+        mConnected = false;
+    }
+
+    @Override
+    public void onRequestConditions(int relevance) {
+        if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
+        // does not advertise conditions
+    }
+
+    @Override
+    public void onSubscribe(Uri conditionId) {
+        if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
+        if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
+            notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
+            return;
+        }
+        mSubscriptions.add(conditionId);
+        evaluateSubscriptions();
+    }
+
+    @Override
+    public void onUnsubscribe(Uri conditionId) {
+        if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
+        mSubscriptions.remove(conditionId);
+        evaluateSubscriptions();
+    }
+
+    @Override
+    public void attachBase(Context base) {
+        attachBaseContext(base);
+    }
+
+    @Override
+    public IConditionProvider asInterface() {
+        return (IConditionProvider) onBind(null);
+    }
+
+    private void evaluateSubscriptions() {
+        setRegistered(!mSubscriptions.isEmpty());
+        final long now = System.currentTimeMillis();
+        long nextAlarmTime = 0;
+        for (Uri conditionId : mSubscriptions) {
+            final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+            if (cal != null && cal.isInSchedule(now)) {
+                notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
+            } else {
+                notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
+            }
+            if (cal != null) {
+                final long nextChangeTime = cal.getNextChangeTime(now);
+                if (nextChangeTime > 0 && nextChangeTime > now) {
+                    if (nextAlarmTime == 0 || nextChangeTime < nextAlarmTime) {
+                        nextAlarmTime = nextChangeTime;
+                    }
+                }
+            }
+        }
+        updateAlarm(now, nextAlarmTime);
+    }
+
+    private void updateAlarm(long now, long time) {
+        final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+                REQUEST_CODE_EVALUATE,
+                new Intent(ACTION_EVALUATE)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                        .putExtra(EXTRA_TIME, time),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        alarms.cancel(pendingIntent);
+        if (time > now) {
+            if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
+                    ts(time), formatDuration(time - now), ts(now)));
+            alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
+        } else {
+            if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
+        }
+    }
+
+    private static String ts(long time) {
+        return new Date(time) + " (" + time + ")";
+    }
+
+    private static String formatDuration(long millis) {
+        final StringBuilder sb = new StringBuilder();
+        TimeUtils.formatDuration(millis, sb);
+        return sb.toString();
+    }
+
+    private static boolean meetsSchedule(Uri conditionId, long time) {
+        final ScheduleCalendar cal = toScheduleCalendar(conditionId);
+        return cal != null && cal.isInSchedule(time);
+    }
+
+    private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
+        final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
+        if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
+        final ScheduleCalendar sc = new ScheduleCalendar();
+        sc.setSchedule(schedule);
+        sc.setTimeZone(TimeZone.getDefault());
+        return sc;
+    }
+
+    private void setRegistered(boolean registered) {
+        if (mRegistered == registered) return;
+        if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
+        mRegistered = registered;
+        if (mRegistered) {
+            final IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_TIME_CHANGED);
+            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+            filter.addAction(ACTION_EVALUATE);
+            registerReceiver(mReceiver, filter);
+        } else {
+            unregisterReceiver(mReceiver);
+        }
+    }
+
+    private void notifyCondition(Uri conditionId, int state, String reason) {
+        if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state)
+                + " reason=" + reason);
+        notifyCondition(createCondition(conditionId, state));
+    }
+
+    private Condition createCondition(Uri id, int state) {
+        final String summary = NOT_SHOWN;
+        final String line1 = NOT_SHOWN;
+        final String line2 = NOT_SHOWN;
+        return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
+    }
+
+    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
+            evaluateSubscriptions();
+        }
+    };
+
+}
diff --git a/services/core/java/com/android/server/notification/SystemConditionProviderService.java b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
new file mode 100644
index 0000000..a217623
--- /dev/null
+++ b/services/core/java/com/android/server/notification/SystemConditionProviderService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 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.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.ConditionProviderService;
+import android.service.notification.IConditionProvider;
+
+import com.android.server.notification.NotificationManagerService.DumpFilter;
+
+import java.io.PrintWriter;
+
+public abstract class SystemConditionProviderService extends ConditionProviderService {
+
+    abstract public void dump(PrintWriter pw, DumpFilter filter);
+    abstract public void attachBase(Context context);
+    abstract public IConditionProvider asInterface();
+    abstract public ComponentName getComponent();
+    abstract public boolean isValidConditionid(Uri id);
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
new file mode 100644
index 0000000..67a2a54
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2015, 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.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.service.notification.IConditionListener;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+public class ZenModeConditions implements ConditionProviders.Callback {
+    private static final String TAG = ZenModeHelper.TAG;
+    private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+    private final ZenModeHelper mHelper;
+    private final ConditionProviders mConditionProviders;
+    private final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
+
+    private CountdownConditionProvider mCountdown;
+    private ScheduleConditionProvider mSchedule;
+
+    public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
+        mHelper = helper;
+        mConditionProviders = conditionProviders;
+        if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
+            mCountdown = new CountdownConditionProvider();
+            mConditionProviders.addSystemProvider(mCountdown);
+        }
+        if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
+            mSchedule = new ScheduleConditionProvider();
+            mConditionProviders.addSystemProvider(mSchedule);
+        }
+        mConditionProviders.setCallback(this);
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
+    }
+
+    public void requestConditions(IConditionListener callback, int relevance) {
+        mConditionProviders.requestConditions(callback, relevance);
+    }
+
+    public void evaluateConfig(ZenModeConfig config) {
+        if (config == null) return;
+        if (config.manualRule != null && !config.manualRule.isTrueOrUnknown()) {
+            if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
+            config.manualRule = null;
+        }
+        final ArraySet<Uri> current = new ArraySet<>();
+        evaluateRule(config.manualRule, current);
+        for (ZenRule automaticRule : config.automaticRules.values()) {
+            evaluateRule(automaticRule, current);
+        }
+        final int N = mSubscriptions.size();
+        for (int i = N - 1; i >= 0; i--) {
+            final Uri id = mSubscriptions.keyAt(i);
+            final ComponentName component = mSubscriptions.valueAt(i);
+            if (!current.contains(id)) {
+                mConditionProviders.unsubscribeIfNecessary(component, id);
+                mSubscriptions.removeAt(i);
+            }
+        }
+    }
+
+    private void evaluateRule(ZenRule rule, ArraySet<Uri> current) {
+        if (rule == null || rule.conditionId == null) return;
+        final Uri id = rule.conditionId;
+        for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
+            if (sp.isValidConditionid(id)) {
+                mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
+                rule.component = sp.getComponent();
+            }
+        }
+        current.add(id);
+        if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
+            mSubscriptions.put(rule.conditionId, rule.component);
+        }
+    }
+
+    @Override
+    public void onBootComplete() {
+        // noop
+    }
+
+    @Override
+    public void onUserSwitched() {
+        // noop
+    }
+
+    @Override
+    public void onConditionChanged(Uri id, Condition condition) {
+        if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
+        ZenModeConfig config = mHelper.getConfig();
+        if (config == null) return;
+        config = config.copy();
+        boolean updated = updateCondition(id, condition, config.manualRule);
+        for (ZenRule automaticRule : config.automaticRules.values()) {
+            updated |= updateCondition(id, condition, automaticRule);
+            updated |= updateSnoozing(automaticRule);
+        }
+        if (updated) {
+            mHelper.setConfig(config, "conditionChanged");
+        }
+    }
+
+    private boolean updateSnoozing(ZenRule rule) {
+        if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
+            rule.snoozing = false;
+            if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean updateCondition(Uri id, Condition condition, ZenRule rule) {
+        if (id == null || rule == null || rule.conditionId == null) return false;
+        if (!rule.conditionId.equals(id)) return false;
+        if (Objects.equals(condition, rule.condition)) return false;
+        rule.condition = condition;
+        return true;
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
new file mode 100644
index 0000000..32fd01a
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2015, 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.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
+import android.service.notification.ZenModeConfig;
+import android.telecom.TelecomManager;
+import android.util.Slog;
+
+import java.util.Objects;
+
+public class ZenModeFiltering {
+    private static final String TAG = ZenModeHelper.TAG;
+    private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+    private final Context mContext;
+
+    private ComponentName mDefaultPhoneApp;
+
+    public ZenModeFiltering(Context context) {
+        mContext = context;
+    }
+
+    public ComponentName getDefaultPhoneApp() {
+        return mDefaultPhoneApp;
+    }
+
+    /**
+     * @param extras extras of the notification with EXTRA_PEOPLE populated
+     * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
+     * @param timeoutAffinity affinity to return when the timeout specified via
+     *                        <code>contactsTimeoutMs</code> is hit
+     */
+    public static boolean matchesCallFilter(int zen, ZenModeConfig config, UserHandle userHandle,
+            Bundle extras, ValidateNotificationPeople validator, int contactsTimeoutMs,
+            float timeoutAffinity) {
+        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
+        if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
+        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+            if (!config.allowCalls) return false; // no calls get through
+            if (validator != null) {
+                final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+                        contactsTimeoutMs, timeoutAffinity);
+                return audienceMatches(config, contactAffinity);
+            }
+        }
+        return true;
+    }
+
+    public boolean shouldIntercept(int zen, ZenModeConfig config, NotificationRecord record) {
+        if (isSystem(record)) {
+            return false;
+        }
+        switch (zen) {
+            case Global.ZEN_MODE_NO_INTERRUPTIONS:
+                // #notevenalarms
+                ZenLog.traceIntercepted(record, "none");
+                return true;
+            case Global.ZEN_MODE_ALARMS:
+                if (isAlarm(record)) {
+                    // Alarms only
+                    return false;
+                }
+                ZenLog.traceIntercepted(record, "alarmsOnly");
+                return true;
+            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                if (isAlarm(record)) {
+                    // Alarms are always priority
+                    return false;
+                }
+                // allow user-prioritized packages through in priority mode
+                if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
+                    ZenLog.traceNotIntercepted(record, "priorityApp");
+                    return false;
+                }
+                if (isCall(record)) {
+                    if (!config.allowCalls) {
+                        ZenLog.traceIntercepted(record, "!allowCalls");
+                        return true;
+                    }
+                    return shouldInterceptAudience(config, record);
+                }
+                if (isMessage(record)) {
+                    if (!config.allowMessages) {
+                        ZenLog.traceIntercepted(record, "!allowMessages");
+                        return true;
+                    }
+                    return shouldInterceptAudience(config, record);
+                }
+                if (isEvent(record)) {
+                    if (!config.allowEvents) {
+                        ZenLog.traceIntercepted(record, "!allowEvents");
+                        return true;
+                    }
+                    return false;
+                }
+                if (isReminder(record)) {
+                    if (!config.allowReminders) {
+                        ZenLog.traceIntercepted(record, "!allowReminders");
+                        return true;
+                    }
+                    return false;
+                }
+                ZenLog.traceIntercepted(record, "!priority");
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean shouldInterceptAudience(ZenModeConfig config,
+            NotificationRecord record) {
+        if (!audienceMatches(config, record.getContactAffinity())) {
+            ZenLog.traceIntercepted(record, "!audienceMatches");
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isSystem(NotificationRecord record) {
+        return record.isCategory(Notification.CATEGORY_SYSTEM);
+    }
+
+    private static boolean isAlarm(NotificationRecord record) {
+        return record.isCategory(Notification.CATEGORY_ALARM)
+                || record.isAudioStream(AudioManager.STREAM_ALARM)
+                || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
+    }
+
+    private static boolean isEvent(NotificationRecord record) {
+        return record.isCategory(Notification.CATEGORY_EVENT);
+    }
+
+    private static boolean isReminder(NotificationRecord record) {
+        return record.isCategory(Notification.CATEGORY_REMINDER);
+    }
+
+    public boolean isCall(NotificationRecord record) {
+        return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
+                || record.isCategory(Notification.CATEGORY_CALL));
+    }
+
+    private boolean isDefaultPhoneApp(String pkg) {
+        if (mDefaultPhoneApp == null) {
+            final TelecomManager telecomm =
+                    (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+            mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
+            if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
+        }
+        return pkg != null && mDefaultPhoneApp != null
+                && pkg.equals(mDefaultPhoneApp.getPackageName());
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean isDefaultMessagingApp(NotificationRecord record) {
+        final int userId = record.getUserId();
+        if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
+        final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
+                Secure.SMS_DEFAULT_APPLICATION, userId);
+        return Objects.equals(defaultApp, record.sbn.getPackageName());
+    }
+
+    private boolean isMessage(NotificationRecord record) {
+        return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
+    }
+
+    private static boolean audienceMatches(ZenModeConfig config, float contactAffinity) {
+        switch (config.allowFrom) {
+            case ZenModeConfig.SOURCE_ANYONE:
+                return true;
+            case ZenModeConfig.SOURCE_CONTACT:
+                return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
+            case ZenModeConfig.SOURCE_STAR:
+                return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+            default:
+                Slog.w(TAG, "Encountered unknown source: " + config.allowFrom);
+                return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1775df2..77f78fe 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -21,14 +21,12 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
 import android.app.AppOpsManager;
-import android.app.Notification;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
-import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.VolumePolicy;
@@ -39,12 +37,13 @@
 import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings.Global;
-import android.provider.Settings.Secure;
+import android.service.notification.IConditionListener;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
-import android.telecom.TelecomManager;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.ArraySet;
 import android.util.Log;
-import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.server.LocalServices;
@@ -58,14 +57,13 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 
 /**
  * NotificationManagerService helper for functionality related to zen mode.
  */
-public class ZenModeHelper implements AudioManagerInternal.RingerModeDelegate {
-    private static final String TAG = "ZenModeHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+public class ZenModeHelper {
+    static final String TAG = "ZenModeHelper";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
     private final H mHandler;
@@ -73,38 +71,46 @@
     private final AppOpsManager mAppOps;
     private final ZenModeConfig mDefaultConfig;
     private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+    private final ZenModeFiltering mFiltering;
+    private final RingerModeDelegate mRingerModeDelegate = new RingerModeDelegate();
+    private final ZenModeConditions mConditions;
 
-    private ComponentName mDefaultPhoneApp;
     private int mZenMode;
     private ZenModeConfig mConfig;
     private AudioManagerInternal mAudioManager;
     private int mPreviousRingerMode = -1;
     private boolean mEffectsSuppressed;
 
-    public ZenModeHelper(Context context, Looper looper) {
+    public ZenModeHelper(Context context, Looper looper, ConditionProviders conditionProviders) {
         mContext = context;
         mHandler = new H(looper);
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mDefaultConfig = readDefaultConfig(context.getResources());
+        appendDefaultScheduleRules(mDefaultConfig);
         mConfig = mDefaultConfig;
         mSettingsObserver = new SettingsObserver(mHandler);
         mSettingsObserver.observe();
+        mFiltering = new ZenModeFiltering(mContext);
+        mConditions = new ZenModeConditions(this, conditionProviders);
     }
 
-    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();
+    @Override
+    public String toString() {
+        return TAG;
+    }
+
+    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
+            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
+        return ZenModeFiltering.matchesCallFilter(mZenMode, mConfig, userHandle, extras, validator,
+                contactsTimeoutMs, timeoutAffinity);
+    }
+
+    public boolean isCall(NotificationRecord record) {
+        return mFiltering.isCall(record);
+    }
+
+    public boolean shouldIntercept(NotificationRecord record) {
+        return mFiltering.shouldIntercept(mZenMode, mConfig, record);
     }
 
     public void addCallback(Callback callback) {
@@ -115,48 +121,32 @@
         mCallbacks.remove(callback);
     }
 
+    public void initZenMode() {
+        if (DEBUG) Log.d(TAG, "initZenMode");
+        evaluateZenMode("init", true /*setRingerMode*/);
+    }
+
     public void onSystemReady() {
+        if (DEBUG) Log.d(TAG, "onSystemReady");
         mAudioManager = LocalServices.getService(AudioManagerInternal.class);
         if (mAudioManager != null) {
-            mAudioManager.setRingerModeDelegate(this);
+            mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
         }
     }
 
+    public void requestZenModeConditions(IConditionListener callback, int relevance) {
+        mConditions.requestConditions(callback, relevance);
+    }
+
     public int getZenModeListenerInterruptionFilter() {
-        switch (mZenMode) {
-            case Global.ZEN_MODE_OFF:
-                return NotificationListenerService.INTERRUPTION_FILTER_ALL;
-            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
-            case Global.ZEN_MODE_ALARMS:
-                return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
-            case Global.ZEN_MODE_NO_INTERRUPTIONS:
-                return NotificationListenerService.INTERRUPTION_FILTER_NONE;
-            default:
-                return 0;
-        }
-    }
-
-    private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
-            int defValue) {
-        switch (listenerInterruptionFilter) {
-            case NotificationListenerService.INTERRUPTION_FILTER_ALL:
-                return Global.ZEN_MODE_OFF;
-            case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
-                return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-            case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
-                return Global.ZEN_MODE_ALARMS;
-            case NotificationListenerService.INTERRUPTION_FILTER_NONE:
-                return Global.ZEN_MODE_NO_INTERRUPTIONS;
-            default:
-                return defValue;
-        }
+        return getZenModeListenerInterruptionFilter(mZenMode);
     }
 
     public void requestFromListener(ComponentName name, int interruptionFilter) {
         final int newZen = zenModeFromListenerInterruptionFilter(interruptionFilter, -1);
         if (newZen != -1) {
-            setZenMode(newZen, "listener:" + (name != null ? name.flattenToShortString() : null));
+            setManualZenMode(newZen, null,
+                    "listener:" + (name != null ? name.flattenToShortString() : null));
         }
     }
 
@@ -166,100 +156,144 @@
         applyRestrictions();
     }
 
-    public boolean shouldIntercept(NotificationRecord record) {
-        if (isSystem(record)) {
-            return false;
-        }
-        switch (mZenMode) {
-            case Global.ZEN_MODE_NO_INTERRUPTIONS:
-                // #notevenalarms
-                ZenLog.traceIntercepted(record, "none");
-                return true;
-            case Global.ZEN_MODE_ALARMS:
-                if (isAlarm(record)) {
-                    // Alarms only
-                    return false;
-                }
-                ZenLog.traceIntercepted(record, "alarmsOnly");
-                return true;
-            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
-                if (isAlarm(record)) {
-                    // Alarms are always priority
-                    return false;
-                }
-                // allow user-prioritized packages through in priority mode
-                if (record.getPackagePriority() == Notification.PRIORITY_MAX) {
-                    ZenLog.traceNotIntercepted(record, "priorityApp");
-                    return false;
-                }
-                if (isCall(record)) {
-                    if (!mConfig.allowCalls) {
-                        ZenLog.traceIntercepted(record, "!allowCalls");
-                        return true;
-                    }
-                    return shouldInterceptAudience(record);
-                }
-                if (isMessage(record)) {
-                    if (!mConfig.allowMessages) {
-                        ZenLog.traceIntercepted(record, "!allowMessages");
-                        return true;
-                    }
-                    return shouldInterceptAudience(record);
-                }
-                if (isEvent(record)) {
-                    if (!mConfig.allowEvents) {
-                        ZenLog.traceIntercepted(record, "!allowEvents");
-                        return true;
-                    }
-                    return false;
-                }
-                if (isReminder(record)) {
-                    if (!mConfig.allowReminders) {
-                        ZenLog.traceIntercepted(record, "!allowReminders");
-                        return true;
-                    }
-                    return false;
-                }
-                ZenLog.traceIntercepted(record, "!priority");
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private boolean shouldInterceptAudience(NotificationRecord record) {
-        if (!audienceMatches(record.getContactAffinity())) {
-            ZenLog.traceIntercepted(record, "!audienceMatches");
-            return true;
-        }
-        return false;
-    }
-
     public int getZenMode() {
         return mZenMode;
     }
 
-    public void setZenMode(int zenMode, String reason) {
-        setZenMode(zenMode, reason, true);
+    public void setManualZenMode(int zenMode, Uri conditionId, String reason) {
+        setManualZenMode(zenMode, conditionId, reason, true /*setRingerMode*/);
     }
 
-    private void setZenMode(int zenMode, String reason, boolean setRingerMode) {
-        ZenLog.traceSetZenMode(zenMode, reason);
-        if (mZenMode == zenMode) return;
-        ZenLog.traceUpdateZenMode(mZenMode, zenMode);
-        mZenMode = zenMode;
-        Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, mZenMode);
+    private void setManualZenMode(int zenMode, Uri conditionId, String reason,
+            boolean setRingerMode) {
+        if (mConfig == null) return;
+        if (!Global.isValidZenMode(zenMode)) return;
+        if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+                + " conditionId=" + conditionId + " reason=" + reason
+                + " setRingerMode=" + setRingerMode);
+        final ZenModeConfig newConfig = mConfig.copy();
+        if (zenMode == Global.ZEN_MODE_OFF) {
+            newConfig.manualRule = null;
+            for (ZenRule automaticRule : newConfig.automaticRules.values()) {
+                if (automaticRule.isTrueOrUnknown()) {
+                    automaticRule.snoozing = true;
+                }
+            }
+        } else {
+            final ZenRule newRule = new ZenRule();
+            newRule.enabled = true;
+            newRule.zenMode = zenMode;
+            newRule.conditionId = conditionId;
+            newConfig.manualRule = newRule;
+        }
+        setConfig(newConfig, reason, setRingerMode);
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mZenMode=");
+        pw.println(Global.zenModeToString(mZenMode));
+        dump(pw, prefix, "mConfig", mConfig);
+        dump(pw, prefix, "mDefaultConfig", mDefaultConfig);
+        pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
+        pw.print(prefix); pw.print("DefaultPhoneApp="); pw.println(mFiltering.getDefaultPhoneApp());
+        pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+        mConditions.dump(pw, prefix);
+    }
+
+    private static void dump(PrintWriter pw, String prefix, String var, ZenModeConfig config) {
+        pw.print(prefix); pw.print(var); pw.print('=');
+        if (config == null) {
+            pw.println(config);
+            return;
+        }
+        pw.printf("allow(calls=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+                config.allowCalls, config.allowEvents, config.allowFrom, config.allowMessages,
+                config.allowReminders);
+        pw.print(prefix); pw.print("  manualRule="); pw.println(config.manualRule);
+        if (config.automaticRules.isEmpty()) return;
+        final int N = config.automaticRules.size();
+        for (int i = 0; i < N; i++) {
+            pw.print(prefix); pw.print(i == 0 ? "  automaticRules=" : "                 ");
+            pw.println(config.automaticRules.valueAt(i));
+        }
+    }
+
+    public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException {
+        final ZenModeConfig config = ZenModeConfig.readXml(parser, mConfigMigration);
+        if (config != null) {
+            if (DEBUG) Log.d(TAG, "readXml");
+            setConfig(config, "readXml");
+        }
+    }
+
+    public void writeXml(XmlSerializer out) throws IOException {
+        mConfig.writeXml(out);
+    }
+
+    public ZenModeConfig getConfig() {
+        return mConfig;
+    }
+
+    public boolean setConfig(ZenModeConfig config, String reason) {
+        return setConfig(config, reason, true /*setRingerMode*/);
+    }
+
+    private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+        if (config == null || !config.isValid()) {
+            Log.w(TAG, "Invalid config in setConfig; " + config);
+            return false;
+        }
+        mConditions.evaluateConfig(config);  // may modify config
+        if (config.equals(mConfig)) return true;
+        if (DEBUG) Log.d(TAG, "setConfig reason=" + reason);
+        ZenLog.traceConfig(mConfig, config);
+        mConfig = config;
+        dispatchOnConfigChanged();
+        final String val = Integer.toString(mConfig.hashCode());
+        Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
+        if (!evaluateZenMode(reason, setRingerMode)) {
+            applyRestrictions();  // evaluateZenMode will also apply restrictions if changed
+        }
+        return true;
+    }
+
+    private int getZenModeSetting() {
+        return Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, Global.ZEN_MODE_OFF);
+    }
+
+    private void setZenModeSetting(int zen) {
+        Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+    }
+
+    private boolean evaluateZenMode(String reason, boolean setRingerMode) {
+        if (DEBUG) Log.d(TAG, "evaluateZenMode");
+        final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
+        final int zen = computeZenMode(automaticRules);
+        if (zen == mZenMode) return false;
+        ZenLog.traceSetZenMode(zen, reason);
+        mZenMode = zen;
+        setZenModeSetting(mZenMode);
         if (setRingerMode) {
             applyZenToRingerMode();
         }
         applyRestrictions();
         mHandler.postDispatchOnZenModeChanged();
+        return true;
     }
 
-    public void readZenModeFromSetting() {
-        final int newMode = Global.getInt(mContext.getContentResolver(),
-                Global.ZEN_MODE, Global.ZEN_MODE_OFF);
-        setZenMode(newMode, "setting");
+    private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+        if (mConfig == null) return Global.ZEN_MODE_OFF;
+        if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+        int zen = Global.ZEN_MODE_OFF;
+        for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+            if (automaticRule.enabled && !automaticRule.snoozing
+                    && automaticRule.isTrueOrUnknown()) {
+                if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+                    zen = automaticRule.zenMode;
+                }
+            }
+        }
+        return zen;
     }
 
     private void applyRestrictions() {
@@ -288,43 +322,6 @@
                 exceptionPackages);
     }
 
-    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);
-        pw.print(prefix); pw.print("mPreviousRingerMode="); pw.println(mPreviousRingerMode);
-        pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
-        pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
-    }
-
-    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;
-        ZenLog.traceConfig(mConfig, config);
-        mConfig = config;
-        dispatchOnConfigChanged();
-        final String val = Integer.toString(mConfig.hashCode());
-        Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
-        applyRestrictions();
-        return true;
-    }
-
     private void applyZenToRingerMode() {
         if (mAudioManager == null) return;
         // force the ringer mode into compliance
@@ -352,81 +349,6 @@
         }
     }
 
-    @Override  // RingerModeDelegate
-    public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
-            int ringerModeExternal, VolumePolicy policy) {
-        final boolean isChange = ringerModeOld != ringerModeNew;
-
-        int ringerModeExternalOut = ringerModeNew;
-
-        int newZen = -1;
-        switch (ringerModeNew) {
-            case AudioManager.RINGER_MODE_SILENT:
-                if (isChange && policy.doNotDisturbWhenSilent) {
-                    if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
-                            && mZenMode != Global.ZEN_MODE_ALARMS) {
-                        newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
-                    }
-                }
-                break;
-            case AudioManager.RINGER_MODE_VIBRATE:
-            case AudioManager.RINGER_MODE_NORMAL:
-                if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
-                        && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
-                                || mZenMode == Global.ZEN_MODE_ALARMS)) {
-                    newZen = Global.ZEN_MODE_OFF;
-                } else if (mZenMode != Global.ZEN_MODE_OFF) {
-                    ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
-                }
-                break;
-        }
-        if (newZen != -1) {
-            setZenMode(newZen, "ringerModeInternal", false /*setRingerMode*/);
-        }
-
-        if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
-            ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
-                    ringerModeExternal, ringerModeExternalOut);
-        }
-        return ringerModeExternalOut;
-    }
-
-    @Override  // RingerModeDelegate
-    public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
-            int ringerModeInternal, VolumePolicy policy) {
-        int ringerModeInternalOut = ringerModeNew;
-        final boolean isChange = ringerModeOld != ringerModeNew;
-        final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
-
-        int newZen = -1;
-        switch (ringerModeNew) {
-            case AudioManager.RINGER_MODE_SILENT:
-                if (isChange) {
-                    if (mZenMode == Global.ZEN_MODE_OFF) {
-                        newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-                    }
-                    ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
-                            : AudioManager.RINGER_MODE_NORMAL;
-                } else {
-                    ringerModeInternalOut = ringerModeInternal;
-                }
-                break;
-            case AudioManager.RINGER_MODE_VIBRATE:
-            case AudioManager.RINGER_MODE_NORMAL:
-                if (mZenMode != Global.ZEN_MODE_OFF) {
-                    newZen = Global.ZEN_MODE_OFF;
-                }
-                break;
-        }
-        if (newZen != -1) {
-            setZenMode(newZen, "ringerModeExternal", false /*setRingerMode*/);
-        }
-
-        ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, ringerModeInternal,
-                ringerModeInternalOut);
-        return ringerModeInternalOut;
-    }
-
     private void dispatchOnConfigChanged() {
         for (Callback callback : mCallbacks) {
             callback.onConfigChanged();
@@ -439,94 +361,210 @@
         }
     }
 
-    private static boolean isSystem(NotificationRecord record) {
-        return record.isCategory(Notification.CATEGORY_SYSTEM);
-    }
-
-    private static boolean isAlarm(NotificationRecord record) {
-        return record.isCategory(Notification.CATEGORY_ALARM)
-                || record.isAudioStream(AudioManager.STREAM_ALARM)
-                || record.isAudioAttributesUsage(AudioAttributes.USAGE_ALARM);
-    }
-
-    private static boolean isEvent(NotificationRecord record) {
-        return record.isCategory(Notification.CATEGORY_EVENT);
-    }
-
-    private static boolean isReminder(NotificationRecord record) {
-        return record.isCategory(Notification.CATEGORY_REMINDER);
-    }
-
-    public boolean isCall(NotificationRecord record) {
-        return record != null && (isDefaultPhoneApp(record.sbn.getPackageName())
-                || record.isCategory(Notification.CATEGORY_CALL));
-    }
-
-    private boolean isDefaultPhoneApp(String pkg) {
-        if (mDefaultPhoneApp == null) {
-            final TelecomManager telecomm =
-                    (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
-            mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultPhoneApp() : null;
-            if (DEBUG) Slog.d(TAG, "Default phone app: " + mDefaultPhoneApp);
-        }
-        return pkg != null && mDefaultPhoneApp != null
-                && pkg.equals(mDefaultPhoneApp.getPackageName());
-    }
-
-    private boolean isDefaultMessagingApp(NotificationRecord record) {
-        final int userId = record.getUserId();
-        if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
-        final String defaultApp = Secure.getStringForUser(mContext.getContentResolver(),
-                Secure.SMS_DEFAULT_APPLICATION, userId);
-        return Objects.equals(defaultApp, record.sbn.getPackageName());
-    }
-
-    private boolean isMessage(NotificationRecord record) {
-        return record.isCategory(Notification.CATEGORY_MESSAGE) || isDefaultMessagingApp(record);
-    }
-
-    /**
-     * @param extras extras of the notification with EXTRA_PEOPLE populated
-     * @param contactsTimeoutMs timeout in milliseconds to wait for contacts response
-     * @param timeoutAffinity affinity to return when the timeout specified via
-     *                        <code>contactsTimeoutMs</code> is hit
-     */
-    public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
-            ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
-        final int zen = mZenMode;
-        if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) return false; // nothing gets through
-        if (zen == Global.ZEN_MODE_ALARMS) return false; // not an alarm
-        if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
-            if (!mConfig.allowCalls) return false; // no calls get through
-            if (validator != null) {
-                final float contactAffinity = validator.getContactAffinity(userHandle, extras,
-                        contactsTimeoutMs, timeoutAffinity);
-                return audienceMatches(contactAffinity);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public String toString() {
-        return TAG;
-    }
-
-    private boolean audienceMatches(float contactAffinity) {
-        switch (mConfig.allowFrom) {
-            case ZenModeConfig.SOURCE_ANYONE:
-                return true;
-            case ZenModeConfig.SOURCE_CONTACT:
-                return contactAffinity >= ValidateNotificationPeople.VALID_CONTACT;
-            case ZenModeConfig.SOURCE_STAR:
-                return contactAffinity >= ValidateNotificationPeople.STARRED_CONTACT;
+    private static int getZenModeListenerInterruptionFilter(int zen) {
+        switch (zen) {
+            case Global.ZEN_MODE_OFF:
+                return NotificationListenerService.INTERRUPTION_FILTER_ALL;
+            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                return NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
+            case Global.ZEN_MODE_ALARMS:
+                return NotificationListenerService.INTERRUPTION_FILTER_ALARMS;
+            case Global.ZEN_MODE_NO_INTERRUPTIONS:
+                return NotificationListenerService.INTERRUPTION_FILTER_NONE;
             default:
-                Slog.w(TAG, "Encountered unknown source: " + mConfig.allowFrom);
-                return true;
+                return 0;
         }
     }
 
-    private class SettingsObserver extends ContentObserver {
+    private static int zenModeFromListenerInterruptionFilter(int listenerInterruptionFilter,
+            int defValue) {
+        switch (listenerInterruptionFilter) {
+            case NotificationListenerService.INTERRUPTION_FILTER_ALL:
+                return Global.ZEN_MODE_OFF;
+            case NotificationListenerService.INTERRUPTION_FILTER_PRIORITY:
+                return Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+            case NotificationListenerService.INTERRUPTION_FILTER_ALARMS:
+                return Global.ZEN_MODE_ALARMS;
+            case NotificationListenerService.INTERRUPTION_FILTER_NONE:
+                return Global.ZEN_MODE_NO_INTERRUPTIONS;
+            default:
+                return defValue;
+        }
+    }
+
+    private 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, mConfigMigration);
+                if (config != null) return config;
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Error reading default zen mode config from resource", e);
+        } finally {
+            IoUtils.closeQuietly(parser);
+        }
+        return new ZenModeConfig();
+    }
+
+    private void appendDefaultScheduleRules(ZenModeConfig config) {
+        if (config == null) return;
+
+        final ScheduleInfo weeknights = new ScheduleInfo();
+        weeknights.days = ZenModeConfig.WEEKNIGHT_DAYS;
+        weeknights.startHour = 22;
+        weeknights.endHour = 7;
+        final ZenRule rule1 = new ZenRule();
+        rule1.enabled = false;
+        rule1.name = mContext.getResources()
+                .getString(R.string.zen_mode_default_weeknights_name);
+        rule1.conditionId = ZenModeConfig.toScheduleConditionId(weeknights);
+        rule1.zenMode = Global.ZEN_MODE_ALARMS;
+        config.automaticRules.put(config.newRuleId(), rule1);
+
+        final ScheduleInfo weekends = new ScheduleInfo();
+        weekends.days = ZenModeConfig.WEEKEND_DAYS;
+        weekends.startHour = 23;
+        weekends.startMinute = 30;
+        weekends.endHour = 10;
+        final ZenRule rule2 = new ZenRule();
+        rule2.enabled = false;
+        rule2.name = mContext.getResources()
+                .getString(R.string.zen_mode_default_weekends_name);
+        rule2.conditionId = ZenModeConfig.toScheduleConditionId(weekends);
+        rule2.zenMode = Global.ZEN_MODE_ALARMS;
+        config.automaticRules.put(config.newRuleId(), rule2);
+    }
+
+    private static int zenSeverity(int zen) {
+        switch (zen) {
+            case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: return 1;
+            case Global.ZEN_MODE_ALARMS: return 2;
+            case Global.ZEN_MODE_NO_INTERRUPTIONS: return 3;
+            default: return 0;
+        }
+    }
+
+    private final ZenModeConfig.Migration mConfigMigration = new ZenModeConfig.Migration() {
+        @Override
+        public ZenModeConfig migrate(ZenModeConfig.XmlV1 v1) {
+            if (v1 == null) return null;
+            final ZenModeConfig rt = new ZenModeConfig();
+            rt.allowCalls = v1.allowCalls;
+            rt.allowEvents = v1.allowEvents;
+            rt.allowFrom = v1.allowFrom;
+            rt.allowMessages = v1.allowMessages;
+            rt.allowReminders = v1.allowReminders;
+            // don't migrate current exit condition
+            final int[] days = ZenModeConfig.XmlV1.tryParseDays(v1.sleepMode);
+            if (days != null && days.length > 0) {
+                Log.i(TAG, "Migrating existing V1 downtime to single schedule");
+                final ScheduleInfo schedule = new ScheduleInfo();
+                schedule.days = days;
+                schedule.startHour = v1.sleepStartHour;
+                schedule.startMinute = v1.sleepStartMinute;
+                schedule.endHour = v1.sleepEndHour;
+                schedule.endMinute = v1.sleepEndMinute;
+                final ZenRule rule = new ZenRule();
+                rule.enabled = true;
+                rule.name = mContext.getResources()
+                        .getString(R.string.zen_mode_downtime_feature_name);
+                rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
+                rule.zenMode = v1.sleepNone ? Global.ZEN_MODE_NO_INTERRUPTIONS
+                        : Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+                rt.automaticRules.put(rt.newRuleId(), rule);
+            } else {
+                Log.i(TAG, "No existing V1 downtime found, generating default schedules");
+                appendDefaultScheduleRules(rt);
+            }
+            return rt;
+        }
+    };
+
+    private final class RingerModeDelegate implements AudioManagerInternal.RingerModeDelegate {
+        @Override
+        public String toString() {
+            return TAG;
+        }
+
+        @Override
+        public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller,
+                int ringerModeExternal, VolumePolicy policy) {
+            final boolean isChange = ringerModeOld != ringerModeNew;
+
+            int ringerModeExternalOut = ringerModeNew;
+
+            int newZen = -1;
+            switch (ringerModeNew) {
+                case AudioManager.RINGER_MODE_SILENT:
+                    if (isChange && policy.doNotDisturbWhenSilent) {
+                        if (mZenMode != Global.ZEN_MODE_NO_INTERRUPTIONS
+                                && mZenMode != Global.ZEN_MODE_ALARMS) {
+                            newZen = Global.ZEN_MODE_NO_INTERRUPTIONS;
+                        }
+                    }
+                    break;
+                case AudioManager.RINGER_MODE_VIBRATE:
+                case AudioManager.RINGER_MODE_NORMAL:
+                    if (isChange && ringerModeOld == AudioManager.RINGER_MODE_SILENT
+                            && (mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+                                    || mZenMode == Global.ZEN_MODE_ALARMS)) {
+                        newZen = Global.ZEN_MODE_OFF;
+                    } else if (mZenMode != Global.ZEN_MODE_OFF) {
+                        ringerModeExternalOut = AudioManager.RINGER_MODE_SILENT;
+                    }
+                    break;
+            }
+            if (newZen != -1) {
+                setManualZenMode(newZen, null, "ringerModeInternal", false /*setRingerMode*/);
+            }
+
+            if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) {
+                ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller,
+                        ringerModeExternal, ringerModeExternalOut);
+            }
+            return ringerModeExternalOut;
+        }
+
+        @Override
+        public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller,
+                int ringerModeInternal, VolumePolicy policy) {
+            int ringerModeInternalOut = ringerModeNew;
+            final boolean isChange = ringerModeOld != ringerModeNew;
+            final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+
+            int newZen = -1;
+            switch (ringerModeNew) {
+                case AudioManager.RINGER_MODE_SILENT:
+                    if (isChange) {
+                        if (mZenMode == Global.ZEN_MODE_OFF) {
+                            newZen = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+                        }
+                        ringerModeInternalOut = isVibrate ? AudioManager.RINGER_MODE_VIBRATE
+                                : AudioManager.RINGER_MODE_NORMAL;
+                    } else {
+                        ringerModeInternalOut = ringerModeInternal;
+                    }
+                    break;
+                case AudioManager.RINGER_MODE_VIBRATE:
+                case AudioManager.RINGER_MODE_NORMAL:
+                    if (mZenMode != Global.ZEN_MODE_OFF) {
+                        newZen = Global.ZEN_MODE_OFF;
+                    }
+                    break;
+            }
+            if (newZen != -1) {
+                setManualZenMode(newZen, null, "ringerModeExternal", false /*setRingerMode*/);
+            }
+
+            ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller,
+                    ringerModeInternal, ringerModeInternalOut);
+            return ringerModeInternalOut;
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
         private final Uri ZEN_MODE = Global.getUriFor(Global.ZEN_MODE);
 
         public SettingsObserver(Handler handler) {
@@ -546,12 +584,15 @@
 
         public void update(Uri uri) {
             if (ZEN_MODE.equals(uri)) {
-                readZenModeFromSetting();
+                if (mZenMode != getZenModeSetting()) {
+                    if (DEBUG) Log.d(TAG, "Fixing zen mode setting");
+                    setZenModeSetting(mZenMode);
+                }
             }
         }
     }
 
-    private class H extends Handler {
+    private final class H extends Handler {
         private static final int MSG_DISPATCH = 1;
 
         private H(Looper looper) {
@@ -577,4 +618,5 @@
         void onConfigChanged() {}
         void onZenModeChanged() {}
     }
+
 }