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/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() {}
     }
+
 }