Implement long-term condition subscriptions.

Bind long-term conditions (like "in a meeting") to enter/exit
zen mode automatically.

Persist automatic condition subscriptions to maintain them across
reboots.

Normalize condition state binding: true => enter zen, false => exit.

Change-Id: Icba2b8b25c0a352ae8215f4c0a324e4f966c0165
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ad4027d..b917263 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -58,6 +58,8 @@
     ZenModeConfig getZenModeConfig();
     boolean setZenModeConfig(in ZenModeConfig config);
     oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
-    oneway void requestZenModeConditions(in IConditionListener callback, boolean requested);
+    oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
     oneway void setZenModeCondition(in Uri conditionId);
+    oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
+    Condition[] getAutomaticZenModeConditions();
 }
\ No newline at end of file
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 71e3166..dd8b34d 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -71,7 +71,7 @@
         dest.writeParcelable(id, 0);
         dest.writeString(caption);
         dest.writeInt(state);
-        dest.writeInt(flags);
+        dest.writeInt(this.flags);
     }
 
     @Override
@@ -92,6 +92,14 @@
         throw new IllegalArgumentException("state is invalid: " + state);
     }
 
+    public static String relevanceToString(int flags) {
+        final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
+        final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
+        if (!now && !always) return "NONE";
+        if (now && always) return "NOW, ALWAYS";
+        return now ? "NOW" : "ALWAYS";
+    }
+
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof Condition)) return false;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 925ddcf..846e292 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -16,6 +16,8 @@
 
 package android.service.notification;
 
+import android.content.ComponentName;
+import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -25,6 +27,8 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 
 /**
@@ -51,6 +55,10 @@
     private static final String SLEEP_ATT_END_HR = "endHour";
     private static final String SLEEP_ATT_END_MIN = "endMin";
 
+    private static final String CONDITION_TAG = "condition";
+    private static final String CONDITION_ATT_COMPONENT = "component";
+    private static final String CONDITION_ATT_ID = "id";
+
     public boolean allowCalls;
     public boolean allowMessages;
 
@@ -59,6 +67,8 @@
     public int sleepStartMinute;
     public int sleepEndHour;
     public int sleepEndMinute;
+    public ComponentName[] conditionComponents;
+    public Uri[] conditionIds;
 
     public ZenModeConfig() { }
 
@@ -72,6 +82,16 @@
         sleepStartMinute = source.readInt();
         sleepEndHour = source.readInt();
         sleepEndMinute = source.readInt();
+        int len = source.readInt();
+        if (len > 0) {
+            conditionComponents = new ComponentName[len];
+            source.readTypedArray(conditionComponents, ComponentName.CREATOR);
+        }
+        len = source.readInt();
+        if (len > 0) {
+            conditionIds = new Uri[len];
+            source.readTypedArray(conditionIds, Uri.CREATOR);
+        }
     }
 
     @Override
@@ -88,6 +108,18 @@
         dest.writeInt(sleepStartMinute);
         dest.writeInt(sleepEndHour);
         dest.writeInt(sleepEndMinute);
+        if (conditionComponents != null && conditionComponents.length > 0) {
+            dest.writeInt(conditionComponents.length);
+            dest.writeTypedArray(conditionComponents, 0);
+        } else {
+            dest.writeInt(0);
+        }
+        if (conditionIds != null && conditionIds.length > 0) {
+            dest.writeInt(conditionIds.length);
+            dest.writeTypedArray(conditionIds, 0);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     @Override
@@ -98,6 +130,10 @@
             .append(",sleepMode=").append(sleepMode)
             .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
             .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
+            .append(",conditionComponents=")
+            .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
+            .append(",conditionIds=")
+            .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
             .append(']').toString();
     }
 
@@ -112,13 +148,16 @@
                 && other.sleepStartHour == sleepStartHour
                 && other.sleepStartMinute == sleepStartMinute
                 && other.sleepEndHour == sleepEndHour
-                && other.sleepEndMinute == sleepEndMinute;
+                && other.sleepEndMinute == sleepEndMinute
+                && Objects.deepEquals(other.conditionComponents, conditionComponents)
+                && Objects.deepEquals(other.conditionIds, conditionIds);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
-                sleepStartMinute, sleepEndHour, sleepEndMinute);
+                sleepStartMinute, sleepEndHour, sleepEndMinute,
+                Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds));
     }
 
     public boolean isValid() {
@@ -136,9 +175,18 @@
         if (!ZEN_TAG.equals(tag)) return null;
         final ZenModeConfig rt = new ZenModeConfig();
         final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION));
+        final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+        final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
-            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) return rt;
+            if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+                if (!conditionComponents.isEmpty()) {
+                    rt.conditionComponents = conditionComponents
+                            .toArray(new ComponentName[conditionComponents.size()]);
+                    rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+                }
+                return rt;
+            }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
                     rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
@@ -155,10 +203,18 @@
                     rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
                     rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
                     rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+                } else if (CONDITION_TAG.equals(tag)) {
+                    final ComponentName component =
+                            safeComponentName(parser, CONDITION_ATT_COMPONENT);
+                    final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+                    if (component != null && conditionId != null) {
+                        conditionComponents.add(component);
+                        conditionIds.add(conditionId);
+                    }
                 }
             }
         }
-        return rt;
+        throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
     public void writeXml(XmlSerializer out) throws IOException {
@@ -180,6 +236,16 @@
         out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
         out.endTag(null, SLEEP_TAG);
 
+        if (conditionComponents != null && conditionIds != null
+                && conditionComponents.length == conditionIds.length) {
+            for (int i = 0; i < conditionComponents.length; i++) {
+                out.startTag(null, CONDITION_TAG);
+                out.attribute(null, CONDITION_ATT_COMPONENT,
+                        conditionComponents[i].flattenToString());
+                out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
+                out.endTag(null, CONDITION_TAG);
+            }
+        }
         out.endTag(null, ZEN_TAG);
     }
 
@@ -203,6 +269,18 @@
         return Integer.valueOf(val);
     }
 
+    private static ComponentName safeComponentName(XmlPullParser parser, String att) {
+        final String val = parser.getAttributeValue(null, att);
+        if (TextUtils.isEmpty(val)) return null;
+        return ComponentName.unflattenFromString(val);
+    }
+
+    private static Uri safeUri(XmlPullParser parser, String att) {
+        final String val = parser.getAttributeValue(null, att);
+        if (TextUtils.isEmpty(val)) return null;
+        return Uri.parse(val);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
index 1bc97a0..b09b462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
@@ -58,7 +58,7 @@
         mNoMan = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
         try {
-            mNoMan.requestZenModeConditions(mListener, true /*requested*/);
+            mNoMan.requestZenModeConditions(mListener, Condition.FLAG_RELEVANT_NOW);
         } catch (RemoteException e) {
             // noop
         }
@@ -98,7 +98,7 @@
     @Override
     public void dispose() {
         try {
-            mNoMan.requestZenModeConditions(mListener, false /*requested*/);
+            mNoMan.requestZenModeConditions(mListener, 0 /*none*/);
         } catch (RemoteException e) {
             // noop
         }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 5567944..d074565 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Handler;
@@ -28,31 +29,31 @@
 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;
 
 import com.android.internal.R;
 
-import libcore.util.Objects;
-
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 
 public class ConditionProviders extends ManagedServices {
+    private static final Condition[] NO_CONDITIONS = new Condition[0];
 
     private final ZenModeHelper mZenModeHelper;
     private final ArrayMap<IBinder, IConditionListener> mListeners
             = new ArrayMap<IBinder, IConditionListener>();
-    private final ArrayMap<Uri, ManagedServiceInfo> mConditions
-            = new ArrayMap<Uri, ManagedServiceInfo>();
-
-    private Uri mCurrentConditionId;
+    private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
 
     public ConditionProviders(Context context, Handler handler,
             UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
         super(context, handler, new Object(), userProfiles);
         mZenModeHelper = zenModeHelper;
         mZenModeHelper.addCallback(new ZenModeHelperCallback());
+        loadZenConfig();
     }
 
     @Override
@@ -71,20 +72,13 @@
     public void dump(PrintWriter pw) {
         super.dump(pw);
         synchronized(mMutex) {
-            pw.print("    mCurrentConditionId="); pw.println(mCurrentConditionId);
             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("    mConditions("); pw.print(mConditions.size()); pw.println("):");
-            for (int i = 0; i < mConditions.size(); i++) {
-                pw.print("      "); pw.print(mConditions.keyAt(i));
-                final ManagedServiceInfo info = mConditions.valueAt(i);
-                pw.print(" -> "); pw.print(info.component);
-                if (!mServices.contains(info)) {
-                    pw.print(" (orphan)");
-                }
-                pw.println();
+            pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
+            for (int i = 0; i < mRecords.size(); i++) {
+                pw.print("      "); pw.println(mRecords.get(i));
             }
         }
     }
@@ -95,29 +89,49 @@
     }
 
     @Override
-    protected void onServiceAdded(IInterface service) {
-        Slog.d(TAG, "onServiceAdded " + service);
-        final IConditionProvider provider = (IConditionProvider) service;
+    protected void onServiceAdded(ManagedServiceInfo info) {
+        Slog.d(TAG, "onServiceAdded " + info);
+        final IConditionProvider provider = provider(info);
         try {
             provider.onConnected();
         } catch (RemoteException e) {
             // we tried
         }
+        synchronized (mMutex) {
+            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, auto-subscribe
+                if (r.isAutomatic) {
+                    try {
+                        final Uri id = r.id;
+                        if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
+                        provider.onSubscribe(id);
+                    } catch (RemoteException e) {
+                        // we tried
+                    }
+                }
+            }
+        }
     }
 
     @Override
     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
         if (removed == null) return;
-        if (mCurrentConditionId != null) {
-            if (removed.equals(mConditions.get(mCurrentConditionId))) {
-                mCurrentConditionId = null;
+        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
                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
             }
-        }
-        for (int i = mConditions.size() - 1; i >= 0; i--) {
-            if (removed.equals(mConditions.valueAt(i))) {
-                mConditions.removeAt(i);
+            if (r.isAutomatic) {
+                // removing an automatic condition, exit zen
+                mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
             }
+            mRecords.remove(i);
         }
     }
 
@@ -127,14 +141,15 @@
         }
     }
 
-    public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+    public void requestZenModeConditions(IConditionListener callback, int relevance) {
         synchronized(mMutex) {
             if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
-                    + " requested=" + requested);
+                    + " relevance=" + Condition.relevanceToString(relevance));
             if (callback == null) return;
-            if (requested) {
+            relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
+            if (relevance != 0) {
                 mListeners.put(callback.asBinder(), callback);
-                requestConditionsLocked(Condition.FLAG_RELEVANT_NOW);
+                requestConditionsLocked(relevance);
             } else {
                 mListeners.remove(callback.asBinder());
                 if (mListeners.isEmpty()) {
@@ -144,25 +159,51 @@
         }
     }
 
+    private Condition[] validateConditions(String pkg, Condition[] conditions) {
+        if (conditions == null || conditions.length == 0) return null;
+        final int N = conditions.length;
+        final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
+        for (int i = 0; i < N; i++) {
+            final Uri id = conditions[i].id;
+            if (!Condition.isValidId(id, pkg)) {
+                Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
+                continue;
+            }
+            if (valid.containsKey(id)) {
+                Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
+                continue;
+            }
+            valid.put(id, conditions[i]);
+        }
+        if (valid.size() == 0) return null;
+        if (valid.size() == N) return conditions;
+        final Condition[] rt = new Condition[valid.size()];
+        for (int i = 0; i < rt.length; i++) {
+            rt[i] = valid.valueAt(i);
+        }
+        return rt;
+    }
+
+    private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+        final int N = mRecords.size();
+        for (int i = 0; i < N; i++) {
+            final ConditionRecord r = mRecords.get(i);
+            if (r.id.equals(id) && r.component.equals(component)) {
+                return r;
+            }
+        }
+        final ConditionRecord r = new ConditionRecord(id, component);
+        mRecords.add(r);
+        return r;
+    }
+
     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
         synchronized(mMutex) {
             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
                     + (conditions == null ? null : Arrays.asList(conditions)));
+            conditions = validateConditions(pkg, conditions);
             if (conditions == null || conditions.length == 0) return;
             final int N = conditions.length;
-            boolean valid = true;
-            for (int i = 0; i < N; i++) {
-                final Uri id = conditions[i].id;
-                if (!Condition.isValidId(id, pkg)) {
-                    Slog.w(TAG, "Ignoring conditions from " + pkg + " for invalid id: " + id);
-                    valid = false;
-                }
-            }
-            if (!valid) return;
-
-            for (int i = 0; i < N; i++) {
-                mConditions.put(conditions[i].id, info);
-            }
             for (IConditionListener listener : mListeners.values()) {
                 try {
                     listener.onConditionsReceived(conditions);
@@ -170,62 +211,154 @@
                     Slog.w(TAG, "Error sending conditions to listener " + listener, e);
                 }
             }
-            if (mCurrentConditionId != null) {
-                for (int i = 0; i < N; i++) {
-                    final Condition c = conditions[i];
-                    if (!c.id.equals(mCurrentConditionId)) continue;
-                    if (c.state == Condition.STATE_TRUE || c.state == Condition.STATE_ERROR) {
-                        triggerExitLocked(c.state == Condition.STATE_ERROR);
-                        return;
+            for (int i = 0; i < N; i++) {
+                final Condition c = conditions[i];
+                final ConditionRecord r = getRecordLocked(c.id, info.component);
+                r.info = info;
+                r.condition = c;
+                // if manual, exit zen if false (or failed)
+                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);
+                        }
+                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+                        unsubscribeLocked(r);
+                        r.isManual = false;
+                    }
+                }
+                // 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);
+                    } else if (c.state == Condition.STATE_TRUE) {
+                        Slog.d(TAG, "Enter zen: automatic condition true: " + c);
+                        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
                     }
                 }
             }
         }
     }
 
-    private void triggerExitLocked(boolean error) {
-        if (error) {
-            Slog.w(TAG, "Zen mode exit condition failed");
-        } else if (DEBUG) {
-            Slog.d(TAG, "Zen mode exit condition triggered");
-        }
-        mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
-        unsubscribeLocked(mCurrentConditionId);
-        mCurrentConditionId = null;
-    }
-
     public void setZenModeCondition(Uri conditionId) {
+        if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
         synchronized(mMutex) {
-            if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
-            if (Objects.equal(mCurrentConditionId, conditionId)) return;
-
-            if (mCurrentConditionId != null) {
-                unsubscribeLocked(mCurrentConditionId);
-            }
-            if (conditionId != null) {
-                final ManagedServiceInfo info = mConditions.get(conditionId);
-                final IConditionProvider provider = provider(info);
-                if (provider == null) return;
-                try {
-                    provider.onSubscribe(conditionId);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Error subscribing to " + conditionId
-                            + " from " + info.component, e);
+            final int N = mRecords.size();
+            for (int i = 0; i < N; i++) {
+                final ConditionRecord r = mRecords.get(i);
+                final boolean idEqual = r.id.equals(conditionId);
+                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;
                 }
             }
-            mCurrentConditionId = conditionId;
         }
     }
 
-    private void unsubscribeLocked(Uri conditionId) {
-        final ManagedServiceInfo info = mConditions.get(mCurrentConditionId);
-        final IConditionProvider provider = provider(info);
-        if (provider == null) return;
-        try {
-            provider.onUnsubscribe(conditionId);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Error unsubscribing to " + conditionId + " from " + info.component, e);
+    private void subscribeLocked(ConditionRecord r) {
+        if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
+        final IConditionProvider provider = provider(r);
+        if (provider == null) {
+            Slog.w(TAG, "subscribeLocked: no provider");
+            return;
         }
+        try {
+            provider.onSubscribe(r.id);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error subscribing to " + r, e);
+        }
+    }
+
+    private static <T> ArraySet<T> safeSet(T... items) {
+        final ArraySet<T> rt = new ArraySet<T>();
+        if (items == null || items.length == 0) return rt;
+        final int N = items.length;
+        for (int i = 0; i < N; i++) {
+            final T item = items[i];
+            if (item != null) {
+                rt.add(item);
+            }
+        }
+        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);
+        if (provider == null) {
+            Slog.w(TAG, "unsubscribeLocked: no provider");
+            return;
+        }
+        try {
+            provider.onUnsubscribe(r.id);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error unsubscribing to " + r, e);
+        }
+    }
+
+    private static IConditionProvider provider(ConditionRecord r) {
+        return r == null ? null : provider(r.info);
     }
 
     private static IConditionProvider provider(ManagedServiceInfo info) {
@@ -244,20 +377,99 @@
         }
     }
 
+    private void loadZenConfig() {
+        final ZenModeConfig config = mZenModeHelper.getConfig();
+        if (config == null) {
+            if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
+            return;
+        }
+        synchronized (mMutex) {
+            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;
+            }
+        }
+        if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
+        mZenModeHelper.setConfig(config);
+    }
+
     private class ZenModeHelperCallback extends ZenModeHelper.Callback {
         @Override
+        void onConfigChanged() {
+            loadZenConfig();
+        }
+
+        @Override
         void onZenModeChanged() {
             final int mode = mZenModeHelper.getZenMode();
             if (mode == Global.ZEN_MODE_OFF) {
-                synchronized (mMutex) {
-                    if (mCurrentConditionId != null) {
-                        if (DEBUG) Slog.d(TAG, "Zen mode off, forcing unsubscribe from "
-                                + mCurrentConditionId);
-                        unsubscribeLocked(mCurrentConditionId);
-                        mCurrentConditionId = null;
-                    }
-                }
+                // ensure any manual condition is cleared
+                setZenModeCondition(null);
             }
         }
     }
+
+    private static class ConditionRecord {
+        public final Uri id;
+        public final ComponentName component;
+        public Condition condition;
+        public ManagedServiceInfo info;
+        public boolean isAutomatic;
+        public boolean isManual;
+
+        private ConditionRecord(Uri id, ComponentName component) {
+            this.id = id;
+            this.component = component;
+        }
+
+        @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");
+            return sb.append(']').toString();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0621f58..d34b09c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -101,7 +101,7 @@
 
     abstract protected IInterface asInterface(IBinder binder);
 
-    abstract protected void onServiceAdded(IInterface service);
+    abstract protected void onServiceAdded(ManagedServiceInfo info);
 
     protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
 
@@ -368,11 +368,12 @@
                             @Override
                             public void onServiceConnected(ComponentName name, IBinder binder) {
                                 boolean added = false;
+                                ManagedServiceInfo info = null;
                                 synchronized (mMutex) {
                                     mServicesBinding.remove(servicesBindingTag);
                                     try {
                                         mService = asInterface(binder);
-                                        ManagedServiceInfo info = newServiceInfo(mService, name,
+                                        info = newServiceInfo(mService, name,
                                                 userid, false /*isSystem*/, this, targetSdkVersion);
                                         binder.linkToDeath(info, 0);
                                         added = mServices.add(info);
@@ -381,7 +382,7 @@
                                     }
                                 }
                                 if (added) {
-                                    onServiceAdded(mService);
+                                    onServiceAdded(info);
                                 }
                             }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6e4eb565..2e52983 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1355,9 +1355,9 @@
         }
 
         @Override
-        public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+        public void requestZenModeConditions(IConditionListener callback, int relevance) {
             enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
-            mConditionProviders.requestZenModeConditions(callback, requested);
+            mConditionProviders.requestZenModeConditions(callback, relevance);
         }
 
         @Override
@@ -1366,6 +1366,18 @@
             mConditionProviders.setZenModeCondition(conditionId);
         }
 
+        @Override
+        public void setAutomaticZenModeConditions(Uri[] conditionIds) {
+            enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
+            mConditionProviders.setAutomaticZenModeConditions(conditionIds);
+        }
+
+        @Override
+        public Condition[] getAutomaticZenModeConditions() {
+            enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
+            return mConditionProviders.getAutomaticZenModeConditions();
+        }
+
         private void enforceSystemOrSystemUI(String message) {
             if (isCallerSystem()) return;
             getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
@@ -2320,8 +2332,8 @@
         }
 
         @Override
-        public void onServiceAdded(IInterface service) {
-            final INotificationListener listener = (INotificationListener) service;
+        public void onServiceAdded(ManagedServiceInfo info) {
+            final INotificationListener listener = (INotificationListener) info.service;
             final String[] keys = getActiveNotificationKeysFromListener(listener);
             try {
                 listener.onListenerConnected(keys);