Merge "Add a method to associate MediaDrm session with MediaCrypto"
diff --git a/api/current.txt b/api/current.txt
index 6339777..75a8df9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3976,6 +3976,48 @@
     field public java.lang.String serviceDetails;
   }
 
+  public final class AssistAction {
+    method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+    field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+    field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+    field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+    field public static final java.lang.String KEY_DESCRIPTION = "description";
+    field public static final java.lang.String KEY_ID = "@id";
+    field public static final java.lang.String KEY_NAME = "name";
+    field public static final java.lang.String KEY_TYPE = "@type";
+    field public static final java.lang.String KEY_URL = "url";
+    field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+    field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+    field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+    field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+    field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+    field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+    field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+    field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+    field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+  }
+
+  public static final class AssistAction.ActionBuilder {
+    ctor public AssistAction.ActionBuilder();
+    method public android.os.Bundle build();
+    method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+    method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+    method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+    method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+  }
+
+  public static final class AssistAction.ThingBuilder {
+    ctor public AssistAction.ThingBuilder();
+    method public android.os.Bundle build();
+    method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+    method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+    method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+  }
+
   public class AssistContent implements android.os.Parcelable {
     ctor public AssistContent();
     method public int describeContents();
@@ -5317,6 +5359,11 @@
     method public void onRejectSharedElements(java.util.List<android.view.View>);
     method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
     method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+    method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+  }
+
+  public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+    method public abstract void onSharedElementsReady();
   }
 
   public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -9162,6 +9209,7 @@
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
     field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+    field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
     field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -9726,15 +9774,13 @@
     method public void registerDataSetObserver(android.database.DataSetObserver);
     method public boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
-    field protected boolean mClosed;
-    field protected android.content.ContentResolver mContentResolver;
-    field protected deprecated java.lang.Long mCurrentRowID;
-    field protected int mPos;
-    field protected deprecated int mRowIdColumnIndex;
-    field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+    field protected deprecated boolean mClosed;
+    field protected deprecated android.content.ContentResolver mContentResolver;
+    field protected deprecated int mPos;
   }
 
   protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -9834,6 +9880,7 @@
     method public abstract void registerDataSetObserver(android.database.DataSetObserver);
     method public abstract deprecated boolean requery();
     method public abstract android.os.Bundle respond(android.os.Bundle);
+    method public abstract void setExtras(android.os.Bundle);
     method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public abstract void unregisterContentObserver(android.database.ContentObserver);
     method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -9905,7 +9952,7 @@
     ctor public CursorWrapper(android.database.Cursor);
     method public void close();
     method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
-    method public void deactivate();
+    method public deprecated void deactivate();
     method public byte[] getBlob(int);
     method public int getColumnCount();
     method public int getColumnIndex(java.lang.String);
@@ -9939,8 +9986,9 @@
     method public boolean moveToPrevious();
     method public void registerContentObserver(android.database.ContentObserver);
     method public void registerDataSetObserver(android.database.DataSetObserver);
-    method public boolean requery();
+    method public deprecated boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -15478,6 +15526,7 @@
     field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
     field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
     field public static final java.lang.String KEY_MIME = "mime";
+    field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
     field public static final java.lang.String KEY_PRIORITY = "priority";
     field public static final java.lang.String KEY_PROFILE = "profile";
     field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -15600,6 +15649,7 @@
     field public static final int METADATA_KEY_ARTIST = 2; // 0x2
     field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
     field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+    field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
     field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
     field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
     field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -16785,9 +16835,8 @@
     method public int describeContents();
     method public int getId();
     method public int getInputPortCount();
-    method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
     method public int getOutputPortCount();
-    method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+    method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
     method public android.os.Bundle getProperties();
     method public int getType();
     method public boolean isPrivate();
@@ -30558,7 +30607,7 @@
     ctor public MockCursor();
     method public void close();
     method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
-    method public void deactivate();
+    method public deprecated void deactivate();
     method public byte[] getBlob(int);
     method public int getColumnCount();
     method public int getColumnIndex(java.lang.String);
@@ -30591,8 +30640,9 @@
     method public boolean moveToPrevious();
     method public void registerContentObserver(android.database.ContentObserver);
     method public void registerDataSetObserver(android.database.DataSetObserver);
-    method public boolean requery();
+    method public deprecated boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
diff --git a/api/removed.txt b/api/removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -6,6 +6,16 @@
 
 }
 
+package android.database {
+
+  public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+    field protected java.lang.Long mCurrentRowID;
+    field protected int mRowIdColumnIndex;
+    field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+  }
+
+}
+
 package android.media {
 
   public class AudioFormat {
diff --git a/api/system-current.txt b/api/system-current.txt
index 25b53fb..d272c20 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4067,6 +4067,48 @@
     field public java.lang.String serviceDetails;
   }
 
+  public final class AssistAction {
+    method public static void updateAssistData(android.os.Bundle, android.os.Bundle);
+    field public static final java.lang.String ASSIST_ACTION_KEY = "android:assist_action";
+    field public static final java.lang.String KEY_ACTION_OBJECT = "object";
+    field public static final java.lang.String KEY_ACTION_STATUS = "actionStatus";
+    field public static final java.lang.String KEY_DESCRIPTION = "description";
+    field public static final java.lang.String KEY_ID = "@id";
+    field public static final java.lang.String KEY_NAME = "name";
+    field public static final java.lang.String KEY_TYPE = "@type";
+    field public static final java.lang.String KEY_URL = "url";
+    field public static final java.lang.String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+    field public static final java.lang.String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+    field public static final java.lang.String TYPE_ADD_ACTION = "AddAction";
+    field public static final java.lang.String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+    field public static final java.lang.String TYPE_LIKE_ACTION = "LikeAction";
+    field public static final java.lang.String TYPE_LISTEN_ACTION = "ListenAction";
+    field public static final java.lang.String TYPE_VIEW_ACTION = "ViewAction";
+    field public static final java.lang.String TYPE_WANT_ACTION = "WantAction";
+    field public static final java.lang.String TYPE_WATCH_ACTION = "WatchAction";
+  }
+
+  public static final class AssistAction.ActionBuilder {
+    ctor public AssistAction.ActionBuilder();
+    method public android.os.Bundle build();
+    method public android.app.AssistAction.ActionBuilder set(java.lang.String, java.lang.String);
+    method public android.app.AssistAction.ActionBuilder set(java.lang.String, android.os.Bundle);
+    method public android.app.AssistAction.ActionBuilder setObject(android.os.Bundle);
+    method public android.app.AssistAction.ActionBuilder setType(java.lang.String);
+  }
+
+  public static final class AssistAction.ThingBuilder {
+    ctor public AssistAction.ThingBuilder();
+    method public android.os.Bundle build();
+    method public android.app.AssistAction.ThingBuilder set(java.lang.String, java.lang.String);
+    method public android.app.AssistAction.ThingBuilder set(java.lang.String, android.os.Bundle);
+    method public android.app.AssistAction.ThingBuilder setDescription(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setId(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setName(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setType(java.lang.String);
+    method public android.app.AssistAction.ThingBuilder setUrl(android.net.Uri);
+  }
+
   public class AssistContent implements android.os.Parcelable {
     ctor public AssistContent();
     method public int describeContents();
@@ -5408,6 +5450,11 @@
     method public void onRejectSharedElements(java.util.List<android.view.View>);
     method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
     method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+    method public void onSharedElementsArrived(java.util.List<java.lang.String>, java.util.List<android.view.View>, android.app.SharedElementCallback.OnSharedElementsReadyListener);
+  }
+
+  public static abstract interface SharedElementCallback.OnSharedElementsReadyListener {
+    method public abstract void onSharedElementsReady();
   }
 
   public deprecated class TabActivity extends android.app.ActivityGroup {
@@ -9418,6 +9465,7 @@
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
     field public static final java.lang.String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
     field public static final java.lang.String FEATURE_GAMEPAD = "android.hardware.gamepad";
+    field public static final java.lang.String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final java.lang.String FEATURE_HOME_SCREEN = "android.software.home_screen";
     field public static final java.lang.String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final java.lang.String FEATURE_LEANBACK = "android.software.leanback";
@@ -10017,15 +10065,13 @@
     method public void registerDataSetObserver(android.database.DataSetObserver);
     method public boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
-    field protected boolean mClosed;
-    field protected android.content.ContentResolver mContentResolver;
-    field protected deprecated java.lang.Long mCurrentRowID;
-    field protected int mPos;
-    field protected deprecated int mRowIdColumnIndex;
-    field protected deprecated java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+    field protected deprecated boolean mClosed;
+    field protected deprecated android.content.ContentResolver mContentResolver;
+    field protected deprecated int mPos;
   }
 
   protected static class AbstractCursor.SelfContentObserver extends android.database.ContentObserver {
@@ -10125,6 +10171,7 @@
     method public abstract void registerDataSetObserver(android.database.DataSetObserver);
     method public abstract deprecated boolean requery();
     method public abstract android.os.Bundle respond(android.os.Bundle);
+    method public abstract void setExtras(android.os.Bundle);
     method public abstract void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public abstract void unregisterContentObserver(android.database.ContentObserver);
     method public abstract void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -10196,7 +10243,7 @@
     ctor public CursorWrapper(android.database.Cursor);
     method public void close();
     method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
-    method public void deactivate();
+    method public deprecated void deactivate();
     method public byte[] getBlob(int);
     method public int getColumnCount();
     method public int getColumnIndex(java.lang.String);
@@ -10230,8 +10277,9 @@
     method public boolean moveToPrevious();
     method public void registerContentObserver(android.database.ContentObserver);
     method public void registerDataSetObserver(android.database.DataSetObserver);
-    method public boolean requery();
+    method public deprecated boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
@@ -16688,6 +16736,7 @@
     field public static final java.lang.String KEY_MAX_INPUT_SIZE = "max-input-size";
     field public static final java.lang.String KEY_MAX_WIDTH = "max-width";
     field public static final java.lang.String KEY_MIME = "mime";
+    field public static final java.lang.String KEY_OPERATING_RATE = "operating-rate";
     field public static final java.lang.String KEY_PRIORITY = "priority";
     field public static final java.lang.String KEY_PROFILE = "profile";
     field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
@@ -16810,6 +16859,7 @@
     field public static final int METADATA_KEY_ARTIST = 2; // 0x2
     field public static final int METADATA_KEY_AUTHOR = 3; // 0x3
     field public static final int METADATA_KEY_BITRATE = 20; // 0x14
+    field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
     field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
     field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
     field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
@@ -18061,9 +18111,8 @@
     method public int describeContents();
     method public int getId();
     method public int getInputPortCount();
-    method public android.media.midi.MidiDeviceInfo.PortInfo getInputPortInfo(int);
     method public int getOutputPortCount();
-    method public android.media.midi.MidiDeviceInfo.PortInfo getOutputPortInfo(int);
+    method public android.media.midi.MidiDeviceInfo.PortInfo[] getPortList();
     method public android.os.Bundle getProperties();
     method public int getType();
     method public boolean isPrivate();
@@ -30267,6 +30316,7 @@
     method public abstract byte[] read() throws android.os.RemoteException;
     method public abstract void setOemUnlockEnabled(boolean) throws android.os.RemoteException;
     method public abstract void wipe() throws android.os.RemoteException;
+    method public abstract void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent) throws android.os.RemoteException;
     method public abstract int write(byte[]) throws android.os.RemoteException;
   }
 
@@ -30278,7 +30328,14 @@
     method public byte[] read();
     method public void setOemUnlockEnabled(boolean);
     method public void wipe();
+    method public void wipeIfAllowed(android.os.Bundle, android.app.PendingIntent);
     method public int write(byte[]);
+    field public static final java.lang.String ACTION_WIPE_IF_ALLOWED = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+    field public static final java.lang.String EXTRA_WIPE_IF_ALLOWED_CALLBACK = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+    field public static final int STATUS_ERROR_NETWORK_ERROR = 2; // 0x2
+    field public static final int STATUS_ERROR_NOT_COMPLIANT = 3; // 0x3
+    field public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1; // 0x1
+    field public static final int STATUS_SUCCESS = 0; // 0x0
   }
 
 }
@@ -33161,7 +33218,7 @@
     ctor public MockCursor();
     method public void close();
     method public void copyStringToBuffer(int, android.database.CharArrayBuffer);
-    method public void deactivate();
+    method public deprecated void deactivate();
     method public byte[] getBlob(int);
     method public int getColumnCount();
     method public int getColumnIndex(java.lang.String);
@@ -33194,8 +33251,9 @@
     method public boolean moveToPrevious();
     method public void registerContentObserver(android.database.ContentObserver);
     method public void registerDataSetObserver(android.database.DataSetObserver);
-    method public boolean requery();
+    method public deprecated boolean requery();
     method public android.os.Bundle respond(android.os.Bundle);
+    method public void setExtras(android.os.Bundle);
     method public void setNotificationUri(android.content.ContentResolver, android.net.Uri);
     method public void unregisterContentObserver(android.database.ContentObserver);
     method public void unregisterDataSetObserver(android.database.DataSetObserver);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index c2b9d3e..0c433c3 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -6,6 +6,16 @@
 
 }
 
+package android.database {
+
+  public abstract class AbstractCursor implements android.database.CrossProcessCursor {
+    field protected java.lang.Long mCurrentRowID;
+    field protected int mRowIdColumnIndex;
+    field protected java.util.HashMap<java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>> mUpdatedRows;
+  }
+
+}
+
 package android.media {
 
   public class AudioFormat {
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 81a01ee..4a9ba3b 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -68,6 +68,7 @@
     private static final int VALUE_TYPE_INT         = 1;
     private static final int VALUE_TYPE_PATH        = 2;
     private static final int VALUE_TYPE_COLOR       = 3;
+    private static final int VALUE_TYPE_UNDEFINED   = 4;
 
     private static final boolean DBG_ANIMATOR_INFLATER = false;
 
@@ -299,8 +300,6 @@
     private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
             int valueFromId, int valueToId, String propertyName) {
 
-        boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
-
         TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
         boolean hasFrom = (tvFrom != null);
         int fromType = hasFrom ? tvFrom.type : 0;
@@ -308,6 +307,17 @@
         boolean hasTo = (tvTo != null);
         int toType = hasTo ? tvTo.type : 0;
 
+        if (valueType == VALUE_TYPE_UNDEFINED) {
+            // Check whether it's color type. If not, fall back to default type (i.e. float type)
+            if ((hasFrom && isColorType(fromType)) || (hasTo && isColorType(toType))) {
+                valueType = VALUE_TYPE_COLOR;
+            } else {
+                valueType = VALUE_TYPE_FLOAT;
+            }
+        }
+
+        boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
         PropertyValuesHolder returnValue = null;
 
         if (valueType == VALUE_TYPE_PATH) {
@@ -341,12 +351,8 @@
         } else {
             TypeEvaluator evaluator = null;
             // Integer and float value types are handled here.
-            if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                    (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
-                    (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                            (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+            if (valueType == VALUE_TYPE_COLOR) {
                 // special case for colors: ignore valueType and get ints
-                getFloats = false;
                 evaluator = ArgbEvaluator.getInstance();
             }
             if (getFloats) {
@@ -383,8 +389,7 @@
                 if (hasFrom) {
                     if (fromType == TypedValue.TYPE_DIMENSION) {
                         valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
-                    } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                            (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                    } else if (isColorType(fromType)) {
                         valueFrom = styledAttributes.getColor(valueFromId, 0);
                     } else {
                         valueFrom = styledAttributes.getInt(valueFromId, 0);
@@ -392,8 +397,7 @@
                     if (hasTo) {
                         if (toType == TypedValue.TYPE_DIMENSION) {
                             valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
-                        } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                                (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        } else if (isColorType(toType)) {
                             valueTo = styledAttributes.getColor(valueToId, 0);
                         } else {
                             valueTo = styledAttributes.getInt(valueToId, 0);
@@ -406,8 +410,7 @@
                     if (hasTo) {
                         if (toType == TypedValue.TYPE_DIMENSION) {
                             valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
-                        } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                                (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        } else if (isColorType(toType)) {
                             valueTo = styledAttributes.getColor(valueToId, 0);
                         } else {
                             valueTo = styledAttributes.getInt(valueToId, 0);
@@ -613,8 +616,7 @@
             if (hasFrom) {
                 if (fromType == TypedValue.TYPE_DIMENSION) {
                     valueFrom = (int) arrayAnimator.getDimension(valueFromIndex, 0f);
-                } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                        (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                } else if (isColorType(fromType)) {
                     valueFrom = arrayAnimator.getColor(valueFromIndex, 0);
                 } else {
                     valueFrom = arrayAnimator.getInt(valueFromIndex, 0);
@@ -622,8 +624,7 @@
                 if (hasTo) {
                     if (toType == TypedValue.TYPE_DIMENSION) {
                         valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
-                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                    } else if (isColorType(toType)) {
                         valueTo = arrayAnimator.getColor(valueToIndex, 0);
                     } else {
                         valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -636,8 +637,7 @@
                 if (hasTo) {
                     if (toType == TypedValue.TYPE_DIMENSION) {
                         valueTo = (int) arrayAnimator.getDimension(valueToIndex, 0f);
-                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
-                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                    } else if (isColorType(toType)) {
                         valueTo = arrayAnimator.getColor(valueToIndex, 0);
                     } else {
                         valueTo = arrayAnimator.getInt(valueToIndex, 0);
@@ -749,7 +749,8 @@
                 }
                 String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
                 int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
-                        VALUE_TYPE_FLOAT);
+                        VALUE_TYPE_UNDEFINED);
+
                 PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
                 if (pvh == null) {
                     pvh = getPVH(a, valueType,
@@ -793,6 +794,7 @@
         }
     }
 
+    // Load property values holder if there are keyframes defined in it. Otherwise return null.
     private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
             String propertyName, int valueType)
             throws XmlPullParserException, IOException {
@@ -928,7 +930,17 @@
 
         float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
 
-        boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null;
+        TypedValue keyframeValue = a.peekValue(R.styleable.Keyframe_value);
+        boolean hasValue = (keyframeValue != null);
+        if (valueType == VALUE_TYPE_UNDEFINED) {
+            // When no value type is provided, check whether it's a color type first.
+            // If not, fall back to default value type (i.e. float type).
+            if (hasValue && isColorType(keyframeValue.type)) {
+                valueType = VALUE_TYPE_COLOR;
+            } else {
+                valueType = VALUE_TYPE_FLOAT;
+            }
+        }
 
         if (hasValue) {
             switch (valueType) {
@@ -1028,4 +1040,8 @@
             return sTmpTypedValue.changingConfigurations;
         }
     }
+
+    private static boolean isColorType(int type) {
+       return (type >= TypedValue.TYPE_FIRST_COLOR_INT) && (type <= TypedValue.TYPE_LAST_COLOR_INT);
+    }
 }
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2386007..6ffa5dd 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1478,6 +1478,12 @@
         anim.mInitialized = false;
         anim.mPlayingState = STOPPED;
         anim.mStartedDelay = false;
+        anim.mStarted = false;
+        anim.mRunning = false;
+        anim.mPaused = false;
+        anim.mResumed = false;
+        anim.mStartListenersCalled = false;
+
         PropertyValuesHolder[] oldValues = mValues;
         if (oldValues != null) {
             int numValues = oldValues.length;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ed05321..3b96fd5 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5322,19 +5322,25 @@
 
         private DropBoxManager dropBox;
 
-        public DropBoxReporter() {
-            dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
-        }
+        public DropBoxReporter() {}
 
         @Override
         public void addData(String tag, byte[] data, int flags) {
+            ensureInitialized();
             dropBox.addData(tag, data, flags);
         }
 
         @Override
         public void addText(String tag, String data) {
+            ensureInitialized();
             dropBox.addText(tag, data);
         }
+
+        private synchronized void ensureInitialized() {
+            if (dropBox == null) {
+                dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
+            }
+        }
     }
 
     public static void main(String[] args) {
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 3e545f9..abe12dc 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -22,6 +22,7 @@
 import android.annotation.AttrRes;
 import android.annotation.DrawableRes;
 import android.annotation.StringRes;
+import android.annotation.StyleRes;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.database.Cursor;
@@ -134,6 +135,7 @@
      * {@code context}'s theme.
      *
      * @param context the parent context
+     * @see android.R.styleable#Theme_alertDialogTheme
      */
     protected AlertDialog(Context context) {
         this(context, 0);
@@ -155,6 +157,7 @@
      * {@code context}'s theme.
      *
      * @param context the parent context
+     * @see android.R.styleable#Theme_alertDialogTheme
      */
     protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
         this(context, 0);
@@ -187,9 +190,15 @@
      * @param themeResId the resource ID of the theme against which to inflate
      *                   this dialog, or {@code 0} to use the parent
      *                   {@code context}'s default alert dialog theme
+     * @see android.R.styleable#Theme_alertDialogTheme
      */
-    protected AlertDialog(Context context, @AttrRes int themeResId) {
-        super(context, resolveDialogTheme(context, themeResId));
+    protected AlertDialog(Context context, @StyleRes int themeResId) {
+        this(context, themeResId, true);
+    }
+
+    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
+                createContextThemeWrapper);
 
         mWindow.alwaysReadCloseOnTouchAttr();
         mAlert = new AlertController(getContext(), this, getWindow());
@@ -428,7 +437,6 @@
 
     public static class Builder {
         private final AlertController.AlertParams P;
-        private int mThemeResId;
 
         /**
          * Creates a builder for an alert dialog that uses the default alert
@@ -473,7 +481,6 @@
         public Builder(Context context, int themeResId) {
             P = new AlertController.AlertParams(new ContextThemeWrapper(
                     context, resolveDialogTheme(context, themeResId)));
-            mThemeResId = themeResId;
         }
 
         /**
@@ -1075,7 +1082,7 @@
          * create and display the dialog.
          */
         public AlertDialog create() {
-            final AlertDialog dialog = new AlertDialog(P.mContext, mThemeResId);
+            final AlertDialog dialog = new AlertDialog(P.mContext);
             P.apply(dialog.mAlert);
             dialog.setCancelable(P.mCancelable);
             if (P.mCancelable) {
diff --git a/core/java/android/app/AssistAction.java b/core/java/android/app/AssistAction.java
new file mode 100644
index 0000000..eb33542
--- /dev/null
+++ b/core/java/android/app/AssistAction.java
@@ -0,0 +1,277 @@
+/*
+ * 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 android.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Helper class for building a {@link Bundle} representing an action being performed by the user,
+ * to be included in the Bundle generated by {@link Activity#onProvideAssistData}.
+ *
+ * @see Activity#onProvideAssistData
+ */
+public final class AssistAction {
+
+    /**
+     * Key name for the Bundle containing the schema.org representation of
+     * an action performed, and should be stored in the Bundle generated by
+     * {@link Activity#onProvideAssistData}.
+     */
+    public static final String ASSIST_ACTION_KEY = "android:assist_action";
+
+    /** Bundle key to specify the schema.org ID of the content. */
+    public static final String KEY_ID = "@id";
+
+    /** Bundle key to specify the schema.org type of the content. */
+    public static final String KEY_TYPE = "@type";
+
+    /** Bundle key to specify the name of the content. */
+    public static final String KEY_NAME = "name";
+
+    /** Bundle key to specify the description of the content. */
+    public static final String KEY_DESCRIPTION = "description";
+
+    /** Bundle key to specify the URL of the content. */
+    public static final String KEY_URL = "url";
+
+    /** Bundle key to specify the object of an action. */
+    public static final String KEY_ACTION_OBJECT = "object";
+
+    /** Bundle key to specify the action's status. */
+    public static final String KEY_ACTION_STATUS = "actionStatus";
+
+    /** The act of editing by adding an object to a collection. */
+    public static final String TYPE_ADD_ACTION = "AddAction";
+
+    /** The act of bookmarking an object. */
+    public static final String TYPE_BOOKMARK_ACTION = "BookmarkAction";
+
+    /** The act of liking an object. */
+    public static final String TYPE_LIKE_ACTION = "LikeAction";
+
+    /** The act of consuming audio content. */
+    public static final String TYPE_LISTEN_ACTION = "ListenAction";
+
+    /** The act of consuming static visual content. */
+    public static final String TYPE_VIEW_ACTION = "ViewAction";
+
+    /** The act of expressing a desire about the object. */
+    public static final String TYPE_WANT_ACTION = "WantAction";
+
+    /** The act of watching an object. */
+    public static final String TYPE_WATCH_ACTION = "WatchAction";
+
+    /** The status of an active action. */
+    public static final String STATUS_TYPE_ACTIVE = "ActiveActionStatus";
+
+    /** The status of a completed action. */
+    public static final String STATUS_TYPE_COMPLETED = "CompletedActionStatus";
+
+    private AssistAction() {
+    }
+
+    /**
+     * Update the Bundle passed into {@link Activity#onProvideAssistData} with the action Bundle,
+     * built with {@link ActionBuilder}.
+     *
+     * @param assistDataBundle The Bundle provided to {@link Activity#onProvideAssistData}.
+     * @param actionBundle The Bundle representing an schema.org action.
+     */
+    public static void updateAssistData(Bundle assistDataBundle, Bundle actionBundle) {
+        Preconditions.checkNotNull(assistDataBundle);
+        Preconditions.checkNotNull(actionBundle);
+
+        Preconditions.checkNotNull(actionBundle.getString(KEY_TYPE),
+                "The '@type' property is required in the provided actionBundle");
+        assistDataBundle.putParcelable(ASSIST_ACTION_KEY, actionBundle);
+    }
+
+    /**
+     * Builds a {@link Bundle} representing a schema.org entity.
+     */
+    public static final class ThingBuilder {
+        private final Bundle mBundle;
+
+        public ThingBuilder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Sets the name of the content.
+         *
+         * @param name The name of the content.
+         */
+        public ThingBuilder setName(@Nullable String name) {
+            set(KEY_NAME, name);
+            return this;
+        }
+
+        /**
+         * Sets the app URI of the content.
+         *
+         * @param uri The app URI of the content.
+         */
+        public ThingBuilder setUrl(@Nullable Uri uri) {
+            if (uri != null) {
+                set(KEY_URL, uri.toString());
+            }
+            return this;
+        }
+
+        /**
+         * Sets the ID of the content.
+         *
+         * @param id Set the ID of the content.
+         */
+        public ThingBuilder setId(@Nullable String id) {
+            set(KEY_ID, id);
+            return this;
+        }
+
+        /**
+        * Sets the schema.org type of the content.
+        *
+        * @param type The schema.org type.
+        */
+        public ThingBuilder setType(@Nullable String type) {
+            set(KEY_TYPE, type);
+            return this;
+        }
+
+        /**
+         * Sets the optional description of the content.
+         *
+         * @param description The description of the content.
+         */
+        public ThingBuilder setDescription(@Nullable String description) {
+            set(KEY_DESCRIPTION, description);
+            return this;
+        }
+
+        /**
+         * Sets a property of the content.
+         *
+         * @param key The schema.org property. Must not be null.
+         * @param value The value of the schema.org property.
+         *              If null, the value will be ignored.
+         */
+        public ThingBuilder set(@NonNull String key, @Nullable String value) {
+            if (value != null) {
+                mBundle.putString(key, value);
+            }
+            return this;
+        }
+
+        /**
+         * Sets a property of the content.
+         *
+         * @param key The schema.org property. Must not be null.
+         * @param value The value of the schema.org property represented as a bundle.
+         *              If null, the value will be ignored.
+         */
+        public ThingBuilder set(@NonNull String key, @Nullable Bundle value) {
+            if (value != null) {
+                mBundle.putParcelable(key, value);
+            }
+            return this;
+        }
+
+        /**
+         * Build the {@link Bundle} object representing the schema.org entity.
+         */
+        public Bundle build() {
+            return mBundle;
+        }
+    }
+
+    /**
+     * Builds a {@link Bundle} representing a schema.org action.
+     */
+    public static final class ActionBuilder {
+        private final Bundle mBundle;
+
+        public ActionBuilder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Sets the schema.org type of the action.
+         *
+         * @param type The schema.org type.
+         */
+        public ActionBuilder setType(@Nullable String type) {
+            set(KEY_TYPE, type);
+            return this;
+        }
+
+        /**
+         * Sets the schema.org object of the action.
+         *
+         * @param object The schema.org object of the action.
+         */
+        public ActionBuilder setObject(@Nullable Bundle object) {
+            set(KEY_ACTION_OBJECT, object);
+            return this;
+        }
+
+        /**
+         * Sets a property of the action.
+         *
+         * @param key The schema.org property. Must not be null.
+         * @param value The value of the schema.org property.
+         *              If null, the value will be ignored.
+         */
+        public ActionBuilder set(@NonNull String key, @Nullable String value) {
+            if (value != null) {
+                mBundle.putString(key, value);
+            }
+            return this;
+        }
+
+        /**
+         * Sets a property of the action.
+         *
+         * @param key The schema.org property. Must not be null.
+         * @param value The value of the schema.org property represented as a bundle.
+         *              If null, the value will be ignored.
+         */
+        public ActionBuilder set(@NonNull String key, @Nullable Bundle value) {
+            if (value != null) {
+                mBundle.putParcelable(key, value);
+            }
+            return this;
+        }
+
+        /**
+         * Build the {@link Bundle} object representing the schema.org action.
+         */
+        public Bundle build() {
+            if (TextUtils.isEmpty(mBundle.getString(KEY_TYPE, null))) {
+                // Defaults to the base action type http://schema.org/Action.
+                setType("Action");
+            }
+
+            return mBundle;
+        }
+    }
+}
diff --git a/core/java/android/app/AssistContent.java b/core/java/android/app/AssistContent.java
index ace4af7..cb1a3f5 100644
--- a/core/java/android/app/AssistContent.java
+++ b/core/java/android/app/AssistContent.java
@@ -51,7 +51,7 @@
     /**
      * Sets the Intent associated with the content, describing the current top-level context of
      * the activity.  If this contains a reference to a piece of data related to the activity,
-     * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibilty
+     * be sure to set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} so the accessibility
      * service can access it.
      */
     public void setIntent(Intent intent) {
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 9defcbe5..786a52f 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -20,10 +20,11 @@
 import android.annotation.DrawableRes;
 import android.annotation.IdRes;
 import android.annotation.LayoutRes;
+import android.annotation.NonNull;
 import android.annotation.StringRes;
-import com.android.internal.app.WindowDecorActionBar;
 
 import android.annotation.Nullable;
+import android.annotation.StyleRes;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
@@ -56,6 +57,9 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+
 import java.lang.ref.WeakReference;
 
 /**
@@ -130,52 +134,58 @@
     };
 
     /**
-     * Create a Dialog window that uses the default dialog frame style.
-     * 
-     * @param context The Context the Dialog is to run it.  In particular, it
-     *                uses the window manager and theme in this context to
-     *                present its UI.
+     * Creates a dialog window that uses the default dialog theme.
+     * <p>
+     * The supplied {@code context} is used to obtain the window manager and
+     * base theme used to present the dialog.
+     *
+     * @param context the context in which the dialog should run
+     * @see android.R.styleable#Theme_dialogTheme
      */
-    public Dialog(Context context) {
+    public Dialog(@NonNull Context context) {
         this(context, 0, true);
     }
 
     /**
-     * Create a Dialog window that uses a custom dialog style.
+     * Creates a dialog window that uses a custom dialog style.
+     * <p>
+     * The supplied {@code context} is used to obtain the window manager and
+     * base theme used to present the dialog.
+     * <p>
+     * The supplied {@code theme} is applied on top of the context's theme. See
+     * <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
+     * Style and Theme Resources</a> for more information about defining and
+     * using styles.
      *
-     * @param context The Context in which the Dialog should run. In particular, it
-     *                uses the window manager and theme from this context to
-     *                present its UI.
-     * @param theme A style resource describing the theme to use for the
-     * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
-     * and Theme Resources</a> for more information about defining and using
-     * styles.  This theme is applied on top of the current theme in
-     * <var>context</var>.  If 0, the default dialog theme will be used.
+     * @param context the context in which the dialog should run
+     * @param themeResId a style resource describing the theme to use for the
+     *              window, or {@code 0} to use the default dialog theme
      */
-    public Dialog(Context context, int theme) {
-        this(context, theme, true);
+    public Dialog(@NonNull Context context, @StyleRes int themeResId) {
+        this(context, themeResId, true);
     }
 
-    Dialog(Context context, int theme, boolean createContextThemeWrapper) {
+    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
         if (createContextThemeWrapper) {
-            if (theme == 0) {
-                TypedValue outValue = new TypedValue();
-                context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
-                        outValue, true);
-                theme = outValue.resourceId;
+            if (themeResId == 0) {
+                final TypedValue outValue = new TypedValue();
+                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
+                themeResId = outValue.resourceId;
             }
-            mContext = new ContextThemeWrapper(context, theme);
+            mContext = new ContextThemeWrapper(context, themeResId);
         } else {
             mContext = context;
         }
 
-        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
-        Window w = new PhoneWindow(mContext);
+        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+        final Window w = new PhoneWindow(mContext);
         mWindow = w;
         w.setCallback(this);
         w.setOnWindowDismissedCallback(this);
         w.setWindowManager(mWindowManager, null, null);
         w.setGravity(Gravity.CENTER);
+
         mListenersHandler = new ListenersHandler(this);
     }
 
@@ -184,14 +194,13 @@
      * @hide
      */
     @Deprecated
-    protected Dialog(Context context, boolean cancelable,
-            Message cancelCallback) {
+    protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) {
         this(context);
         mCancelable = cancelable;
         mCancelMessage = cancelCallback;
     }
 
-    protected Dialog(Context context, boolean cancelable,
+    protected Dialog(@NonNull Context context, boolean cancelable,
             OnCancelListener cancelListener) {
         this(context);
         mCancelable = cancelable;
@@ -203,6 +212,7 @@
      * 
      * @return Context The Context used by the Dialog.
      */
+    @NonNull
     public final Context getContext() {
         return mContext;
     }
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c053c83..e84a8da 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -140,13 +141,13 @@
         } else {
             decor.getViewTreeObserver()
                     .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-                @Override
-                public boolean onPreDraw() {
-                    decor.getViewTreeObserver().removeOnPreDrawListener(this);
-                    viewsReady(sharedElements);
-                    return true;
-                }
-            });
+                        @Override
+                        public boolean onPreDraw() {
+                            decor.getViewTreeObserver().removeOnPreDrawListener(this);
+                            viewsReady(sharedElements);
+                            return true;
+                        }
+                    });
         }
     }
 
@@ -383,23 +384,33 @@
         }
         final Bundle sharedElementState = mSharedElementsBundle;
         mSharedElementsBundle = null;
-        final View decorView = getDecor();
-        if (decorView != null) {
-            decorView.getViewTreeObserver()
-                    .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
-                        @Override
-                        public boolean onPreDraw() {
-                            decorView.getViewTreeObserver().removeOnPreDrawListener(this);
-                            startTransition(new Runnable() {
+        OnSharedElementsReadyListener listener = new OnSharedElementsReadyListener() {
+            @Override
+            public void onSharedElementsReady() {
+                final View decorView = getDecor();
+                if (decorView != null) {
+                    decorView.getViewTreeObserver()
+                            .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                                 @Override
-                                public void run() {
-                                    startSharedElementTransition(sharedElementState);
+                                public boolean onPreDraw() {
+                                    decorView.getViewTreeObserver().removeOnPreDrawListener(this);
+                                    startTransition(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            startSharedElementTransition(sharedElementState);
+                                        }
+                                    });
+                                    return false;
                                 }
                             });
-                            return false;
-                        }
-                    });
-            decorView.invalidate();
+                    decorView.invalidate();
+                }
+            }
+        };
+        if (mListener == null) {
+            listener.onSharedElementsReady();
+        } else {
+            mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements, listener);
         }
     }
 
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index dd3df47..169952ad 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -18,6 +18,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.app.SharedElementCallback.OnSharedElementsReadyListener;
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -27,6 +28,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.ResultReceiver;
 import android.transition.Transition;
 import android.transition.TransitionManager;
 import android.view.View;
@@ -408,21 +410,41 @@
             if (!mSharedElementNotified) {
                 mSharedElementNotified = true;
                 delayCancel();
-                mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
-            }
-            if (!mExitNotified && mExitComplete) {
-                mExitNotified = true;
-                mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
-                mResultReceiver = null; // done talking
-                ViewGroup decorView = getDecor();
-                if (!mIsReturning && decorView != null) {
-                    decorView.suppressLayout(false);
+                if (mListener == null) {
+                    mResultReceiver.send(MSG_TAKE_SHARED_ELEMENTS, mSharedElementBundle);
+                    notifyExitComplete();
+                } else {
+                    final ResultReceiver resultReceiver = mResultReceiver;
+                    final Bundle sharedElementBundle = mSharedElementBundle;
+                    mListener.onSharedElementsArrived(mSharedElementNames, mSharedElements,
+                            new OnSharedElementsReadyListener() {
+                                @Override
+                                public void onSharedElementsReady() {
+                                    resultReceiver.send(MSG_TAKE_SHARED_ELEMENTS,
+                                            sharedElementBundle);
+                                    notifyExitComplete();
+                                }
+                            });
                 }
-                finishIfNecessary();
+            } else {
+                notifyExitComplete();
             }
         }
     }
 
+    private void notifyExitComplete() {
+        if (!mExitNotified && mExitComplete) {
+            mExitNotified = true;
+            mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
+            mResultReceiver = null; // done talking
+            ViewGroup decorView = getDecor();
+            if (!mIsReturning && decorView != null) {
+                decorView.suppressLayout(false);
+            }
+            finishIfNecessary();
+        }
+    }
+
     private void finishIfNecessary() {
         if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty() ||
                 mSharedElementsHidden)) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 33262b3..e2230da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -76,12 +76,10 @@
     boolean matchesCallFilter(in Bundle extras);
     boolean isSystemConditionProviderEnabled(String path);
 
+    int getZenMode();
     ZenModeConfig getZenModeConfig();
-    boolean setZenModeConfig(in ZenModeConfig config);
-    oneway void setZenMode(int mode);
+    boolean setZenModeConfig(in ZenModeConfig config, String reason);
+    oneway void setZenMode(int mode, in Uri conditionId, String reason);
     oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
     oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
-    oneway void setZenModeCondition(in Condition condition);
-    oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
-    Condition[] getAutomaticZenModeConditions();
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 479327d..fa61e18 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -20,6 +20,7 @@
 import android.app.Notification.Builder;
 import android.content.ComponentName;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -27,7 +28,7 @@
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.UserHandle;
-import android.service.notification.Condition;
+import android.provider.Settings.Global;
 import android.service.notification.IConditionListener;
 import android.service.notification.ZenModeConfig;
 import android.util.Log;
@@ -282,10 +283,10 @@
     /**
      * @hide
      */
-    public void setZenMode(int mode) {
+    public void setZenMode(int mode, Uri conditionId, String reason) {
         INotificationManager service = getService();
         try {
-            service.setZenMode(mode);
+            service.setZenMode(mode, conditionId, reason);
         } catch (RemoteException e) {
         }
     }
@@ -293,6 +294,18 @@
     /**
      * @hide
      */
+    public boolean setZenModeConfig(ZenModeConfig config, String reason) {
+        INotificationManager service = getService();
+        try {
+            return service.setZenModeConfig(config, reason);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * @hide
+     */
     public void requestZenModeConditions(IConditionListener listener, int relevance) {
         INotificationManager service = getService();
         try {
@@ -304,24 +317,22 @@
     /**
      * @hide
      */
-    public void setZenModeCondition(Condition exitCondition) {
+    public int getZenMode() {
         INotificationManager service = getService();
         try {
-            service.setZenModeCondition(exitCondition);
+            return service.getZenMode();
         } catch (RemoteException e) {
         }
+        return Global.ZEN_MODE_OFF;
     }
 
     /**
      * @hide
      */
-    public Condition getZenModeCondition() {
+    public ZenModeConfig getZenModeConfig() {
         INotificationManager service = getService();
         try {
-            final ZenModeConfig config = service.getZenModeConfig();
-            if (config != null) {
-                return config.exitCondition;
-            }
+            return service.getZenModeConfig();
         } catch (RemoteException e) {
         }
         return null;
diff --git a/core/java/android/app/SharedElementCallback.java b/core/java/android/app/SharedElementCallback.java
index 6ac2401..e58b7fb 100644
--- a/core/java/android/app/SharedElementCallback.java
+++ b/core/java/android/app/SharedElementCallback.java
@@ -221,4 +221,42 @@
         }
         return view;
     }
+
+    /**
+     * Called during an Activity Transition when the shared elements have arrived at the
+     * final location and are ready to be transferred. This method is called for both the
+     * source and destination Activities.
+     * <p>
+     * When the shared elements are ready to be transferred,
+     * {@link OnSharedElementsReadyListener#onSharedElementsReady()}
+     * must be called to trigger the transfer.
+     * <p>
+     * The default behavior is to trigger the transfer immediately.
+     *
+     * @param sharedElementNames The names of the shared elements that are being transferred..
+     * @param sharedElements The shared elements that are part of the View hierarchy.
+     * @param listener The listener to call when the shared elements are ready to be hidden
+     *                 in the source Activity or shown in the destination Activity.
+     */
+    public void onSharedElementsArrived(List<String> sharedElementNames,
+            List<View> sharedElements, OnSharedElementsReadyListener listener) {
+        listener.onSharedElementsReady();
+    }
+
+    /**
+     * Listener to be called after {@link
+     * SharedElementCallback#onSharedElementsArrived(List, List, OnSharedElementsReadyListener)}
+     * when the shared elements are ready to be hidden in the source Activity and shown in the
+     * destination Activity.
+     */
+    public interface OnSharedElementsReadyListener {
+
+        /**
+         * Call this method during or after the OnSharedElementsReadyListener has been received
+         * in {@link SharedElementCallback#onSharedElementsArrived(List, List,
+         * OnSharedElementsReadyListener)} to indicate that the shared elements are ready to be
+         * hidden in the source and shown in the destination Activity.
+         */
+        void onSharedElementsReady();
+    }
 }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 93ea299..5dbfa6a 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -36,7 +36,7 @@
 
 /**
  * This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}. It
+ * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
  * can also request different types of callbacks for delivering the result.
  * <p>
  * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 303b709..d5461eba 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1318,6 +1318,15 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports high fidelity sensor processing
+     * capabilities.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_HIFI_SENSORS =
+            "android.hardware.sensor.hifi_sensors";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a telephony radio with data
      * communication support.
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 6b9d3f8..7523675 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2778,7 +2778,7 @@
     }
 
     /**
-     * Check if one of the IntentFilter as an action VIEW and a HTTP/HTTPS data URI
+     * Check if one of the IntentFilter as both actions DEFAULT / VIEW and a HTTP/HTTPS data URI
      */
     private static boolean hasDomainURLs(Package pkg) {
         if (pkg == null || pkg.activities == null) return false;
@@ -2792,6 +2792,7 @@
             for (int m=0; m<countFilters; m++) {
                 ActivityIntentInfo aii = filters.get(m);
                 if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
+                if (!aii.hasAction(Intent.ACTION_DEFAULT)) continue;
                 if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
                         aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
                     Slog.d(TAG, "hasDomainURLs:true for package:" + pkg.packageName);
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index b5b89dd..581fe7f 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -35,34 +35,38 @@
     private static final String TAG = "Cursor";
 
     /**
-     * @deprecated This is never updated by this class and should not be used
+     * @removed This field should not be used.
      */
-    @Deprecated
     protected HashMap<Long, Map<String, Object>> mUpdatedRows;
 
-    protected int mPos;
-
     /**
-     * This must be set to the index of the row ID column by any
-     * subclass that wishes to support updates.
-     *
-     * @deprecated This field should not be used.
+     * @removed This field should not be used.
      */
-    @Deprecated
     protected int mRowIdColumnIndex;
 
     /**
-     * If {@link #mRowIdColumnIndex} is not -1 this contains contains the value of
-     * the column at {@link #mRowIdColumnIndex} for the current row this cursor is
-     * pointing at.
-     *
-     * @deprecated This field should not be used.
+     * @removed This field should not be used.
      */
-    @Deprecated
     protected Long mCurrentRowID;
 
+    /**
+     * @deprecated Use {@link #getPosition()} instead.
+     */
+    @Deprecated
+    protected int mPos;
+
+    /**
+     * @deprecated Use {@link #isClosed()} instead.
+     */
+    @Deprecated
     protected boolean mClosed;
+
+    /**
+     * @deprecated Do not use.
+     */
+    @Deprecated
     protected ContentResolver mContentResolver;
+
     private Uri mNotifyUri;
 
     private final Object mSelfObserverLock = new Object();
@@ -76,18 +80,28 @@
 
     /* -------------------------------------------------------- */
     /* These need to be implemented by subclasses */
+    @Override
     abstract public int getCount();
 
+    @Override
     abstract public String[] getColumnNames();
 
+    @Override
     abstract public String getString(int column);
+    @Override
     abstract public short getShort(int column);
+    @Override
     abstract public int getInt(int column);
+    @Override
     abstract public long getLong(int column);
+    @Override
     abstract public float getFloat(int column);
+    @Override
     abstract public double getDouble(int column);
+    @Override
     abstract public boolean isNull(int column);
 
+    @Override
     public int getType(int column) {
         // Reflects the assumption that all commonly used field types (meaning everything
         // but blobs) are convertible to strings so it should be safe to call
@@ -96,6 +110,7 @@
     }
 
     // TODO implement getBlob in all cursor types
+    @Override
     public byte[] getBlob(int column) {
         throw new UnsupportedOperationException("getBlob is not supported");
     }
@@ -108,14 +123,17 @@
      *
      * @return The pre-filled window that backs this cursor, or null if none.
      */
+    @Override
     public CursorWindow getWindow() {
         return null;
     }
 
+    @Override
     public int getColumnCount() {
         return getColumnNames().length;
     }
 
+    @Override
     public void deactivate() {
         onDeactivateOrClose();
     }
@@ -129,6 +147,7 @@
         mDataSetObservable.notifyInvalidated();
     }
 
+    @Override
     public boolean requery() {
         if (mSelfObserver != null && mSelfObserverRegistered == false) {
             mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
@@ -138,10 +157,12 @@
         return true;
     }
 
+    @Override
     public boolean isClosed() {
         return mClosed;
     }
 
+    @Override
     public void close() {
         mClosed = true;
         mContentObservable.unregisterAll();
@@ -158,11 +179,13 @@
      * @param newPosition the position that we're moving to
      * @return true if the move is successful, false otherwise
      */
+    @Override
     public boolean onMove(int oldPosition, int newPosition) {
         return true;
     }
 
 
+    @Override
     public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
         // Default implementation, uses getString
         String result = getString(columnIndex);
@@ -183,15 +206,14 @@
     /* Implementation */
     public AbstractCursor() {
         mPos = -1;
-        mRowIdColumnIndex = -1;
-        mCurrentRowID = null;
-        mUpdatedRows = new HashMap<Long, Map<String, Object>>();
     }
 
+    @Override
     public final int getPosition() {
         return mPos;
     }
 
+    @Override
     public final boolean moveToPosition(int position) {
         // Make sure position isn't past the end of the cursor
         final int count = getCount();
@@ -216,9 +238,6 @@
             mPos = -1;
         } else {
             mPos = position;
-            if (mRowIdColumnIndex != -1) {
-                mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
-            }
         }
 
         return result;
@@ -229,35 +248,43 @@
         DatabaseUtils.cursorFillWindow(this, position, window);
     }
 
+    @Override
     public final boolean move(int offset) {
         return moveToPosition(mPos + offset);
     }
 
+    @Override
     public final boolean moveToFirst() {
         return moveToPosition(0);
     }
 
+    @Override
     public final boolean moveToLast() {
         return moveToPosition(getCount() - 1);
     }
 
+    @Override
     public final boolean moveToNext() {
         return moveToPosition(mPos + 1);
     }
 
+    @Override
     public final boolean moveToPrevious() {
         return moveToPosition(mPos - 1);
     }
 
+    @Override
     public final boolean isFirst() {
         return mPos == 0 && getCount() != 0;
     }
 
+    @Override
     public final boolean isLast() {
         int cnt = getCount();
         return mPos == (cnt - 1) && cnt != 0;
     }
 
+    @Override
     public final boolean isBeforeFirst() {
         if (getCount() == 0) {
             return true;
@@ -265,6 +292,7 @@
         return mPos == -1;
     }
 
+    @Override
     public final boolean isAfterLast() {
         if (getCount() == 0) {
             return true;
@@ -272,6 +300,7 @@
         return mPos == getCount();
     }
 
+    @Override
     public int getColumnIndex(String columnName) {
         // Hack according to bug 903852
         final int periodIndex = columnName.lastIndexOf('.');
@@ -297,6 +326,7 @@
         return -1;
     }
 
+    @Override
     public int getColumnIndexOrThrow(String columnName) {
         final int index = getColumnIndex(columnName);
         if (index < 0) {
@@ -305,14 +335,17 @@
         return index;
     }
 
+    @Override
     public String getColumnName(int columnIndex) {
         return getColumnNames()[columnIndex];
     }
 
+    @Override
     public void registerContentObserver(ContentObserver observer) {
         mContentObservable.registerObserver(observer);
     }
 
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         // cursor will unregister all observers when it close
         if (!mClosed) {
@@ -320,10 +353,12 @@
         }
     }
 
+    @Override
     public void registerDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.registerObserver(observer);
     }
 
+    @Override
     public void unregisterDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.unregisterObserver(observer);
     }
@@ -350,6 +385,7 @@
      * @param notifyUri The URI to watch for changes. This can be a
      * specific row URI, or a base URI for a whole class of content.
      */
+    @Override
     public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
         setNotificationUri(cr, notifyUri, UserHandle.myUserId());
     }
@@ -368,31 +404,29 @@
         }
     }
 
+    @Override
     public Uri getNotificationUri() {
         synchronized (mSelfObserverLock) {
             return mNotifyUri;
         }
     }
 
+    @Override
     public boolean getWantsAllOnMoveCalls() {
         return false;
     }
 
-    /**
-     * Sets a {@link Bundle} that will be returned by {@link #getExtras()}.  <code>null</code> will
-     * be converted into {@link Bundle#EMPTY}.
-     *
-     * @param extras {@link Bundle} to set.
-     * @hide
-     */
+    @Override
     public void setExtras(Bundle extras) {
         mExtras = (extras == null) ? Bundle.EMPTY : extras;
     }
 
+    @Override
     public Bundle getExtras() {
         return mExtras;
     }
 
+    @Override
     public Bundle respond(Bundle extras) {
         return Bundle.EMPTY;
     }
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 98c7043..8576715 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -41,7 +41,6 @@
     public void initialize(BulkCursorDescriptor d) {
         mBulkCursor = d.cursor;
         mColumns = d.columnNames;
-        mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
         mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
         mCount = d.count;
         if (d.window != null) {
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index fc2a885..d10c9b8 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -444,6 +444,13 @@
     boolean getWantsAllOnMoveCalls();
 
     /**
+     * Sets a {@link Bundle} that will be returned by {@link #getExtras()}.
+     *
+     * @param extras {@link Bundle} to set, or null to set an empty bundle.
+     */
+    void setExtras(Bundle extras);
+
+    /**
      * Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band
      * metadata to their users. One use of this is for reporting on the progress of network requests
      * that are required to fetch data for the cursor.
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index d8fcb17..63a2792 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -45,163 +45,210 @@
         return mCursor;
     }
 
+    @Override
     public void close() {
         mCursor.close(); 
     }
  
+    @Override
     public boolean isClosed() {
         return mCursor.isClosed();
     }
 
+    @Override
     public int getCount() {
         return mCursor.getCount();
     }
 
+    @Override
+    @Deprecated
     public void deactivate() {
         mCursor.deactivate();
     }
 
+    @Override
     public boolean moveToFirst() {
         return mCursor.moveToFirst();
     }
 
+    @Override
     public int getColumnCount() {
         return mCursor.getColumnCount();
     }
 
+    @Override
     public int getColumnIndex(String columnName) {
         return mCursor.getColumnIndex(columnName);
     }
 
+    @Override
     public int getColumnIndexOrThrow(String columnName)
             throws IllegalArgumentException {
         return mCursor.getColumnIndexOrThrow(columnName);
     }
 
+    @Override
     public String getColumnName(int columnIndex) {
          return mCursor.getColumnName(columnIndex);
     }
 
+    @Override
     public String[] getColumnNames() {
         return mCursor.getColumnNames();
     }
 
+    @Override
     public double getDouble(int columnIndex) {
         return mCursor.getDouble(columnIndex);
     }
 
+    @Override
+    public void setExtras(Bundle extras) {
+        mCursor.setExtras(extras);
+    }
+
+    @Override
     public Bundle getExtras() {
         return mCursor.getExtras();
     }
 
+    @Override
     public float getFloat(int columnIndex) {
         return mCursor.getFloat(columnIndex);
     }
 
+    @Override
     public int getInt(int columnIndex) {
         return mCursor.getInt(columnIndex);
     }
 
+    @Override
     public long getLong(int columnIndex) {
         return mCursor.getLong(columnIndex);
     }
 
+    @Override
     public short getShort(int columnIndex) {
         return mCursor.getShort(columnIndex);
     }
 
+    @Override
     public String getString(int columnIndex) {
         return mCursor.getString(columnIndex);
     }
     
+    @Override
     public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
         mCursor.copyStringToBuffer(columnIndex, buffer);
     }
 
+    @Override
     public byte[] getBlob(int columnIndex) {
         return mCursor.getBlob(columnIndex);
     }
     
+    @Override
     public boolean getWantsAllOnMoveCalls() {
         return mCursor.getWantsAllOnMoveCalls();
     }
 
+    @Override
     public boolean isAfterLast() {
         return mCursor.isAfterLast();
     }
 
+    @Override
     public boolean isBeforeFirst() {
         return mCursor.isBeforeFirst();
     }
 
+    @Override
     public boolean isFirst() {
         return mCursor.isFirst();
     }
 
+    @Override
     public boolean isLast() {
         return mCursor.isLast();
     }
 
+    @Override
     public int getType(int columnIndex) {
         return mCursor.getType(columnIndex);
     }
 
+    @Override
     public boolean isNull(int columnIndex) {
         return mCursor.isNull(columnIndex);
     }
 
+    @Override
     public boolean moveToLast() {
         return mCursor.moveToLast();
     }
 
+    @Override
     public boolean move(int offset) {
         return mCursor.move(offset);
     }
 
+    @Override
     public boolean moveToPosition(int position) {
         return mCursor.moveToPosition(position);
     }
 
+    @Override
     public boolean moveToNext() {
         return mCursor.moveToNext();
     }
 
+    @Override
     public int getPosition() {
         return mCursor.getPosition();
     }
 
+    @Override
     public boolean moveToPrevious() {
         return mCursor.moveToPrevious();
     }
 
+    @Override
     public void registerContentObserver(ContentObserver observer) {
-        mCursor.registerContentObserver(observer);   
+        mCursor.registerContentObserver(observer);
     }
 
+    @Override
     public void registerDataSetObserver(DataSetObserver observer) {
-        mCursor.registerDataSetObserver(observer);   
+        mCursor.registerDataSetObserver(observer);
     }
 
+    @Override
+    @Deprecated
     public boolean requery() {
         return mCursor.requery();
     }
 
+    @Override
     public Bundle respond(Bundle extras) {
         return mCursor.respond(extras);
     }
 
+    @Override
     public void setNotificationUri(ContentResolver cr, Uri uri) {
-        mCursor.setNotificationUri(cr, uri);        
+        mCursor.setNotificationUri(cr, uri);
     }
 
+    @Override
     public Uri getNotificationUri() {
         return mCursor.getNotificationUri();
     }
 
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
-        mCursor.unregisterContentObserver(observer);        
+        mCursor.unregisterContentObserver(observer);
     }
 
+    @Override
     public void unregisterDataSetObserver(DataSetObserver observer) {
         mCursor.unregisterDataSetObserver(observer);
     }
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 5a1a8e2..2dc5ca4 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -105,7 +105,6 @@
         mQuery = query;
 
         mColumns = query.getColumnNames();
-        mRowIdColumnIndex = DatabaseUtils.findRowIdColumnIndex(mColumns);
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5ee8fb3..3087e1d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -880,6 +880,15 @@
             "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
 
     /**
+     * Activity Action: Show Zen Mode schedule rule configuration settings.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS
+            = "android.settings.ZEN_MODE_SCHEDULE_RULE_SETTINGS";
+
+    /**
      * Activity Action: Show the regulatory information screen for the device.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -7218,6 +7227,18 @@
             return "ZEN_MODE_OFF";
         }
 
+        /** @hide */ public static boolean isValidZenMode(int value) {
+            switch (value) {
+                case Global.ZEN_MODE_OFF:
+                case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+                case Global.ZEN_MODE_ALARMS:
+                case Global.ZEN_MODE_NO_INTERRUPTIONS:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
         /**
          * Opaque value, changes when persisted zen mode configuration changes.
          *
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 5e2accd..e16691c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -59,9 +59,6 @@
     public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
 
     public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200;
-    public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201;
-    public static final int KM_TAG_DSA_P = KM_BIGNUM | 202;
-    public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203;
     public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
     public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
     public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -91,40 +88,17 @@
 
     // Algorithm values.
     public static final int KM_ALGORITHM_RSA = 1;
-    public static final int KM_ALGORITHM_DSA = 2;
-    public static final int KM_ALGORITHM_ECDSA = 3;
-    public static final int KM_ALGORITHM_ECIES = 4;
+    public static final int KM_ALGORITHM_EC = 3;
     public static final int KM_ALGORITHM_AES = 32;
-    public static final int KM_ALGORITHM_3DES = 33;
-    public static final int KM_ALGORITHM_SKIPJACK = 34;
-    public static final int KM_ALGORITHM_MARS = 48;
-    public static final int KM_ALGORITHM_RC6 = 49;
-    public static final int KM_ALGORITHM_SERPENT = 50;
-    public static final int KM_ALGORITHM_TWOFISH = 51;
-    public static final int KM_ALGORITHM_IDEA = 52;
-    public static final int KM_ALGORITHM_RC5 = 53;
-    public static final int KM_ALGORITHM_CAST5 = 54;
-    public static final int KM_ALGORITHM_BLOWFISH = 55;
-    public static final int KM_ALGORITHM_RC4 = 64;
-    public static final int KM_ALGORITHM_CHACHA20 = 65;
     public static final int KM_ALGORITHM_HMAC = 128;
 
     // Block modes.
     public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1;
     public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED;
     public static final int KM_MODE_CBC = 2;
-    public static final int KM_MODE_CBC_CTS = 3;
     public static final int KM_MODE_CTR = 4;
-    public static final int KM_MODE_OFB = 5;
-    public static final int KM_MODE_CFB = 6;
-    public static final int KM_MODE_XTS = 7;
     public static final int KM_MODE_FIRST_AUTHENTICATED = 32;
     public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED;
-    public static final int KM_MODE_OCB = 33;
-    public static final int KM_MODE_CCM = 34;
-    public static final int KM_MODE_FIRST_MAC = 128;
-    public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC;
-    public static final int KM_MODE_POLY1305 = 129;
 
     // Padding modes.
     public static final int KM_PAD_NONE = 1;
@@ -132,11 +106,7 @@
     public static final int KM_PAD_RSA_PSS = 3;
     public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
     public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
-    public static final int KM_PAD_ANSI_X923 = 32;
-    public static final int KM_PAD_ISO_10126 = 33;
-    public static final int KM_PAD_ZERO = 64;
-    public static final int KM_PAD_PKCS7 = 65;
-    public static final int KM_PAD_ISO_7816_4 = 66;
+    public static final int KM_PAD_PKCS7 = 64;
 
     // Digest modes.
     public static final int KM_DIGEST_NONE = 0;
@@ -146,9 +116,6 @@
     public static final int KM_DIGEST_SHA_2_256 = 4;
     public static final int KM_DIGEST_SHA_2_384 = 5;
     public static final int KM_DIGEST_SHA_2_512 = 6;
-    public static final int KM_DIGEST_SHA_3_256 = 7;
-    public static final int KM_DIGEST_SHA_3_384 = 8;
-    public static final int KM_DIGEST_SHA_3_512 = 9;
 
     // Key origins.
     public static final int KM_ORIGIN_HARDWARE = 0;
@@ -168,7 +135,6 @@
     // Key formats.
     public static final int KM_KEY_FORMAT_X509 = 0;
     public static final int KM_KEY_FORMAT_PKCS8 = 1;
-    public static final int KM_KEY_FORMAT_PKCS12 = 2;
     public static final int KM_KEY_FORMAT_RAW = 3;
 
     // User authenticators.
@@ -219,7 +185,6 @@
     public static final int KM_ERROR_INVALID_TAG = -40;
     public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
     public static final int KM_ERROR_INVALID_RESCOPING = -42;
-    public static final int KM_ERROR_INVALID_DSA_PARAMS = -43;
     public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
     public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
     public static final int KM_ERROR_OPERATION_CANCELLED = -46;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 2702457..56eb510 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -22,22 +22,25 @@
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.provider.Settings.Global;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Locale;
 import java.util.Objects;
-
-import com.android.internal.R;
+import java.util.UUID;
 
 /**
  * Persisted configuration for zen mode.
@@ -47,10 +50,6 @@
 public class ZenModeConfig implements Parcelable {
     private static String TAG = "ZenModeConfig";
 
-    public static final String SLEEP_MODE_NIGHTS = "nights";
-    public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
-    public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
-
     public static final int SOURCE_ANYONE = 0;
     public static final int SOURCE_CONTACT = 1;
     public static final int SOURCE_STAR = 2;
@@ -60,6 +59,7 @@
             Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
     public static final int[] WEEKNIGHT_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
             Calendar.WEDNESDAY, Calendar.THURSDAY };
+    public static final int[] WEEKEND_DAYS = { Calendar.FRIDAY, Calendar.SATURDAY };
 
     public static final int[] MINUTE_BUCKETS = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
     private static final int SECONDS_MS = 1000;
@@ -68,24 +68,18 @@
 
     private static final boolean DEFAULT_ALLOW_REMINDERS = true;
     private static final boolean DEFAULT_ALLOW_EVENTS = true;
+    private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false;
 
-    private static final int XML_VERSION = 1;
+    private static final int XML_VERSION = 2;
     private static final String ZEN_TAG = "zen";
     private static final String ZEN_ATT_VERSION = "version";
     private static final String ALLOW_TAG = "allow";
     private static final String ALLOW_ATT_CALLS = "calls";
+    private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers";
     private static final String ALLOW_ATT_MESSAGES = "messages";
     private static final String ALLOW_ATT_FROM = "from";
     private static final String ALLOW_ATT_REMINDERS = "reminders";
     private static final String ALLOW_ATT_EVENTS = "events";
-    private static final String SLEEP_TAG = "sleep";
-    private static final String SLEEP_ATT_MODE = "mode";
-    private static final String SLEEP_ATT_NONE = "none";
-
-    private static final String SLEEP_ATT_START_HR = "startHour";
-    private static final String SLEEP_ATT_START_MIN = "startMin";
-    private static final String SLEEP_ATT_END_HR = "endHour";
-    private static final String SLEEP_ATT_END_MIN = "endMin";
 
     private static final String CONDITION_TAG = "condition";
     private static final String CONDITION_ATT_COMPONENT = "component";
@@ -97,111 +91,115 @@
     private static final String CONDITION_ATT_STATE = "state";
     private static final String CONDITION_ATT_FLAGS = "flags";
 
-    private static final String EXIT_CONDITION_TAG = "exitCondition";
-    private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+    private static final String MANUAL_TAG = "manual";
+    private static final String AUTOMATIC_TAG = "automatic";
+
+    private static final String RULE_ATT_ID = "id";
+    private static final String RULE_ATT_ENABLED = "enabled";
+    private static final String RULE_ATT_SNOOZING = "snoozing";
+    private static final String RULE_ATT_NAME = "name";
+    private static final String RULE_ATT_COMPONENT = "component";
+    private static final String RULE_ATT_ZEN = "zen";
+    private static final String RULE_ATT_CONDITION_ID = "conditionId";
 
     public boolean allowCalls;
+    public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS;
     public boolean allowMessages;
     public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
     public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
     public int allowFrom = SOURCE_ANYONE;
 
-    public String sleepMode;
-    public int sleepStartHour;   // 0-23
-    public int sleepStartMinute; // 0-59
-    public int sleepEndHour;
-    public int sleepEndMinute;
-    public boolean sleepNone;    // false = priority, true = none
-    public ComponentName[] conditionComponents;
-    public Uri[] conditionIds;
-    public Condition exitCondition;
-    public ComponentName exitConditionComponent;
+    public ZenRule manualRule;
+    public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();
 
     public ZenModeConfig() { }
 
     public ZenModeConfig(Parcel source) {
         allowCalls = source.readInt() == 1;
+        allowRepeatCallers = source.readInt() == 1;
         allowMessages = source.readInt() == 1;
         allowReminders = source.readInt() == 1;
         allowEvents = source.readInt() == 1;
-        if (source.readInt() == 1) {
-            sleepMode = source.readString();
-        }
-        sleepStartHour = source.readInt();
-        sleepStartMinute = source.readInt();
-        sleepEndHour = source.readInt();
-        sleepEndMinute = source.readInt();
-        sleepNone = source.readInt() == 1;
-        int len = source.readInt();
-        if (len > 0) {
-            conditionComponents = new ComponentName[len];
-            source.readTypedArray(conditionComponents, ComponentName.CREATOR);
-        }
-        len = source.readInt();
-        if (len > 0) {
-            conditionIds = new Uri[len];
-            source.readTypedArray(conditionIds, Uri.CREATOR);
-        }
         allowFrom = source.readInt();
-        exitCondition = source.readParcelable(null);
-        exitConditionComponent = source.readParcelable(null);
+        manualRule = source.readParcelable(null);
+        final int len = source.readInt();
+        if (len > 0) {
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            source.readStringArray(ids);
+            source.readTypedArray(rules, ZenRule.CREATOR);
+            for (int i = 0; i < len; i++) {
+                automaticRules.put(ids[i], rules[i]);
+            }
+        }
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(allowCalls ? 1 : 0);
+        dest.writeInt(allowRepeatCallers ? 1 : 0);
         dest.writeInt(allowMessages ? 1 : 0);
         dest.writeInt(allowReminders ? 1 : 0);
         dest.writeInt(allowEvents ? 1 : 0);
-        if (sleepMode != null) {
-            dest.writeInt(1);
-            dest.writeString(sleepMode);
-        } else {
-            dest.writeInt(0);
-        }
-        dest.writeInt(sleepStartHour);
-        dest.writeInt(sleepStartMinute);
-        dest.writeInt(sleepEndHour);
-        dest.writeInt(sleepEndMinute);
-        dest.writeInt(sleepNone ? 1 : 0);
-        if (conditionComponents != null && conditionComponents.length > 0) {
-            dest.writeInt(conditionComponents.length);
-            dest.writeTypedArray(conditionComponents, 0);
-        } else {
-            dest.writeInt(0);
-        }
-        if (conditionIds != null && conditionIds.length > 0) {
-            dest.writeInt(conditionIds.length);
-            dest.writeTypedArray(conditionIds, 0);
-        } else {
-            dest.writeInt(0);
-        }
         dest.writeInt(allowFrom);
-        dest.writeParcelable(exitCondition, 0);
-        dest.writeParcelable(exitConditionComponent, 0);
+        dest.writeParcelable(manualRule, 0);
+        if (!automaticRules.isEmpty()) {
+            final int len = automaticRules.size();
+            final String[] ids = new String[len];
+            final ZenRule[] rules = new ZenRule[len];
+            for (int i = 0; i < len; i++) {
+                ids[i] = automaticRules.keyAt(i);
+                rules[i] = automaticRules.valueAt(i);
+            }
+            dest.writeInt(len);
+            dest.writeStringArray(ids);
+            dest.writeTypedArray(rules, 0);
+        } else {
+            dest.writeInt(0);
+        }
     }
 
     @Override
     public String toString() {
         return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
             .append("allowCalls=").append(allowCalls)
+            .append(",allowRepeatCallers=").append(allowRepeatCallers)
             .append(",allowMessages=").append(allowMessages)
             .append(",allowFrom=").append(sourceToString(allowFrom))
             .append(",allowReminders=").append(allowReminders)
             .append(",allowEvents=").append(allowEvents)
-            .append(",sleepMode=").append(sleepMode)
-            .append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
-            .append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
-            .append(",sleepNone=").append(sleepNone)
-            .append(",conditionComponents=")
-            .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
-            .append(",conditionIds=")
-            .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
-            .append(",exitCondition=").append(exitCondition)
-            .append(",exitConditionComponent=").append(exitConditionComponent)
+            .append(",automaticRules=").append(automaticRules)
+            .append(",manualRule=").append(manualRule)
             .append(']').toString();
     }
 
+    public boolean isValid() {
+        if (!isValidManualRule(manualRule)) return false;
+        final int N = automaticRules.size();
+        for (int i = 0; i < N; i++) {
+            if (!isValidAutomaticRule(automaticRules.valueAt(i))) return false;
+        }
+        return true;
+    }
+
+    private static boolean isValidManualRule(ZenRule rule) {
+        return rule == null || Global.isValidZenMode(rule.zenMode) && sameCondition(rule);
+    }
+
+    private static boolean isValidAutomaticRule(ZenRule rule) {
+        return rule != null && !TextUtils.isEmpty(rule.name) && Global.isValidZenMode(rule.zenMode)
+                && rule.conditionId != null && sameCondition(rule);
+    }
+
+    private static boolean sameCondition(ZenRule rule) {
+        if (rule == null) return false;
+        if (rule.conditionId == null) {
+            return rule.condition == null;
+        } else {
+            return rule.condition == null || rule.conditionId.equals(rule.condition.id);
+        }
+    }
+
     public static String sourceToString(int source) {
         switch (source) {
             case SOURCE_ANYONE:
@@ -221,49 +219,34 @@
         if (o == this) return true;
         final ZenModeConfig other = (ZenModeConfig) o;
         return other.allowCalls == allowCalls
+                && other.allowRepeatCallers == allowRepeatCallers
                 && other.allowMessages == allowMessages
                 && other.allowFrom == allowFrom
                 && other.allowReminders == allowReminders
                 && other.allowEvents == allowEvents
-                && Objects.equals(other.sleepMode, sleepMode)
-                && other.sleepNone == sleepNone
-                && other.sleepStartHour == sleepStartHour
-                && other.sleepStartMinute == sleepStartMinute
-                && other.sleepEndHour == sleepEndHour
-                && other.sleepEndMinute == sleepEndMinute
-                && Objects.deepEquals(other.conditionComponents, conditionComponents)
-                && Objects.deepEquals(other.conditionIds, conditionIds)
-                && Objects.equals(other.exitCondition, exitCondition)
-                && Objects.equals(other.exitConditionComponent, exitConditionComponent);
+                && Objects.equals(other.automaticRules, automaticRules)
+                && Objects.equals(other.manualRule, manualRule);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents,
-                sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour,
-                sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
-                exitCondition, exitConditionComponent);
+        return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowFrom,
+                allowReminders, allowEvents, automaticRules, manualRule);
     }
 
-    public boolean isValid() {
-        return isValidHour(sleepStartHour) && isValidMinute(sleepStartMinute)
-                && isValidHour(sleepEndHour) && isValidMinute(sleepEndMinute)
-                && isValidSleepMode(sleepMode);
+    private static String toDayList(int[] days) {
+        if (days == null || days.length == 0) return "";
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < days.length; i++) {
+            if (i > 0) sb.append('.');
+            sb.append(days[i]);
+        }
+        return sb.toString();
     }
 
-    public static boolean isValidSleepMode(String sleepMode) {
-        return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
-                || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
-    }
-
-    public static int[] tryParseDays(String sleepMode) {
-        if (sleepMode == null) return null;
-        sleepMode = sleepMode.trim();
-        if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
-        if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
-        if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
-        if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
-        final String[] tokens = sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()).split(",");
+    private static int[] tryParseDayList(String dayList, String sep) {
+        if (dayList == null) return null;
+        final String[] tokens = dayList.split(sep);
         if (tokens.length == 0) return null;
         final int[] rt = new int[tokens.length];
         for (int i = 0; i < tokens.length; i++) {
@@ -283,7 +266,7 @@
         }
     }
 
-    public static ZenModeConfig readXml(XmlPullParser parser)
+    public static ZenModeConfig readXml(XmlPullParser parser, Migration migration)
             throws XmlPullParserException, IOException {
         int type = parser.getEventType();
         if (type != XmlPullParser.START_TAG) return null;
@@ -291,21 +274,20 @@
         if (!ZEN_TAG.equals(tag)) return null;
         final ZenModeConfig rt = new ZenModeConfig();
         final int version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION);
-        final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
-        final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+        if (version == 1) {
+            final XmlV1 v1 = XmlV1.readXml(parser);
+            return migration.migrate(v1);
+        }
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
             if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
-                if (!conditionComponents.isEmpty()) {
-                    rt.conditionComponents = conditionComponents
-                            .toArray(new ComponentName[conditionComponents.size()]);
-                    rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
-                }
                 return rt;
             }
             if (type == XmlPullParser.START_TAG) {
                 if (ALLOW_TAG.equals(tag)) {
                     rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+                    rt.allowRepeatCallers = safeBoolean(parser, ALLOW_ATT_REPEAT_CALLERS,
+                            DEFAULT_ALLOW_REPEAT_CALLERS);
                     rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
                     rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
                             DEFAULT_ALLOW_REMINDERS);
@@ -314,31 +296,13 @@
                     if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
                         throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
                     }
-                } else if (SLEEP_TAG.equals(tag)) {
-                    final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
-                    rt.sleepMode = isValidSleepMode(mode)? mode : null;
-                    rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
-                    final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
-                    final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
-                    final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
-                    final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
-                    rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
-                    rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
-                    rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
-                    rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
-                } else if (CONDITION_TAG.equals(tag)) {
-                    final ComponentName component =
-                            safeComponentName(parser, CONDITION_ATT_COMPONENT);
-                    final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
-                    if (component != null && conditionId != null) {
-                        conditionComponents.add(component);
-                        conditionIds.add(conditionId);
-                    }
-                } else if (EXIT_CONDITION_TAG.equals(tag)) {
-                    rt.exitCondition = readConditionXml(parser);
-                    if (rt.exitCondition != null) {
-                        rt.exitConditionComponent =
-                                safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+                } else if (MANUAL_TAG.equals(tag)) {
+                    rt.manualRule = readRuleXml(parser);
+                } else if (AUTOMATIC_TAG.equals(tag)) {
+                    final String id = parser.getAttributeValue(null, RULE_ATT_ID);
+                    final ZenRule automaticRule = readRuleXml(parser);
+                    if (id != null && automaticRule != null) {
+                        rt.automaticRules.put(id, automaticRule);
                     }
                 }
             }
@@ -352,45 +316,68 @@
 
         out.startTag(null, ALLOW_TAG);
         out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
+        out.attribute(null, ALLOW_ATT_REPEAT_CALLERS, Boolean.toString(allowRepeatCallers));
         out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
         out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
         out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
         out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
         out.endTag(null, ALLOW_TAG);
 
-        out.startTag(null, SLEEP_TAG);
-        if (sleepMode != null) {
-            out.attribute(null, SLEEP_ATT_MODE, sleepMode);
+        if (manualRule != null) {
+            out.startTag(null, MANUAL_TAG);
+            writeRuleXml(manualRule, out);
+            out.endTag(null, MANUAL_TAG);
         }
-        out.attribute(null, SLEEP_ATT_NONE, Boolean.toString(sleepNone));
-        out.attribute(null, SLEEP_ATT_START_HR, Integer.toString(sleepStartHour));
-        out.attribute(null, SLEEP_ATT_START_MIN, Integer.toString(sleepStartMinute));
-        out.attribute(null, SLEEP_ATT_END_HR, Integer.toString(sleepEndHour));
-        out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
-        out.endTag(null, SLEEP_TAG);
-
-        if (conditionComponents != null && conditionIds != null
-                && conditionComponents.length == conditionIds.length) {
-            for (int i = 0; i < conditionComponents.length; i++) {
-                out.startTag(null, CONDITION_TAG);
-                out.attribute(null, CONDITION_ATT_COMPONENT,
-                        conditionComponents[i].flattenToString());
-                out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
-                out.endTag(null, CONDITION_TAG);
-            }
-        }
-        if (exitCondition != null && exitConditionComponent != null) {
-            out.startTag(null, EXIT_CONDITION_TAG);
-            out.attribute(null, EXIT_CONDITION_ATT_COMPONENT,
-                    exitConditionComponent.flattenToString());
-            writeConditionXml(exitCondition, out);
-            out.endTag(null, EXIT_CONDITION_TAG);
+        final int N = automaticRules.size();
+        for (int i = 0; i < N; i++) {
+            final String id = automaticRules.keyAt(i);
+            final ZenRule automaticRule = automaticRules.valueAt(i);
+            out.startTag(null, AUTOMATIC_TAG);
+            out.attribute(null, RULE_ATT_ID, id);
+            writeRuleXml(automaticRule, out);
+            out.endTag(null, AUTOMATIC_TAG);
         }
         out.endTag(null, ZEN_TAG);
     }
 
+    public static ZenRule readRuleXml(XmlPullParser parser) {
+        final ZenRule rt = new ZenRule();
+        rt.enabled = safeBoolean(parser, RULE_ATT_ENABLED, true);
+        rt.snoozing = safeBoolean(parser, RULE_ATT_SNOOZING, false);
+        rt.name = parser.getAttributeValue(null, RULE_ATT_NAME);
+        final String zen = parser.getAttributeValue(null, RULE_ATT_ZEN);
+        rt.zenMode = tryParseZenMode(zen, -1);
+        if (rt.zenMode == -1) {
+            Slog.w(TAG, "Bad zen mode in rule xml:" + zen);
+            return null;
+        }
+        rt.conditionId = safeUri(parser, RULE_ATT_CONDITION_ID);
+        rt.component = safeComponentName(parser, RULE_ATT_COMPONENT);
+        rt.condition = readConditionXml(parser);
+        return rt.condition != null ? rt : null;
+    }
+
+    public static void writeRuleXml(ZenRule rule, XmlSerializer out) throws IOException {
+        out.attribute(null, RULE_ATT_ENABLED, Boolean.toString(rule.enabled));
+        out.attribute(null, RULE_ATT_SNOOZING, Boolean.toString(rule.snoozing));
+        if (rule.name != null) {
+            out.attribute(null, RULE_ATT_NAME, rule.name);
+        }
+        out.attribute(null, RULE_ATT_ZEN, Integer.toString(rule.zenMode));
+        if (rule.component != null) {
+            out.attribute(null, RULE_ATT_COMPONENT, rule.component.flattenToString());
+        }
+        if (rule.conditionId != null) {
+            out.attribute(null, RULE_ATT_CONDITION_ID, rule.conditionId.toString());
+        }
+        if (rule.condition != null) {
+            writeConditionXml(rule.condition, out);
+        }
+    }
+
     public static Condition readConditionXml(XmlPullParser parser) {
         final Uri id = safeUri(parser, CONDITION_ATT_ID);
+        if (id == null) return null;
         final String summary = parser.getAttributeValue(null, CONDITION_ATT_SUMMARY);
         final String line1 = parser.getAttributeValue(null, CONDITION_ATT_LINE1);
         final String line2 = parser.getAttributeValue(null, CONDITION_ATT_LINE2);
@@ -446,6 +433,14 @@
         return Uri.parse(val);
     }
 
+    public ArraySet<String> getAutomaticRuleNames() {
+        final ArraySet<String> rt = new ArraySet<String>();
+        for (int i = 0; i < automaticRules.size(); i++) {
+            rt.add(automaticRules.valueAt(i).name);
+        }
+        return rt;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -475,17 +470,6 @@
         }
     };
 
-    public DowntimeInfo toDowntimeInfo() {
-        final DowntimeInfo downtime = new DowntimeInfo();
-        downtime.startHour = sleepStartHour;
-        downtime.startMinute = sleepStartMinute;
-        downtime.endHour = sleepEndHour;
-        downtime.endMinute = sleepEndMinute;
-        downtime.mode = sleepMode;
-        downtime.none = sleepNone;
-        return downtime;
-    }
-
     public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) {
         final long now = System.currentTimeMillis();
         final long millis = minutesFromNow == 0 ? ZERO_VALUE_MS : minutesFromNow * MINUTES_MS;
@@ -548,38 +532,77 @@
         return tryParseCountdownConditionId(conditionId) != 0;
     }
 
-    // Built-in downtime conditions
-    // e.g. condition://android/downtime?start=10.00&end=7.00&mode=days%3A5%2C6&none=false
-    public static final String DOWNTIME_PATH = "downtime";
+    // built-in schedule conditions
+    public static final String SCHEDULE_PATH = "schedule";
 
-    public static Uri toDowntimeConditionId(DowntimeInfo downtime) {
+    public static class ScheduleInfo {
+        public int[] days;
+        public int startHour;
+        public int startMinute;
+        public int endHour;
+        public int endMinute;
+
+        @Override
+        public int hashCode() {
+            return 0;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof ScheduleInfo)) return false;
+            final ScheduleInfo other = (ScheduleInfo) o;
+            return toDayList(days).equals(toDayList(other.days))
+                    && startHour == other.startHour
+                    && startMinute == other.startMinute
+                    && endHour == other.endHour
+                    && endMinute == other.endMinute;
+        }
+
+        public ScheduleInfo copy() {
+            final ScheduleInfo rt = new ScheduleInfo();
+            if (days != null) {
+                rt.days = new int[days.length];
+                System.arraycopy(days, 0, rt.days, 0, days.length);
+            }
+            rt.startHour = startHour;
+            rt.startMinute = startMinute;
+            rt.endHour = endHour;
+            rt.endMinute = endMinute;
+            return rt;
+        }
+    }
+
+    public static Uri toScheduleConditionId(ScheduleInfo schedule) {
         return new Uri.Builder().scheme(Condition.SCHEME)
                 .authority(SYSTEM_AUTHORITY)
-                .appendPath(DOWNTIME_PATH)
-                .appendQueryParameter("start", downtime.startHour + "." + downtime.startMinute)
-                .appendQueryParameter("end", downtime.endHour + "." + downtime.endMinute)
-                .appendQueryParameter("mode", downtime.mode)
-                .appendQueryParameter("none", Boolean.toString(downtime.none))
+                .appendPath(SCHEDULE_PATH)
+                .appendQueryParameter("days", toDayList(schedule.days))
+                .appendQueryParameter("start", schedule.startHour + "." + schedule.startMinute)
+                .appendQueryParameter("end", schedule.endHour + "." + schedule.endMinute)
                 .build();
     }
 
-    public static DowntimeInfo tryParseDowntimeConditionId(Uri conditionId) {
-        if (!Condition.isValidId(conditionId, SYSTEM_AUTHORITY)
-                || conditionId.getPathSegments().size() != 1
-                || !DOWNTIME_PATH.equals(conditionId.getPathSegments().get(0))) {
-            return null;
-        }
+    public static boolean isValidScheduleConditionId(Uri conditionId) {
+        return tryParseScheduleConditionId(conditionId) != null;
+    }
+
+    public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
+        final boolean isSchedule =  conditionId != null
+                && conditionId.getScheme().equals(Condition.SCHEME)
+                && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY)
+                && conditionId.getPathSegments().size() == 1
+                && conditionId.getPathSegments().get(0).equals(ZenModeConfig.SCHEDULE_PATH);
+        if (!isSchedule) return null;
         final int[] start = tryParseHourAndMinute(conditionId.getQueryParameter("start"));
         final int[] end = tryParseHourAndMinute(conditionId.getQueryParameter("end"));
         if (start == null || end == null) return null;
-        final DowntimeInfo downtime = new DowntimeInfo();
-        downtime.startHour = start[0];
-        downtime.startMinute = start[1];
-        downtime.endHour = end[0];
-        downtime.endMinute = end[1];
-        downtime.mode = conditionId.getQueryParameter("mode");
-        downtime.none = Boolean.toString(true).equals(conditionId.getQueryParameter("none"));
-        return downtime;
+        final ScheduleInfo rt = new ScheduleInfo();
+        rt.days = tryParseDayList(conditionId.getQueryParameter("days"), "\\.");
+        rt.startHour = start[0];
+        rt.startMinute = start[1];
+        rt.endHour = end[0];
+        rt.endMinute = end[1];
+        return rt;
     }
 
     private static int[] tryParseHourAndMinute(String value) {
@@ -591,36 +614,268 @@
         return isValidHour(hour) && isValidMinute(minute) ? new int[] { hour, minute } : null;
     }
 
-    public static boolean isValidDowntimeConditionId(Uri conditionId) {
-        return tryParseDowntimeConditionId(conditionId) != null;
+    private static int tryParseZenMode(String value, int defValue) {
+        final int rt = tryParseInt(value, defValue);
+        return Global.isValidZenMode(rt) ? rt : defValue;
     }
 
-    public static class DowntimeInfo {
-        public int startHour;   // 0-23
-        public int startMinute; // 0-59
-        public int endHour;
-        public int endMinute;
-        public String mode;
-        public boolean none;
+    public String newRuleId() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    public static String getConditionLine1(Context context, ZenModeConfig config,
+            int userHandle) {
+        return getConditionLine(context, config, userHandle, true /*useLine1*/);
+    }
+
+    public static String getConditionSummary(Context context, ZenModeConfig config,
+            int userHandle) {
+        return getConditionLine(context, config, userHandle, false /*useLine1*/);
+    }
+
+    private static String getConditionLine(Context context, ZenModeConfig config,
+            int userHandle, boolean useLine1) {
+        if (config == null) return "";
+        if (config.manualRule != null) {
+            final Uri id = config.manualRule.conditionId;
+            if (id == null) {
+                return context.getString(com.android.internal.R.string.zen_mode_forever);
+            }
+            final long time = tryParseCountdownConditionId(id);
+            Condition c = config.manualRule.condition;
+            if (time > 0) {
+                final long now = System.currentTimeMillis();
+                final long span = time - now;
+                c = toTimeCondition(context,
+                        time, Math.round(span / (float) MINUTES_MS), now, userHandle);
+            }
+            final String rt = c == null ? "" : useLine1 ? c.line1 : c.summary;
+            return TextUtils.isEmpty(rt) ? "" : rt;
+        }
+        String summary = "";
+        for (ZenRule automaticRule : config.automaticRules.values()) {
+            if (automaticRule.enabled && !automaticRule.snoozing
+                    && automaticRule.isTrueOrUnknown()) {
+                if (summary.isEmpty()) {
+                    summary = automaticRule.name;
+                } else {
+                    summary = context.getResources()
+                            .getString(R.string.zen_mode_rule_name_combination, summary,
+                                    automaticRule.name);
+                }
+            }
+        }
+        return summary;
+    }
+
+    public static class ZenRule implements Parcelable {
+        public boolean enabled;
+        public boolean snoozing;         // user manually disabled this instance
+        public String name;              // required for automatic (unique)
+        public int zenMode;
+        public Uri conditionId;          // required for automatic
+        public Condition condition;      // optional
+        public ComponentName component;  // optional
+
+        public ZenRule() { }
+
+        public ZenRule(Parcel source) {
+            enabled = source.readInt() == 1;
+            snoozing = source.readInt() == 1;
+            if (source.readInt() == 1) {
+                name = source.readString();
+            }
+            zenMode = source.readInt();
+            conditionId = source.readParcelable(null);
+            condition = source.readParcelable(null);
+            component = source.readParcelable(null);
+        }
 
         @Override
-        public int hashCode() {
+        public int describeContents() {
             return 0;
         }
 
         @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(enabled ? 1 : 0);
+            dest.writeInt(snoozing ? 1 : 0);
+            if (name != null) {
+                dest.writeInt(1);
+                dest.writeString(name);
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeInt(zenMode);
+            dest.writeParcelable(conditionId, 0);
+            dest.writeParcelable(condition, 0);
+            dest.writeParcelable(component, 0);
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder(ZenRule.class.getSimpleName()).append('[')
+                    .append("enabled=").append(enabled)
+                    .append(",snoozing=").append(snoozing)
+                    .append(",name=").append(name)
+                    .append(",zenMode=").append(Global.zenModeToString(zenMode))
+                    .append(",conditionId=").append(conditionId)
+                    .append(",condition=").append(condition)
+                    .append(",component=").append(component)
+                    .append(']').toString();
+        }
+
+        @Override
         public boolean equals(Object o) {
-            if (!(o instanceof DowntimeInfo)) return false;
-            final DowntimeInfo other = (DowntimeInfo) o;
-            return startHour == other.startHour
-                    && startMinute == other.startMinute
-                    && endHour == other.endHour
-                    && endMinute == other.endMinute
-                    && Objects.equals(mode, other.mode)
-                    && none == other.none;
+            if (!(o instanceof ZenRule)) return false;
+            if (o == this) return true;
+            final ZenRule other = (ZenRule) o;
+            return other.enabled == enabled
+                    && other.snoozing == snoozing
+                    && Objects.equals(other.name, name)
+                    && other.zenMode == zenMode
+                    && Objects.equals(other.conditionId, conditionId)
+                    && Objects.equals(other.condition, condition)
+                    && Objects.equals(other.component, component);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
+                    component);
+        }
+
+        public boolean isTrueOrUnknown() {
+            return condition == null || condition.state == Condition.STATE_TRUE
+                    || condition.state == Condition.STATE_UNKNOWN;
+        }
+
+        public static final Parcelable.Creator<ZenRule> CREATOR
+                = new Parcelable.Creator<ZenRule>() {
+            @Override
+            public ZenRule createFromParcel(Parcel source) {
+                return new ZenRule(source);
+            }
+            @Override
+            public ZenRule[] newArray(int size) {
+                return new ZenRule[size];
+            }
+        };
+    }
+
+    // Legacy config
+    public static final class XmlV1 {
+        public static final String SLEEP_MODE_NIGHTS = "nights";
+        public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+        public static final String SLEEP_MODE_DAYS_PREFIX = "days:";
+
+        private static final String EXIT_CONDITION_TAG = "exitCondition";
+        private static final String EXIT_CONDITION_ATT_COMPONENT = "component";
+        private static final String SLEEP_TAG = "sleep";
+        private static final String SLEEP_ATT_MODE = "mode";
+        private static final String SLEEP_ATT_NONE = "none";
+
+        private static final String SLEEP_ATT_START_HR = "startHour";
+        private static final String SLEEP_ATT_START_MIN = "startMin";
+        private static final String SLEEP_ATT_END_HR = "endHour";
+        private static final String SLEEP_ATT_END_MIN = "endMin";
+
+        public boolean allowCalls;
+        public boolean allowMessages;
+        public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
+        public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
+        public int allowFrom = SOURCE_ANYONE;
+
+        public String sleepMode;     // nights, weeknights, days:1,2,3  Calendar.days
+        public int sleepStartHour;   // 0-23
+        public int sleepStartMinute; // 0-59
+        public int sleepEndHour;
+        public int sleepEndMinute;
+        public boolean sleepNone;    // false = priority, true = none
+        public ComponentName[] conditionComponents;
+        public Uri[] conditionIds;
+        public Condition exitCondition;  // manual exit condition
+        public ComponentName exitConditionComponent;  // manual exit condition component
+
+        private static boolean isValidSleepMode(String sleepMode) {
+            return sleepMode == null || sleepMode.equals(SLEEP_MODE_NIGHTS)
+                    || sleepMode.equals(SLEEP_MODE_WEEKNIGHTS) || tryParseDays(sleepMode) != null;
+        }
+
+        public static int[] tryParseDays(String sleepMode) {
+            if (sleepMode == null) return null;
+            sleepMode = sleepMode.trim();
+            if (SLEEP_MODE_NIGHTS.equals(sleepMode)) return ALL_DAYS;
+            if (SLEEP_MODE_WEEKNIGHTS.equals(sleepMode)) return WEEKNIGHT_DAYS;
+            if (!sleepMode.startsWith(SLEEP_MODE_DAYS_PREFIX)) return null;
+            if (sleepMode.equals(SLEEP_MODE_DAYS_PREFIX)) return null;
+            return tryParseDayList(sleepMode.substring(SLEEP_MODE_DAYS_PREFIX.length()), ",");
+        }
+
+        public static XmlV1 readXml(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int type;
+            String tag;
+            XmlV1 rt = new XmlV1();
+            final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+            final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                tag = parser.getName();
+                if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+                    if (!conditionComponents.isEmpty()) {
+                        rt.conditionComponents = conditionComponents
+                                .toArray(new ComponentName[conditionComponents.size()]);
+                        rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+                    }
+                    return rt;
+                }
+                if (type == XmlPullParser.START_TAG) {
+                    if (ALLOW_TAG.equals(tag)) {
+                        rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
+                        rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+                        rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
+                                DEFAULT_ALLOW_REMINDERS);
+                        rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS,
+                                DEFAULT_ALLOW_EVENTS);
+                        rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+                        if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+                            throw new IndexOutOfBoundsException("bad source in config:"
+                                    + rt.allowFrom);
+                        }
+                    } else if (SLEEP_TAG.equals(tag)) {
+                        final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
+                        rt.sleepMode = isValidSleepMode(mode)? mode : null;
+                        rt.sleepNone = safeBoolean(parser, SLEEP_ATT_NONE, false);
+                        final int startHour = safeInt(parser, SLEEP_ATT_START_HR, 0);
+                        final int startMinute = safeInt(parser, SLEEP_ATT_START_MIN, 0);
+                        final int endHour = safeInt(parser, SLEEP_ATT_END_HR, 0);
+                        final int endMinute = safeInt(parser, SLEEP_ATT_END_MIN, 0);
+                        rt.sleepStartHour = isValidHour(startHour) ? startHour : 0;
+                        rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
+                        rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
+                        rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+                    } else if (CONDITION_TAG.equals(tag)) {
+                        final ComponentName component =
+                                safeComponentName(parser, CONDITION_ATT_COMPONENT);
+                        final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+                        if (component != null && conditionId != null) {
+                            conditionComponents.add(component);
+                            conditionIds.add(conditionId);
+                        }
+                    } else if (EXIT_CONDITION_TAG.equals(tag)) {
+                        rt.exitCondition = readConditionXml(parser);
+                        if (rt.exitCondition != null) {
+                            rt.exitConditionComponent =
+                                    safeComponentName(parser, EXIT_CONDITION_ATT_COMPONENT);
+                        }
+                    }
+                }
+            }
+            throw new IllegalStateException("Failed to reach END_DOCUMENT");
         }
     }
 
-    // built-in next alarm conditions
-    public static final String NEXT_ALARM_PATH = "next_alarm";
+    public interface Migration {
+        ZenModeConfig migrate(XmlV1 v1);
+    }
 }
diff --git a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
index 52db223..0071a33 100644
--- a/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
+++ b/core/java/android/service/persistentdata/IPersistentDataBlockService.aidl
@@ -16,6 +16,8 @@
 
 package android.service.persistentdata;
 
+import android.app.PendingIntent;
+import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
 /**
@@ -30,6 +32,7 @@
     int write(in byte[] data);
     byte[] read();
     void wipe();
+    void wipeIfAllowed(in Bundle bundle, in PendingIntent pi);
     int getDataBlockSize();
     long getMaximumDataBlockSize();
 
diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
index 0ffdf68..31570c6 100644
--- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java
+++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java
@@ -17,6 +17,8 @@
 package android.service.persistentdata;
 
 import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Slog;
 
@@ -41,6 +43,56 @@
 @SystemApi
 public class PersistentDataBlockManager {
     private static final String TAG = PersistentDataBlockManager.class.getSimpleName();
+
+    /**
+     * Broadcast action that will be called when the {@link #wipeIfAllowed(Bundle,PendingIntent)}
+     * method is called.  A broadcast with this action will be sent to the package allowed to write
+     * to the persistent data block. Packages receiving this broadcasts should respond by using the
+     * {@link android.app.PendingIntent} sent in the {@link #EXTRA_WIPE_IF_ALLOWED_CALLBACK} extra.
+     */
+    public static final String ACTION_WIPE_IF_ALLOWED
+            = "android.service.persistentdata.action.WIPE_IF_ALLOWED";
+
+    /**
+     * A {@link android.os.Parcelable} extra of type {@link android.app.PendingIntent} used to
+     * response to {@link #wipeIfAllowed(Bundle,PendingIntent)}. This extra will set in broadcasts
+     * with an action of {@link #ACTION_WIPE_IF_ALLOWED}.
+     */
+    public static final String EXTRA_WIPE_IF_ALLOWED_CALLBACK
+            = "android.service.persistentdata.extra.WIPE_IF_ALLOWED_CALLBACK";
+
+    /**
+     * Result code indicating that the data block was wiped.
+     *
+     * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+     * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+     */
+    public static final int STATUS_SUCCESS = 0;
+
+    /**
+     * Result code indicating that a remote exception was received while processing the request.
+     *
+     * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+     * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+     */
+    public static final int STATUS_ERROR_REMOTE_EXCEPTION = 1;
+
+    /**
+     * Result code indicating that a network error occurred while processing the request.
+     *
+     * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+     * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+     */
+    public static final int STATUS_ERROR_NETWORK_ERROR = 2;
+
+    /**
+     * Result code indicating that the data block could not be cleared with the provided data.
+     *
+     * <p>This value is set as result code of the {@link android.app.PendingIntent} argument to
+     * {@link #wipeIfAllowed(Bundle,PendingIntent)}
+     */
+    public static final int STATUS_ERROR_NOT_COMPLIANT = 3;
+
     private IPersistentDataBlockService sService;
 
     public PersistentDataBlockManager(IPersistentDataBlockService service) {
@@ -118,6 +170,28 @@
     }
 
     /**
+     * Attempt to wipe the data block by sending a broadcast to the package allowed to modify the
+     * datablock. The allowed package can refuse to wipe the data block based on the contents of
+     * the specified bundle. This bundle may contain data used by the allowed package to wipe the
+     * partition such as account credentials or an authorization token.
+     * @param bundle data used to wipe the data block. The contents of this bundle depend on the
+     *    allowed package receiving the data.
+     * @param pi intent called when attempt finished. The result code of this intent will be set
+     *    to one of {@link #STATUS_SUCCESS}, {@link #STATUS_ERROR_REMOTE_EXCEPTION},
+     *    {@link #STATUS_ERROR_NETWORK_ERROR}, or {@link #STATUS_ERROR_NOT_COMPLIANT}.
+     */
+    public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+        if (pi == null) {
+            throw new NullPointerException();
+        }
+        try {
+            sService.wipeIfAllowed(bundle, pi);
+        } catch (RemoteException e) {
+            onError("wiping persistent partition");
+        }
+    }
+
+    /**
      * Writes a byte enabling or disabling the ability to "OEM unlock" the device.
      */
     public void setOemUnlockEnabled(boolean enabled) {
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
index f9920dd..0cea821 100644
--- a/core/java/android/text/method/AllCapsTransformationMethod.java
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.View;
+import android.widget.TextView;
 
 import java.util.Locale;
 
@@ -39,11 +40,23 @@
 
     @Override
     public CharSequence getTransformation(CharSequence source, View view) {
-        if (mEnabled) {
-            return source != null ? source.toString().toUpperCase(mLocale) : null;
+        if (!mEnabled) {
+            Log.w(TAG, "Caller did not enable length changes; not transforming text");
+            return source;
         }
-        Log.w(TAG, "Caller did not enable length changes; not transforming text");
-        return source;
+
+        if (source == null) {
+            return null;
+        }
+
+        Locale locale = null;
+        if (view instanceof TextView) {
+            locale = ((TextView)view).getTextLocale();
+        }
+        if (locale == null) {
+            locale = mLocale;
+        }
+        return source.toString().toUpperCase(locale);
     }
 
     @Override
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index 63607fa..07c1ec3 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -22,6 +22,8 @@
 import android.text.method.TextKeyListener.Capitalize;
 import android.widget.TextView;
 
+import java.text.BreakIterator;
+
 /**
  * Abstract base class for key listeners.
  *
@@ -63,9 +65,9 @@
 
     private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
             KeyEvent event, boolean isForwardDelete) {
-        // Ensure the key event does not have modifiers except ALT or SHIFT.
+        // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
         if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
-                & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK))) {
+                & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
             return false;
         }
 
@@ -74,18 +76,28 @@
             return true;
         }
 
-        // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
-        if (getMetaState(content, META_ALT_ON, event) == 1) {
-            if (deleteLine(view, content)) {
-                return true;
+        // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead.
+        boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0);
+        boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1);
+        boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1);
+
+        if (isCtrlActive) {
+            if (isAltActive || isShiftActive) {
+                // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters.
+                return false;
             }
+            return deleteUntilWordBoundary(view, content, isForwardDelete);
+        }
+
+        // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
+        if (isAltActive && deleteLine(view, content)) {
+            return true;
         }
 
         // Delete a character.
         final int start = Selection.getSelectionEnd(content);
         final int end;
-        if (isForwardDelete || event.isShiftPressed()
-                || getMetaState(content, META_SHIFT_ON) == 1) {
+        if (isForwardDelete || event.isShiftPressed() || isShiftActive) {
             end = TextUtils.getOffsetAfter(content, start);
         } else {
             end = TextUtils.getOffsetBefore(content, start);
@@ -97,6 +109,54 @@
         return false;
     }
 
+    private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) {
+        int currentCursorOffset = Selection.getSelectionStart(content);
+
+        // If there is a selection, do nothing.
+        if (currentCursorOffset != Selection.getSelectionEnd(content)) {
+            return false;
+        }
+
+        // Early exit if there is no contents to delete.
+        if ((!isForwardDelete && currentCursorOffset == 0) ||
+            (isForwardDelete && currentCursorOffset == content.length())) {
+            return false;
+        }
+
+        WordIterator wordIterator = null;
+        if (view instanceof TextView) {
+            wordIterator = ((TextView)view).getWordIterator();
+        }
+
+        if (wordIterator == null) {
+            // Default locale is used for WordIterator since the appropriate locale is not clear
+            // here.
+            // TODO: Use appropriate locale for WordIterator.
+            wordIterator = new WordIterator();
+        }
+
+        int deleteFrom;
+        int deleteTo;
+
+        if (isForwardDelete) {
+            deleteFrom = currentCursorOffset;
+            wordIterator.setCharSequence(content, deleteFrom, content.length());
+            deleteTo = wordIterator.following(currentCursorOffset);
+            if (deleteTo == BreakIterator.DONE) {
+                deleteTo = content.length();
+            }
+        } else {
+            deleteTo = currentCursorOffset;
+            wordIterator.setCharSequence(content, 0, deleteTo);
+            deleteFrom = wordIterator.preceding(currentCursorOffset);
+            if (deleteFrom == BreakIterator.DONE) {
+                deleteFrom = 0;
+            }
+        }
+        content.delete(deleteFrom, deleteTo);
+        return true;
+    }
+
     private boolean deleteSelection(View view, Editable content) {
         int selectionStart = Selection.getSelectionStart(content);
         int selectionEnd = Selection.getSelectionEnd(content);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8f4e8e1..491826a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -557,7 +557,7 @@
         }
     }
 
-    private void hideInsertionPointCursorController() {
+    void hideInsertionPointCursorController() {
         if (mInsertionPointCursorController != null) {
             mInsertionPointCursorController.hide();
         }
@@ -1668,6 +1668,7 @@
         if (mSelectionActionMode != null) {
             // Selection action mode is already started
             // TODO: revisit invocations to minimize this case.
+            mSelectionActionMode.invalidate();
             return false;
         }
         ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
@@ -1682,6 +1683,7 @@
     boolean startSelectionActionModeWithSelection() {
         if (mSelectionActionMode != null) {
             // Selection action mode is already started
+            mSelectionActionMode.invalidate();
             return false;
         }
 
@@ -2965,6 +2967,28 @@
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            mode.setTitle(mTextView.getContext().getString(
+                    com.android.internal.R.string.textSelectionCABTitle));
+            mode.setSubtitle(null);
+            mode.setTitleOptionalHint(true);
+            populateMenuWithItems(menu);
+
+            if (mCustomSelectionActionModeCallback != null) {
+                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+                    // The custom mode can choose to cancel the action mode
+                    return false;
+                }
+            }
+
+            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
+                mTextView.setHasTransientState(true);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        private void populateMenuWithItems(Menu menu) {
             final boolean legacy = mTextView.getContext().getApplicationInfo().targetSdkVersion <
                     Build.VERSION_CODES.LOLLIPOP;
             final Context context = !legacy && menu instanceof MenuBuilder ?
@@ -2973,11 +2997,6 @@
             final TypedArray styledAttributes = context.obtainStyledAttributes(
                     com.android.internal.R.styleable.SelectionModeDrawables);
 
-            mode.setTitle(mTextView.getContext().getString(
-                    com.android.internal.R.string.textSelectionCABTitle));
-            mode.setSubtitle(null);
-            mode.setTitleOptionalHint(true);
-
             if (mTextView.canCut()) {
                 menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
                     setIcon(styledAttributes.getResourceId(
@@ -3012,37 +3031,33 @@
                     setShowAsAction(
                             MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
 
-            if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
-                menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
-                        setShowAsAction(
-                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-            }
+            updateReplaceItem(menu);
 
             styledAttributes.recycle();
-
-            if (mCustomSelectionActionModeCallback != null) {
-                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
-                    // The custom mode can choose to cancel the action mode
-                    return false;
-                }
-            }
-
-            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
-                mTextView.setHasTransientState(true);
-                return true;
-            } else {
-                return false;
-            }
         }
 
         @Override
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            updateReplaceItem(menu);
+
             if (mCustomSelectionActionModeCallback != null) {
                 return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
             }
             return true;
         }
 
+        private void updateReplaceItem(Menu menu) {
+            boolean canReplace = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
+            boolean replaceItemExists = menu.findItem(TextView.ID_REPLACE) != null;
+            if (canReplace && !replaceItemExists) {
+                menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
+                setShowAsAction(
+                        MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+            } else if (!canReplace && replaceItemExists) {
+                menu.removeItem(TextView.ID_REPLACE);
+            }
+        }
+
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             if (mCustomSelectionActionModeCallback != null &&
@@ -3093,6 +3108,17 @@
                         mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
                 mSelectionPath.computeBounds(mSelectionBounds, true);
                 mSelectionBounds.bottom += mSelectionHandleHeight;
+            } else if (mCursorCount == 2) {
+                // We have a split cursor. In this case, we take the rectangle that includes both
+                // parts of the cursor to ensure we don't obscure either of them.
+                Rect firstCursorBounds = mCursorDrawable[0].getBounds();
+                Rect secondCursorBounds = mCursorDrawable[1].getBounds();
+                mSelectionBounds.set(
+                        Math.min(firstCursorBounds.left, secondCursorBounds.left),
+                        Math.min(firstCursorBounds.top, secondCursorBounds.top),
+                        Math.max(firstCursorBounds.right, secondCursorBounds.right),
+                        Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom)
+                            + mInsertionHandleHeight);
             } else {
                 // We have a single cursor.
                 int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
@@ -3798,6 +3824,9 @@
             Selection.setSelection((Spannable) mTextView.getText(), offset,
                     mTextView.getSelectionEnd());
             updateDrawable();
+            if (mSelectionActionMode != null) {
+                mSelectionActionMode.invalidate();
+            }
         }
 
         @Override
@@ -3900,6 +3929,9 @@
         public void updateSelection(int offset) {
             Selection.setSelection((Spannable) mTextView.getText(),
                     mTextView.getSelectionStart(), offset);
+            if (mSelectionActionMode != null) {
+                mSelectionActionMode.invalidate();
+            }
             updateDrawable();
         }
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9caa584..11439e4 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9067,6 +9067,12 @@
     }
 
     boolean selectAllText() {
+        // Need to hide insert point cursor controller before settings selection, otherwise insert
+        // point cursor controller obtains cursor update event and update cursor with cancelling
+        // selection.
+        if (mEditor != null) {
+            mEditor.hideInsertionPointCursorController();
+        }
         final int length = mText.length();
         Selection.setSelection((Spannable) mText, 0, length);
         return length > 0;
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 870d20d..b9208ff 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -5,3 +5,5 @@
 # interaction logs
 524287 sysui_view_visibility (category|1|5),(visible|1|6)
 524288 sysui_action (category|1|5)
+524290 sysui_count (name|3),(increment|1)
+524291 sysui_histogram (name|3),(bucket|1)
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 0d5db77..6be6389 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -26,7 +26,9 @@
  */
 public class MetricsLogger implements MetricsConstants {
     // These constants are temporary, they should migrate to MetricsConstants.
-    // next value is 144;
+    // next value is 145;
+
+    public static final int NOTIFICATION_ZEN_MODE_SCHEDULE_RULE = 144;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
@@ -48,4 +50,14 @@
         }
         EventLogTags.writeSysuiAction(category);
     }
+
+    /** Add an integer value to the monotonically increasing counter with the given name. */
+    public static void count(Context context, String name, int value) {
+        EventLogTags.writeSysuiCount(name, value);
+    }
+
+    /** Increment the bucket with the integer label on the histogram with the given name. */
+    public static void histogram(Context context, String name, int bucket) {
+        EventLogTags.writeSysuiHistogram(name, bucket);
+    }
 }
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 526885f..4c4a39d 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -543,6 +543,11 @@
     return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
 }
 
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size) {
+    SkMemoryStream  stream(data, size);
+    return doDecode(env, &stream, NULL, NULL);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static JNINativeMethod gMethods[] = {
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index a54da43..22a955f 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -21,4 +21,6 @@
 
 jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format);
 
+jobject decodeBitmap(JNIEnv* env, void* data, size_t size);
+
 #endif  // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp
index 655b400..e9f18a6 100644
--- a/core/jni/android_emoji_EmojiFactory.cpp
+++ b/core/jni/android_emoji_EmojiFactory.cpp
@@ -5,6 +5,7 @@
 #include <utils/Log.h>
 #include <ScopedUtfChars.h>
 
+#include "BitmapFactory.h"
 #include "EmojiFactory.h"
 #include "GraphicsJNI.h"
 #include <nativehelper/JNIHelp.h>
@@ -164,14 +165,7 @@
     return NULL;
   }
 
-  SkBitmap *bitmap = new SkBitmap;
-  if (!SkImageDecoder::DecodeMemory(bytes, size, bitmap)) {
-    ALOGE("SkImageDecoder::DecodeMemory() failed.");
-    return NULL;
-  }
-
-  return GraphicsJNI::createBitmap(env, bitmap,
-      GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL);
+  return decodeBitmap(env, (void*)bytes, size);
 }
 
 static void android_emoji_EmojiFactory_destructor(
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f2f7be2..453cb74 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -309,6 +309,8 @@
 
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
 
+    <protected-broadcast android:name="android.service.persistentdata.action.WIPE_IF_ALLOWED" />
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
@@ -3019,6 +3021,12 @@
         android:description="@string/permdesc_accessVoiceInteractionService"
         android:label="@string/permlab_accessVoiceInteractionService" />
 
+    <!-- Allows an app that has this permission and a permissions to install packages
+         to request all runtime permissions to be granted at installation.
+     @hide -->
+    <permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
+        android:protectionLevel="signature" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e879244..6d9bbae 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2044,15 +2044,11 @@
     <!-- Enabled built-in zen mode condition providers -->
     <string-array translatable="false" name="config_system_condition_providers">
         <item>countdown</item>
-        <item>downtime</item>
-        <item>next_alarm</item>
+        <item>schedule</item>
     </string-array>
 
-    <!-- Show the next-alarm as a zen exit condition if it occurs in the next n hours. -->
-    <integer name="config_next_alarm_condition_lookahead_threshold_hrs">12</integer>
-
-    <!-- Show downtime as a zen exit condition if it starts in the next n hours. -->
-    <integer name="config_downtime_condition_lookahead_threshold_hrs">4</integer>
+    <!-- Priority repeat caller threshold, in minutes -->
+    <integer name="config_zen_repeat_callers_threshold">15</integer>
 
     <!-- Flags enabling default window features. See Window.java -->
     <bool name="config_defaultWindowFeatureOptionsPanel">true</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a48e964..578aa45 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5170,12 +5170,6 @@
     <!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
     <string name="battery_saver_description">To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.</string>
 
-    <!-- [CHAR_LIMIT=NONE] Zen mode: Condition summary for built-in downtime condition, if active -->
-    <string name="downtime_condition_summary">Until your downtime ends at <xliff:g id="formattedTime" example="10:00 PM">%1$s</xliff:g></string>
-
-    <!-- [CHAR_LIMIT=NONE] Zen mode: Condition line one for built-in downtime condition, if active -->
-    <string name="downtime_condition_line_one">Until your downtime ends</string>
-
     <!-- Zen mode condition - summary: time duration in minutes. [CHAR LIMIT=NONE] -->
     <plurals name="zen_mode_duration_minutes_summary">
         <item quantity="one">For one minute (until <xliff:g id="formattedTime" example="10:00 PM">%2$s</xliff:g>)</item>
@@ -5206,14 +5200,23 @@
     <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] -->
     <string name="zen_mode_forever">Until you turn this off</string>
 
+    <!-- Zen mode active automatic rule name separator. [CHAR LIMIT=NONE] -->
+    <string name="zen_mode_rule_name_combination"><xliff:g id="first" example="Weeknights">%1$s</xliff:g> / <xliff:g id="rest" example="Meetings">%2$s</xliff:g></string>
+
     <!-- Content description for the Toolbar icon used to collapse an expanded action mode. [CHAR LIMIT=NONE] -->
     <string name="toolbar_collapse_description">Collapse</string>
 
-    <!-- Zen mode condition - summary: until next alarm. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_next_alarm_summary">Until next alarm at <xliff:g id="formattedTime" example="7:30 AM">%1$s</xliff:g></string>
+    <!-- Zen mode - feature name. [CHAR LIMIT=40] -->
+    <string name="zen_mode_feature_name">Block interruptions</string>
 
-    <!-- Zen mode condition - line one: until next alarm. [CHAR LIMIT=NONE] -->
-    <string name="zen_mode_next_alarm_line_one">Until next alarm</string>
+    <!-- Zen mode - downtime legacy feature name. [CHAR LIMIT=40] -->
+    <string name="zen_mode_downtime_feature_name">Downtime</string>
+
+    <!-- Zen mode - name of default automatic schedule for weeknights. [CHAR LIMIT=40] -->
+    <string name="zen_mode_default_weeknights_name">Weeknights</string>
+
+    <!-- Zen mode - name of default automatic schedule for weekends. [CHAR LIMIT=40] -->
+    <string name="zen_mode_default_weekends_name">Weekends</string>
 
     <!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
     <string name="muted_by">Muted by <xliff:g id="third_party">%1$s</xliff:g></string>
@@ -5232,10 +5235,12 @@
     <string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
     <string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
 
+    <!-- User visible name for USB MIDI Peripheral port -->
+    <string name="usb_midi_peripheral_name">Android USB Peripheral Port</string>
     <!-- Manufacturer name for USB MIDI Peripheral port -->
     <string name="usb_midi_peripheral_manufacturer_name">Android</string>
-    <!-- Model name for USB MIDI Peripheral port -->
-    <string name="usb_midi_peripheral_model_name">USB Peripheral Port</string>
+    <!-- Product name for USB MIDI Peripheral port -->
+    <string name="usb_midi_peripheral_product_name">USB Peripheral Port</string>
 
     <!-- Floating toolbar strings -->
     <!-- Content description for the button that opens the floating toolbar overflow. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c2c00b5..42d187d 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2037,19 +2037,18 @@
   <java-symbol type="dimen" name="timepicker_text_size_normal" />
   <java-symbol type="dimen" name="timepicker_text_size_inner" />
   <java-symbol type="string" name="battery_saver_description" />
-  <java-symbol type="string" name="downtime_condition_summary" />
-  <java-symbol type="string" name="downtime_condition_line_one" />
   <java-symbol type="string" name="zen_mode_forever" />
+  <java-symbol type="string" name="zen_mode_rule_name_combination" />
   <java-symbol type="plurals" name="zen_mode_duration_minutes" />
   <java-symbol type="plurals" name="zen_mode_duration_hours" />
   <java-symbol type="plurals" name="zen_mode_duration_minutes_summary" />
   <java-symbol type="plurals" name="zen_mode_duration_hours_summary" />
   <java-symbol type="string" name="zen_mode_until" />
-  <java-symbol type="string" name="zen_mode_next_alarm_summary" />
-  <java-symbol type="string" name="zen_mode_next_alarm_line_one" />
+  <java-symbol type="string" name="zen_mode_feature_name" />
+  <java-symbol type="string" name="zen_mode_downtime_feature_name" />
+  <java-symbol type="string" name="zen_mode_default_weeknights_name" />
+  <java-symbol type="string" name="zen_mode_default_weekends_name" />
   <java-symbol type="array" name="config_system_condition_providers" />
-  <java-symbol type="integer" name="config_next_alarm_condition_lookahead_threshold_hrs" />
-  <java-symbol type="integer" name="config_downtime_condition_lookahead_threshold_hrs" />
   <java-symbol type="string" name="muted_by" />
 
   <java-symbol type="string" name="select_day" />
@@ -2182,8 +2181,9 @@
   <java-symbol type="bool" name="config_LTE_eri_for_network_name" />
   <java-symbol type="bool" name="config_defaultInTouchMode" />
 
+  <java-symbol type="string" name="usb_midi_peripheral_name" />
   <java-symbol type="string" name="usb_midi_peripheral_manufacturer_name" />
-  <java-symbol type="string" name="usb_midi_peripheral_model_name" />
+  <java-symbol type="string" name="usb_midi_peripheral_product_name" />
 
   <java-symbol type="bool" name="allow_stacked_button_bar" />
   <java-symbol type="id" name="spacer" />
@@ -2231,4 +2231,5 @@
   <java-symbol type="string" name="date_picker_next_month_button" />
   <java-symbol type="layout" name="date_picker_month_item_material" />
   <java-symbol type="id" name="month_view" />
+  <java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
 </resources>
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 1bdc1ec..5f4199a 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -18,7 +18,6 @@
 -->
 
 <!-- Default configuration for zen mode.  See android.service.notification.ZenModeConfig. -->
-<zen version="1">
-    <allow calls="false" messages="false" />
-    <sleep startHour="22" startMin="0" endHour="7" endMin="0" />
+<zen version="2">
+    <allow calls="true" messages="false" reminders="true" events="true" />
 </zen>
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 78eee37..05a81de 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -163,7 +163,6 @@
         inflateLayers(r, parser, attrs, theme);
 
         ensurePadding();
-        onStateChange(getState());
     }
 
     /**
@@ -307,7 +306,6 @@
         }
 
         ensurePadding();
-        onStateChange(getState());
     }
 
     @Override
diff --git a/keystore/java/android/security/KeyStoreKeyCharacteristics.java b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
index 543b5d8..1f5d400 100644
--- a/keystore/java/android/security/KeyStoreKeyCharacteristics.java
+++ b/keystore/java/android/security/KeyStoreKeyCharacteristics.java
@@ -31,7 +31,7 @@
     private KeyStoreKeyCharacteristics() {}
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({Origin.GENERATED_INSIDE_TEE, Origin.GENERATED_OUTSIDE_OF_TEE, Origin.IMPORTED})
+    @IntDef({Origin.GENERATED, Origin.IMPORTED})
     public @interface OriginEnum {}
 
     /**
@@ -40,14 +40,11 @@
     public static abstract class Origin {
         private Origin() {}
 
-        /** Key was generated inside a TEE. */
-        public static final int GENERATED_INSIDE_TEE = 1;
+        /** Key was generated inside AndroidKeyStore. */
+        public static final int GENERATED = 1 << 0;
 
-        /** Key was generated outside of a TEE. */
-        public static final int GENERATED_OUTSIDE_OF_TEE = 2;
-
-        /** Key was imported. */
-        public static final int IMPORTED = 0;
+        /** Key was imported into AndroidKeyStore. */
+        public static final int IMPORTED = 1 << 1;
 
         /**
          * @hide
@@ -55,9 +52,7 @@
         public static @OriginEnum int fromKeymaster(int origin) {
             switch (origin) {
                 case KeymasterDefs.KM_ORIGIN_HARDWARE:
-                    return GENERATED_INSIDE_TEE;
-                case KeymasterDefs.KM_ORIGIN_SOFTWARE:
-                    return GENERATED_OUTSIDE_OF_TEE;
+                    return GENERATED;
                 case KeymasterDefs.KM_ORIGIN_IMPORTED:
                     return IMPORTED;
                 default:
diff --git a/keystore/java/android/security/KeyStoreKeyConstraints.java b/keystore/java/android/security/KeyStoreKeyConstraints.java
index e61092f..98ac3e7 100644
--- a/keystore/java/android/security/KeyStoreKeyConstraints.java
+++ b/keystore/java/android/security/KeyStoreKeyConstraints.java
@@ -164,7 +164,7 @@
                 case RSA:
                     return KeymasterDefs.KM_ALGORITHM_RSA;
                 case EC:
-                    return KeymasterDefs.KM_ALGORITHM_ECDSA;
+                    return KeymasterDefs.KM_ALGORITHM_EC;
                 default:
                     throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
             }
@@ -181,7 +181,7 @@
                     return HMAC;
                 case KeymasterDefs.KM_ALGORITHM_RSA:
                     return RSA;
-                case KeymasterDefs.KM_ALGORITHM_ECDSA:
+                case KeymasterDefs.KM_ALGORITHM_EC:
                     return EC;
                 default:
                     throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
diff --git a/keystore/java/android/security/KeyStoreKeySpec.java b/keystore/java/android/security/KeyStoreKeySpec.java
index 256d9b3..65bb236 100644
--- a/keystore/java/android/security/KeyStoreKeySpec.java
+++ b/keystore/java/android/security/KeyStoreKeySpec.java
@@ -28,6 +28,7 @@
 public class KeyStoreKeySpec implements KeySpec {
     private final String mKeystoreAlias;
     private final int mKeySize;
+    private final boolean mTeeBacked;
     private final @KeyStoreKeyCharacteristics.OriginEnum int mOrigin;
     private final Date mKeyValidityStart;
     private final Date mKeyValidityForOriginationEnd;
@@ -46,6 +47,7 @@
      * @hide
      */
     KeyStoreKeySpec(String keystoreKeyAlias,
+            boolean teeBacked,
             @KeyStoreKeyCharacteristics.OriginEnum int origin,
             int keySize,
             Date keyValidityStart,
@@ -61,6 +63,7 @@
             int userAuthenticationValidityDurationSeconds,
             boolean invalidatedOnNewFingerprintEnrolled) {
         mKeystoreAlias = keystoreKeyAlias;
+        mTeeBacked = teeBacked;
         mOrigin = origin;
         mKeySize = keySize;
         mKeyValidityStart = keyValidityStart;
@@ -85,6 +88,14 @@
     }
 
     /**
+     * Returns {@code true} if the key is TEE-backed. Key material of TEE-backed keys is available
+     * in plaintext only inside the TEE.
+     */
+    public boolean isTeeBacked() {
+        return mTeeBacked;
+    }
+
+    /**
      * Gets the origin of the key.
      */
     public @KeyStoreKeyCharacteristics.OriginEnum int getOrigin() {
diff --git a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
index 8bf228a..a5e87d1 100644
--- a/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/KeyStoreSecretKeyFactorySpi.java
@@ -70,7 +70,8 @@
                     + " Keystore error: " + errorCode);
         }
 
-        @KeyStoreKeyCharacteristics.OriginEnum Integer origin;
+        boolean teeBacked;
+        @KeyStoreKeyCharacteristics.OriginEnum int origin;
         int keySize;
         @KeyStoreKeyConstraints.PurposeEnum int purposes;
         @KeyStoreKeyConstraints.AlgorithmEnum int algorithm;
@@ -80,11 +81,17 @@
         @KeyStoreKeyConstraints.UserAuthenticatorEnum int userAuthenticators;
         @KeyStoreKeyConstraints.UserAuthenticatorEnum int teeEnforcedUserAuthenticators;
         try {
-            origin = KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_ORIGIN);
-            if (origin == null) {
+            if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+                teeBacked = true;
+                origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+                        keyCharacteristics.hwEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+            } else if (keyCharacteristics.swEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) {
+                teeBacked = false;
+                origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(
+                        keyCharacteristics.swEnforced.getInt(KeymasterDefs.KM_TAG_ORIGIN, -1));
+            } else {
                 throw new InvalidKeySpecException("Key origin not available");
             }
-            origin = KeyStoreKeyCharacteristics.Origin.fromKeymaster(origin);
             Integer keySizeInteger =
                     KeymasterUtils.getInt(keyCharacteristics, KeymasterDefs.KM_TAG_KEY_SIZE);
             if (keySizeInteger == null) {
@@ -147,6 +154,7 @@
         boolean invalidatedOnNewFingerprintEnrolled = false;
 
         return new KeyStoreKeySpec(entryAlias,
+                teeBacked,
                 origin,
                 keySize,
                 keyValidityStart,
diff --git a/keystore/tests/src/android/security/KeyStoreTest.java b/keystore/tests/src/android/security/KeyStoreTest.java
index c9a140c..6e3f8be 100644
--- a/keystore/tests/src/android/security/KeyStoreTest.java
+++ b/keystore/tests/src/android/security/KeyStoreTest.java
@@ -798,7 +798,7 @@
         // TODO: Verify we have an RSA public key that's well formed.
     }
 
-    public void testAesOcbEncryptSuccess() throws Exception {
+    public void testAesGcmEncryptSuccess() throws Exception {
         String name = "test";
         KeymasterArguments args = new KeymasterArguments();
         args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
@@ -806,7 +806,7 @@
         args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
         args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
+        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_GCM);
         args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
         args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
         args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
@@ -903,9 +903,7 @@
         args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
         args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
-        args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
-        args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_CTR);
         args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
 
         KeyCharacteristics outCharacteristics = new KeyCharacteristics();
@@ -935,11 +933,9 @@
         args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_ENCRYPT);
         args.addInt(KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_DECRYPT);
         args.addInt(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES);
-        args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_NONE);
+        args.addInt(KeymasterDefs.KM_TAG_PADDING, KeymasterDefs.KM_PAD_PKCS7);
         args.addInt(KeymasterDefs.KM_TAG_KEY_SIZE, 256);
-        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_OCB);
-        args.addInt(KeymasterDefs.KM_TAG_CHUNK_LENGTH, 4096);
-        args.addInt(KeymasterDefs.KM_TAG_MAC_LENGTH, 16);
+        args.addInt(KeymasterDefs.KM_TAG_BLOCK_MODE, KeymasterDefs.KM_MODE_ECB);
         args.addInt(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 1);
 
         KeyCharacteristics outCharacteristics = new KeyCharacteristics();
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 56380db..c79ae77 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -164,6 +164,8 @@
     for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) {
         mRGBACacheTextures[i]->init();
     }
+
+    mDrawn = false;
 }
 
 void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) {
diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp
index 0380c51..d0812c9 100644
--- a/libs/hwui/RenderBufferCache.cpp
+++ b/libs/hwui/RenderBufferCache.cpp
@@ -158,6 +158,11 @@
                 buffer->getWidth(), buffer->getHeight());
 
         return true;
+    } else {
+        RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d) Size=%d, MaxSize=%d",
+        RenderBuffer::formatName(buffer->getFormat()),
+                 buffer->getWidth(), buffer->getHeight(), size, mMaxSize);
+        delete buffer;
     }
     return false;
 }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 0c1c7e9..726622f 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -439,6 +439,22 @@
     public static final String KEY_PRIORITY = "priority";
 
     /**
+     * A key describing the desired operating frame rate for video or sample rate for audio
+     * that the codec will need to operate at.
+     * <p>
+     * The associated value is an integer or a float representing frames-per-second or
+     * samples-per-second
+     * <p>
+     * This is used for cases like high-speed/slow-motion video capture, where the video encoder
+     * format contains the target playback rate (e.g. 30fps), but the component must be able to
+     * handle the high operating capture rate (e.g. 240fps).
+     * <p>
+     * This rate will be used by codec for resource planning and setting the operating points.
+     *
+     */
+    public static final String KEY_OPERATING_RATE = "operating-rate";
+
+    /**
      * A key describing the desired profile to be used by an encoder.
      * Constants are declared in {@link MediaCodecInfo.CodecProfileLevel}.
      * This key is only supported for codecs that specify a profile.
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 9a69c06..9aa8003 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -498,5 +498,11 @@
      * The video rotation angle may be 0, 90, 180, or 270 degrees.
      */
     public static final int METADATA_KEY_VIDEO_ROTATION = 24;
+    /**
+     * This key retrieves the original capture framerate, if it's
+     * available. The capture framerate will be a floating point
+     * number.
+     */
+    public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25;
     // Add more here...
 }
diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java
index 7201e25..af108eb 100644
--- a/media/java/android/media/midi/MidiDeviceInfo.java
+++ b/media/java/android/media/midi/MidiDeviceInfo.java
@@ -237,29 +237,23 @@
     }
 
     /**
-     * Returns information about an input port.
+     * Returns information about the device's ports.
+     * The ports are in unspecified order.
      *
-     * @param portNumber the number of the input port
-     * @return the input port's information object
+     * @return array of {@link PortInfo}
      */
-    public PortInfo getInputPortInfo(int portNumber) {
-        if (portNumber < 0 || portNumber >= mInputPortCount) {
-            throw new IllegalArgumentException("portNumber out of range");
-        }
-        return new PortInfo(PortInfo.TYPE_INPUT, portNumber, mInputPortNames[portNumber]);
-    }
+    public PortInfo[] getPortList() {
+        PortInfo[] portInfoList = new PortInfo[mInputPortCount + mOutputPortCount];
 
-    /**
-     * Returns information about an output port.
-     *
-     * @param portNumber the number of the output port
-     * @return the output port's information object
-     */
-    public PortInfo getOutputPortInfo(int portNumber) {
-        if (portNumber < 0 || portNumber >= mOutputPortCount) {
-            throw new IllegalArgumentException("portNumber out of range");
+        int index = 0;
+        for (int i = 0; i < mInputPortCount; i++) {
+            portInfoList[index++] = new PortInfo(PortInfo.TYPE_INPUT, i, mInputPortNames[i]);
         }
-        return new PortInfo(PortInfo.TYPE_OUTPUT, portNumber, mOutputPortNames[portNumber]);
+        for (int i = 0; i < mOutputPortCount; i++) {
+            portInfoList[index++] = new PortInfo(PortInfo.TYPE_OUTPUT, i, mOutputPortNames[i]);
+        }
+
+        return portInfoList;
     }
 
     /**
diff --git a/media/java/android/media/midi/MidiDeviceService.java b/media/java/android/media/midi/MidiDeviceService.java
index 8b1de3e..ce12a4f 100644
--- a/media/java/android/media/midi/MidiDeviceService.java
+++ b/media/java/android/media/midi/MidiDeviceService.java
@@ -91,7 +91,7 @@
     /**
      * Returns an array of {@link MidiReceiver} for the device's input ports.
      * Subclasses must override this to provide the receivers which will receive
-     * data sent to the device's input ports. An empty array or null should be returned if
+     * data sent to the device's input ports. An empty array should be returned if
      * the device has no input ports.
      * @return array of MidiReceivers
      */
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 310ccf0..3ca239a 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -123,5 +123,7 @@
       <item quantity="one">Copying <xliff:g id="count" example="1">%1$d</xliff:g> file.</item>
       <item quantity="other">Copying <xliff:g id="count" example="3">%1$d</xliff:g> files.</item>
     </plurals>
+    <!-- Text shown on the copy notification while DocumentsUI performs setup in preparation for copying files [CHAR LIMIT=32] -->
+    <string name="copy_preparing">Preparing for copy\u2026</string>
 
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index f135af49b..c826aba 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -16,19 +16,24 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.model.DocumentInfo.getCursorLong;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
 import android.app.IntentService;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.content.ContentResolver;
+import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
+import android.database.Cursor;
 import android.net.Uri;
 import android.os.CancellationSignal;
-import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
 import android.text.format.DateUtils;
 import android.util.Log;
 
@@ -36,12 +41,13 @@
 
 import libcore.io.IoUtils;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.text.NumberFormat;
 import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 
 public class CopyService extends IntentService {
     public static final String TAG = "CopyService";
@@ -56,6 +62,7 @@
     private volatile boolean mIsCancelled;
     // Parameters of the copy job. Requests to an IntentService are serialized so this code only
     // needs to deal with one job at a time.
+    private final List<Uri> mFailedFiles;
     private long mBatchSize;
     private long mBytesCopied;
     private long mStartTime;
@@ -65,9 +72,15 @@
     private long mSampleTime;
     private long mSpeed;
     private long mRemainingTime;
+    // Provider clients are acquired for the duration of each copy job. Note that there is an
+    // implicit assumption that all srcs come from the same authority.
+    private ContentProviderClient mSrcClient;
+    private ContentProviderClient mDstClient;
 
     public CopyService() {
         super("CopyService");
+
+        mFailedFiles = new ArrayList<Uri>();
     }
 
     @Override
@@ -88,27 +101,34 @@
         ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
         Uri destinationUri = intent.getData();
 
-        setupCopyJob(srcs, destinationUri);
+        try {
+            // Acquire content providers.
+            mSrcClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+                    srcs.get(0).authority);
+            mDstClient = DocumentsApplication.acquireUnstableProviderOrThrow(getContentResolver(),
+                    destinationUri.getAuthority());
 
-        ArrayList<String> failedFilenames = new ArrayList<String>();
-        for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
-            DocumentInfo src = srcs.get(i);
-            try {
-                copyFile(src, destinationUri);
-            } catch (IOException e) {
-                Log.e(TAG, "Failed to copy " + src.displayName, e);
-                failedFilenames.add(src.displayName);
+            setupCopyJob(srcs, destinationUri);
+
+            for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
+                copy(srcs.get(i), destinationUri);
             }
+        } catch (Exception e) {
+            // Catch-all to prevent any copy errors from wedging the app.
+            Log.e(TAG, "Exceptions occurred during copying", e);
+        } finally {
+            ContentProviderClient.releaseQuietly(mSrcClient);
+            ContentProviderClient.releaseQuietly(mDstClient);
+
+            // Dismiss the ongoing copy notification when the copy is done.
+            mNotificationManager.cancel(mJobId, 0);
+
+            if (mFailedFiles.size() > 0) {
+                // TODO: Display a notification when an error has occurred.
+            }
+
+            // TODO: Display a toast if the copy was cancelled.
         }
-
-        if (failedFilenames.size() > 0) {
-            // TODO: Display a notification when an error has occurred.
-        }
-
-        // Dismiss the ongoing copy notification when the copy is done.
-        mNotificationManager.cancel(mJobId, 0);
-
-        // TODO: Display a toast if the copy was cancelled.
     }
 
     @Override
@@ -123,8 +143,10 @@
      *
      * @param srcs A list of src files to copy.
      * @param destinationUri The URI of the destination directory.
+     * @throws RemoteException
      */
-    private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri) {
+    private void setupCopyJob(ArrayList<DocumentInfo> srcs, Uri destinationUri)
+            throws RemoteException {
         // Create an ID for this copy job. Use the timestamp.
         mJobId = String.valueOf(SystemClock.elapsedRealtime());
         // Reset the cancellation flag.
@@ -144,13 +166,13 @@
         // TODO: Add a content intent to open the destination folder.
 
         // Send an initial progress notification.
+        mProgressBuilder.setProgress(0, 0, true); // Indeterminate progress while setting up.
+        mProgressBuilder.setContentText(getString(R.string.copy_preparing));
         mNotificationManager.notify(mJobId, 0, mProgressBuilder.build());
 
         // Reset batch parameters.
-        mBatchSize = 0;
-        for (DocumentInfo doc : srcs) {
-            mBatchSize += doc.size;
-        }
+        mFailedFiles.clear();
+        mBatchSize = calculateFileSizes(srcs);
         mBytesCopied = 0;
         mStartTime = SystemClock.elapsedRealtime();
         mLastNotificationTime = 0;
@@ -165,6 +187,66 @@
     }
 
     /**
+     * Calculates the cumulative size of all the documents in the list. Directories are recursed
+     * into and totaled up.
+     *
+     * @param srcs
+     * @return Size in bytes.
+     * @throws RemoteException
+     */
+    private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
+        long result = 0;
+        for (DocumentInfo src : srcs) {
+            if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+                // Directories need to be recursed into.
+                result += calculateFileSizesHelper(src.derivedUri);
+            } else {
+                result += src.size;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Calculates (recursively) the cumulative size of all the files under the given directory.
+     *
+     * @throws RemoteException
+     */
+    private long calculateFileSizesHelper(Uri uri) throws RemoteException {
+        final String authority = uri.getAuthority();
+        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(authority,
+                DocumentsContract.getDocumentId(uri));
+        final String queryColumns[] = new String[] {
+                Document.COLUMN_DOCUMENT_ID,
+                Document.COLUMN_MIME_TYPE,
+                Document.COLUMN_SIZE
+        };
+
+        long result = 0;
+        Cursor cursor = null;
+        try {
+            cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+            while (cursor.moveToNext()) {
+                if (Document.MIME_TYPE_DIR.equals(
+                        getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
+                    // Recurse into directories.
+                    final Uri subdirUri = DocumentsContract.buildDocumentUri(authority,
+                            getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+                    result += calculateFileSizesHelper(subdirUri);
+                } else {
+                    // This may return -1 if the size isn't defined. Ignore those cases.
+                    long size = getCursorLong(cursor, Document.COLUMN_SIZE);
+                    result += size > 0 ? size : 0;
+                }
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        return result;
+    }
+
+    /**
      * Cancels the current copy job, if its ID matches the given ID.
      *
      * @param intent The cancellation intent.
@@ -173,7 +255,7 @@
         final String cancelledId = intent.getStringExtra(EXTRA_CANCEL);
         // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
         // cancellation requests from affecting unrelated copy jobs.
-        if (java.util.Objects.equals(mJobId, cancelledId)) {
+        if (Objects.equals(mJobId, cancelledId)) {
             // Set the cancel flag. This causes the copy loops to exit.
             mIsCancelled = true;
             // Dismiss the progress notification here rather than in the copy loop. This preserves
@@ -237,21 +319,78 @@
     }
 
     /**
-     * Copies a file to a given location.
+     * Copies a the given documents to the given location.
      *
-     * @param srcInfo The source file.
-     * @param destinationUri The URI of the destination directory.
-     * @throws IOException
+     * @param srcInfo DocumentInfos for the documents to copy.
+     * @param dstDirUri The URI of the destination directory.
+     * @throws RemoteException
      */
-    private void copyFile(DocumentInfo srcInfo, Uri destinationUri) throws IOException {
-        final Context context = getApplicationContext();
-        final ContentResolver resolver = context.getContentResolver();
-
-        final Uri writableDstUri = DocumentsContract.buildDocumentUriUsingTree(destinationUri,
-                DocumentsContract.getTreeDocumentId(destinationUri));
-        final Uri dstFileUri = DocumentsContract.createDocument(resolver, writableDstUri,
+    private void copy(DocumentInfo srcInfo, Uri dstDirUri) throws RemoteException {
+        final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
                 srcInfo.mimeType, srcInfo.displayName);
+        if (dstUri == null) {
+            // If this is a directory, the entire subdir will not be copied over.
+            Log.e(TAG, "Error while copying " + srcInfo.displayName);
+            mFailedFiles.add(srcInfo.derivedUri);
+            return;
+        }
 
+        if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+            copyDirectoryHelper(srcInfo.derivedUri, dstUri);
+        } else {
+            copyFileHelper(srcInfo.derivedUri, dstUri);
+        }
+    }
+
+    /**
+     * Handles recursion into a directory and copying its contents. Note that in linux terms, this
+     * does the equivalent of "cp src/* dst", not "cp -r src dst".
+     *
+     * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's
+     *            contents, not the directory itself.
+     * @param dstDirUri URI of the directory to copy to. Must be created beforehand.
+     * @throws RemoteException
+     */
+    private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri) throws RemoteException {
+        // Recurse into directories. Copy children into the new subdirectory.
+        final String queryColumns[] = new String[] {
+                Document.COLUMN_DISPLAY_NAME,
+                Document.COLUMN_DOCUMENT_ID,
+                Document.COLUMN_MIME_TYPE,
+                Document.COLUMN_SIZE
+        };
+        final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(),
+                DocumentsContract.getDocumentId(srcDirUri));
+        Cursor cursor = null;
+        try {
+            // Iterate over srcs in the directory; copy to the destination directory.
+            cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+            while (cursor.moveToNext()) {
+                final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+                final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
+                        childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME));
+                final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
+                        getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
+                if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
+                    copyDirectoryHelper(childUri, dstUri);
+                } else {
+                    copyFileHelper(childUri, dstUri);
+                }
+            }
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+    }
+
+    /**
+     * Handles copying a single file.
+     *
+     * @param srcUri URI of the file to copy from.
+     * @param dstUri URI of the *file* to copy to. Must be created beforehand.
+     * @throws RemoteException
+     */
+    private void copyFileHelper(Uri srcUri, Uri dstUri) throws RemoteException {
+        // Copy an individual file.
         CancellationSignal canceller = new CancellationSignal();
         ParcelFileDescriptor srcFile = null;
         ParcelFileDescriptor dstFile = null;
@@ -260,8 +399,8 @@
 
         boolean errorOccurred = false;
         try {
-            srcFile = resolver.openFileDescriptor(srcInfo.derivedUri, "r", canceller);
-            dstFile = resolver.openFileDescriptor(dstFileUri, "w", canceller);
+            srcFile = mSrcClient.openFile(srcUri, "r", canceller);
+            dstFile = mDstClient.openFile(dstUri, "w", canceller);
             src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
             dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
 
@@ -275,7 +414,8 @@
             dstFile.checkError();
         } catch (IOException e) {
             errorOccurred = true;
-            Log.e(TAG, "Error while copying " + srcInfo.displayName, e);
+            Log.e(TAG, "Error while copying " + srcUri.toString(), e);
+            mFailedFiles.add(srcUri);
         } finally {
             // This also ensures the file descriptors are closed.
             IoUtils.closeQuietly(src);
@@ -285,8 +425,13 @@
         if (errorOccurred || mIsCancelled) {
             // Clean up half-copied files.
             canceller.cancel();
-            if (!DocumentsContract.deleteDocument(resolver, dstFileUri)) {
-                Log.w(TAG, "Failed to clean up: " + srcInfo.displayName);
+            try {
+                DocumentsContract.deleteDocument(mDstClient, dstUri);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to clean up: " + srcUri, e);
+                // RemoteExceptions usually signal that the connection is dead, so there's no point
+                // attempting to continue. Propagate the exception up so the copy job is cancelled.
+                throw e;
             }
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 83071bd..0e3016d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -357,7 +357,12 @@
             return;
         }
 
-        Uri destination = data.getData();
+        // Because the destination picker is launched using an open tree intent, the URI returned is
+        // a tree URI. Convert it to a document URI.
+        // TODO: Remove this step when the destination picker returns a document URI.
+        final Uri destinationTree = data.getData();
+        final Uri destination = DocumentsContract.buildDocumentUriUsingTree(destinationTree,
+                DocumentsContract.getTreeDocumentId(destinationTree));
 
         List<DocumentInfo> docs = mSelectedDocumentsForCopy;
         Intent copyIntent = new Intent(context, CopyService.class);
@@ -506,8 +511,10 @@
             open.setVisible(!manageMode);
             share.setVisible(manageMode);
             delete.setVisible(manageMode);
-            // Hide the copy feature by default.
-            copy.setVisible(SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
+            // Hide the copy menu item in the recents folder. For now, also hide it by default
+            // unless the debug flag is enabled.
+            copy.setVisible((mType != TYPE_RECENT_OPEN) &&
+                    SystemProperties.getBoolean("debug.documentsui.enable_copy", false));
 
             return true;
         }
@@ -575,9 +582,7 @@
                 if (cursor != null) {
                     final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                     final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-                    if (!Document.MIME_TYPE_DIR.equals(docMimeType)) {
-                        valid = isDocumentEnabled(docMimeType, docFlags);
-                    }
+                    valid = isDocumentEnabled(docMimeType, docFlags);
                 }
 
                 if (!valid) {
@@ -606,8 +611,17 @@
 
     private void onShareDocuments(List<DocumentInfo> docs) {
         Intent intent;
-        if (docs.size() == 1) {
-            final DocumentInfo doc = docs.get(0);
+
+        // Filter out directories - those can't be shared.
+        List<DocumentInfo> docsForSend = Lists.newArrayList();
+        for (DocumentInfo doc: docs) {
+            if (!Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
+                docsForSend.add(doc);
+            }
+        }
+
+        if (docsForSend.size() == 1) {
+            final DocumentInfo doc = docsForSend.get(0);
 
             intent = new Intent(Intent.ACTION_SEND);
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -615,14 +629,14 @@
             intent.setType(doc.mimeType);
             intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
 
-        } else if (docs.size() > 1) {
+        } else if (docsForSend.size() > 1) {
             intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
             intent.addCategory(Intent.CATEGORY_DEFAULT);
 
             final ArrayList<String> mimeTypes = Lists.newArrayList();
             final ArrayList<Uri> uris = Lists.newArrayList();
-            for (DocumentInfo doc : docs) {
+            for (DocumentInfo doc : docsForSend) {
                 mimeTypes.add(doc.mimeType);
                 uris.add(doc.derivedUri);
             }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 9d16501..35e9636 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -93,6 +93,7 @@
     <uses-permission android:name="android.permission.BIND_APPWIDGET" />
     <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
     <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+    <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" />
 
     <application android:label="@string/app_label">
         <provider
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt.xml b/packages/SystemUI/res/drawable/ic_volume_bt.xml
deleted file mode 100644
index bce407a..0000000
--- a/packages/SystemUI/res/drawable/ic_volume_bt.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-     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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:viewportHeight="48.0"
-    android:viewportWidth="48.0"
-    android:width="24dp" >
-
-    <path
-        android:fillColor="@color/volume_icon_color"
-        android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z" />
-
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml
deleted file mode 100644
index 98a8137..0000000
--- a/packages/SystemUI/res/drawable/ic_volume_bt_mute.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-     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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:viewportHeight="48.0"
-    android:viewportWidth="48.0"
-    android:width="24dp" >
-
-    <path
-        android:fillColor="@color/volume_icon_color"
-        android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z" />
-
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
new file mode 100644
index 0000000..3364d9c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt.xml
@@ -0,0 +1,29 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24.0dp"
+    android:height="24.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+
+    <path
+        android:fillColor="@color/volume_icon_color"
+        android:pathData="M17.0,3.0l-7.0,0.0l0.0,9.3C9.5,12.1 9.0,12.0 8.5,12.0C6.0,12.0 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0s4.5,-2.3 4.5,-4.5C13.0,14.7 13.0,6.0 13.0,6.0l4.0,0.0L17.0,3.0z"/>
+    <path
+        android:fillColor="@color/volume_icon_color"
+        android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
new file mode 100644
index 0000000..39f54f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_volume_media_bt_mute.xml
@@ -0,0 +1,32 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0"
+    android:width="24dp" >
+
+    <path
+        android:fillColor="@color/volume_icon_color"
+        android:pathData="M13.0,6.0l4.0,0.0L17.0,3.0l-7.0,0.0l0.0,5.6l3.0,3.0C13.0,8.8 13.0,6.0 13.0,6.0z"/>
+    <path
+        android:fillColor="@color/volume_icon_color"
+        android:pathData="M2.1,5.7L8.4,12.0C6.0,12.1 4.0,14.0 4.0,16.5S6.0,21.0 8.5,21.0c2.7,0.0 4.5,-2.3 4.5,-4.3l0.0,-0.1l3.9,3.9l1.3,-1.3L3.4,4.5L2.1,5.7z"/>
+    <path
+        android:fillColor="@color/volume_icon_color"
+        android:pathData="M23.4,9.9L20.5,7.0L20.0,7.0l0.0,3.8l-2.3,-2.3L17.0,9.2l2.8,2.8L17.0,14.8l0.7,0.7l2.3,-2.3L20.0,17.0l0.5,0.0l2.8,-2.8L21.2,12.0L23.4,9.9zM21.0,8.9l0.9,0.9l-0.9,1.0L21.0,8.9zM21.9,14.2L21.0,15.1l0.0,-1.9L21.9,14.2z"/>
+
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3705157..779b55e 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -981,5 +981,5 @@
     <string name="volumeui_notification_text">Touch to restore the original.</string>
 
     <!-- Volume dialog zen toggle switch title -->
-    <string name="volume_zen_switch_text">Block interruptions</string>
+    <string name="volume_zen_switch_text">@*android:string/zen_mode_feature_name</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
new file mode 100644
index 0000000..9df67fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -0,0 +1,94 @@
+/*
+ * 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.systemui;
+
+import android.annotation.StringDef;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+
+public final class Prefs {
+    private Prefs() {} // no instantation
+
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+        Key.SEARCH_APP_WIDGET_ID,
+        Key.DEBUG_MODE_ENABLED,
+        Key.HOTSPOT_TILE_LAST_USED,
+        Key.COLOR_INVERSION_TILE_LAST_USED,
+        Key.DND_TILE_VISIBLE,
+        Key.DND_TILE_COMBINED_ICON
+    })
+    public @interface Key {
+        String SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
+        String DEBUG_MODE_ENABLED = "debugModeEnabled";
+        String HOTSPOT_TILE_LAST_USED = "HotspotTileLastUsed";
+        String COLOR_INVERSION_TILE_LAST_USED = "ColorInversionTileLastUsed";
+        String DND_TILE_VISIBLE = "DndTileVisible";
+        String DND_TILE_COMBINED_ICON = "DndTileCombinedIcon";
+    }
+
+    public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
+        return get(context).getBoolean(key, defaultValue);
+    }
+
+    public static void putBoolean(Context context, @Key String key, boolean value) {
+        get(context).edit().putBoolean(key, value).apply();
+    }
+
+    public static int getInt(Context context, @Key String key, int defaultValue) {
+        return get(context).getInt(key, defaultValue);
+    }
+
+    public static void putInt(Context context, @Key String key, int value) {
+        get(context).edit().putInt(key, value).apply();
+    }
+
+    public static long getLong(Context context, @Key String key, long defaultValue) {
+        return get(context).getLong(key, defaultValue);
+    }
+
+    public static void putLong(Context context, @Key String key, long value) {
+        get(context).edit().putLong(key, value).apply();
+    }
+
+    public static Map<String, ?> getAll(Context context) {
+        return get(context).getAll();
+    }
+
+    public static void remove(Context context, @Key String key) {
+        get(context).edit().remove(key).apply();
+    }
+
+    public static void registerListener(Context context,
+            OnSharedPreferenceChangeListener listener) {
+        get(context).registerOnSharedPreferenceChangeListener(listener);
+    }
+
+    public static void unregisterListener(Context context,
+            OnSharedPreferenceChangeListener listener) {
+        get(context).unregisterOnSharedPreferenceChangeListener(listener);
+    }
+
+    private static SharedPreferences get(Context context) {
+        return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
index e60aa53..f36019b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/UsageTracker.java
@@ -23,6 +23,7 @@
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.Listenable;
@@ -32,14 +33,15 @@
 
     private final Context mContext;
     private final long mTimeToShowTile;
-    private final String mPrefKey;
+    @Prefs.Key private final String mPrefKey;
     private final String mResetAction;
 
     private boolean mRegistered;
 
-    public UsageTracker(Context context, Class<?> tile, int timeoutResource) {
+    public UsageTracker(Context context, @Prefs.Key String prefKey, Class<?> tile,
+            int timeoutResource) {
         mContext = context;
-        mPrefKey = tile.getSimpleName() + "LastUsed";
+        mPrefKey = prefKey;
         mTimeToShowTile = MILLIS_PER_DAY * mContext.getResources().getInteger(timeoutResource);
         mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
     }
@@ -56,16 +58,16 @@
     }
 
     public boolean isRecentlyUsed() {
-        long lastUsed = getSharedPrefs().getLong(mPrefKey, 0);
+        long lastUsed = Prefs.getLong(mContext, mPrefKey, 0L /* defaultValue */);
         return (System.currentTimeMillis() - lastUsed) < mTimeToShowTile;
     }
 
     public void trackUsage() {
-        getSharedPrefs().edit().putLong(mPrefKey, System.currentTimeMillis()).commit();
+        Prefs.putLong(mContext, mPrefKey, System.currentTimeMillis());
     }
 
     public void reset() {
-        getSharedPrefs().edit().remove(mPrefKey).commit();
+        Prefs.remove(mContext, mPrefKey);
     }
 
     public void showResetConfirmation(String title, final Runnable onConfirmed) {
@@ -87,10 +89,6 @@
         d.show();
     }
 
-    private SharedPreferences getSharedPrefs() {
-        return mContext.getSharedPreferences(mContext.getPackageName(), 0);
-    }
-
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 5963a45..4a33f55 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -18,6 +18,7 @@
 
 import android.provider.Settings.Secure;
 
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.qs.SecureSetting;
@@ -50,7 +51,8 @@
                 }
             }
         };
-        mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class,
+        mUsageTracker = new UsageTracker(host.getContext(),
+                Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class,
                 R.integer.days_to_show_color_inversion_tile);
         if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
             mUsageTracker.trackUsage();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 64730c2..6ce63d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -29,6 +29,7 @@
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
 
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
 import com.android.systemui.statusbar.policy.ZenModeController;
@@ -40,8 +41,6 @@
 
     private static final String ACTION_SET_VISIBLE = "com.android.systemui.dndtile.SET_VISIBLE";
     private static final String EXTRA_VISIBLE = "visible";
-    private static final String PREF_KEY_VISIBLE = "DndTileVisible";
-    private static final String PREF_KEY_COMBINED_ICON = "DndTileCombinedIcon";
 
     private final ZenModeController mController;
     private final DndDetailAdapter mDetailAdapter;
@@ -57,19 +56,20 @@
     }
 
     public static void setVisible(Context context, boolean visible) {
-        getSharedPrefs(context).edit().putBoolean(PREF_KEY_VISIBLE, visible).commit();
+        Prefs.putBoolean(context, Prefs.Key.DND_TILE_VISIBLE, visible);
     }
 
     public static boolean isVisible(Context context) {
-        return getSharedPrefs(context).getBoolean(PREF_KEY_VISIBLE, false);
+        return Prefs.getBoolean(context, Prefs.Key.DND_TILE_VISIBLE, false /* defaultValue */);
     }
 
     public static void setCombinedIcon(Context context, boolean combined) {
-        getSharedPrefs(context).edit().putBoolean(PREF_KEY_COMBINED_ICON, combined).commit();
+        Prefs.putBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON, combined);
     }
 
     public static boolean isCombinedIcon(Context context) {
-        return getSharedPrefs(context).getBoolean(PREF_KEY_COMBINED_ICON, false);
+        return Prefs.getBoolean(context, Prefs.Key.DND_TILE_COMBINED_ICON,
+                false /* defaultValue */);
     }
 
     @Override
@@ -85,9 +85,9 @@
     @Override
     public void handleClick() {
         if (mState.value) {
-            mController.setZen(Global.ZEN_MODE_OFF);
+            mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
         } else {
-            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
             showDetail(true);
         }
     }
@@ -143,22 +143,20 @@
         mListening = listening;
         if (mListening) {
             mController.addCallback(mZenCallback);
-            getSharedPrefs(mContext).registerOnSharedPreferenceChangeListener(mPrefListener);
+            Prefs.registerListener(mContext, mPrefListener);
         } else {
             mController.removeCallback(mZenCallback);
-            getSharedPrefs(mContext).unregisterOnSharedPreferenceChangeListener(mPrefListener);
+            Prefs.unregisterListener(mContext, mPrefListener);
         }
     }
 
-    private static SharedPreferences getSharedPrefs(Context context) {
-        return context.getSharedPreferences(context.getPackageName(), 0);
-    }
-
     private final OnSharedPreferenceChangeListener mPrefListener
             = new OnSharedPreferenceChangeListener() {
         @Override
-        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-            if (PREF_KEY_COMBINED_ICON.equals(key) || PREF_KEY_VISIBLE.equals(key)) {
+        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+                @Prefs.Key String key) {
+            if (Prefs.Key.DND_TILE_COMBINED_ICON.equals(key) ||
+                    Prefs.Key.DND_TILE_VISIBLE.equals(key)) {
                 refreshState();
             }
         }
@@ -199,7 +197,7 @@
         @Override
         public void setToggleState(boolean state) {
             if (!state) {
-                mController.setZen(Global.ZEN_MODE_OFF);
+                mController.setZen(Global.ZEN_MODE_OFF, null, TAG);
                 showDetail(false);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index fcc517f..6063f80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.Intent;
 
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.qs.UsageTracker;
 import com.android.systemui.qs.QSTile;
@@ -105,7 +106,8 @@
     }
 
     private static UsageTracker newUsageTracker(Context context) {
-        return new UsageTracker(context, HotspotTile.class, R.integer.days_to_show_hotspot_tile);
+        return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class,
+                R.integer.days_to_show_hotspot_tile);
     }
 
     private final class Callback implements HotspotController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index 192acc6..c7f8919 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -59,8 +59,6 @@
     public static class Values {
         public static class App {
             public static int AppWidgetHostId = 1024;
-            public static String Key_SearchAppWidgetId = "searchAppWidgetId";
-            public static String Key_DebugModeEnabled = "debugModeEnabled";
             public static String DebugModeVersion = "A";
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 011c02e..1001feb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -34,6 +33,8 @@
 import android.view.View;
 import android.view.ViewStub;
 import android.widget.Toast;
+
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.DebugTrigger;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
@@ -565,10 +566,9 @@
     /** Called when debug mode is triggered */
     public void onDebugModeTriggered() {
         if (mConfig.developerOptionsEnabled) {
-            SharedPreferences settings = getSharedPreferences(getPackageName(), 0);
-            if (settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false)) {
+            if (Prefs.getBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, false /* boolean */)) {
                 // Disable the debug mode
-                settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
+                Prefs.remove(this, Prefs.Key.DEBUG_MODE_ENABLED);
                 mConfig.debugModeEnabled = false;
                 inflateDebugOverlay();
                 if (mDebugOverlay != null) {
@@ -576,7 +576,7 @@
                 }
             } else {
                 // Enable the debug mode
-                settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
+                Prefs.putBoolean(this, Prefs.Key.DEBUG_MODE_ENABLED, true);
                 mConfig.debugModeEnabled = true;
                 inflateDebugOverlay();
                 if (mDebugOverlay != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index abeb2b0..244fada 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -26,6 +25,8 @@
 import android.util.DisplayMetrics;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -177,12 +178,12 @@
 
     /** Updates the state, given the specified context */
     void update(Context context) {
-        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
         Resources res = context.getResources();
         DisplayMetrics dm = res.getDisplayMetrics();
 
         // Debug mode
-        debugModeEnabled = settings.getBoolean(Constants.Values.App.Key_DebugModeEnabled, false);
+        debugModeEnabled = Prefs.getBoolean(context, Prefs.Key.DEBUG_MODE_ENABLED,
+                false /* defaultValue */);
         if (debugModeEnabled) {
             Console.Enabled = true;
         }
@@ -206,7 +207,8 @@
 
         // Search Bar
         searchBarSpaceHeightPx = res.getDimensionPixelSize(R.dimen.recents_search_bar_space_height);
-        searchBarAppWidgetId = settings.getInt(Constants.Values.App.Key_SearchAppWidgetId, -1);
+        searchBarAppWidgetId = Prefs.getInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID,
+                -1 /* defaultValue */);
 
         // Task stack
         taskStackScrollDuration =
@@ -280,9 +282,7 @@
     /** Updates the search bar app widget */
     public void updateSearchBarAppWidgetId(Context context, int appWidgetId) {
         searchBarAppWidgetId = appWidgetId;
-        SharedPreferences settings = context.getSharedPreferences(context.getPackageName(), 0);
-        settings.edit().putInt(Constants.Values.App.Key_SearchAppWidgetId,
-                appWidgetId).apply();
+        Prefs.putInt(context, Prefs.Key.SEARCH_APP_WIDGET_ID, appWidgetId);
     }
 
     /** Updates the states that need to be re-read whenever we re-initialize. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index ad78f6a..974ce41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -113,6 +113,7 @@
 import com.android.systemui.DemoMode;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
+import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
@@ -2573,8 +2574,7 @@
         }
 
         pw.println("SharedPreferences:");
-        for (Map.Entry<String, ?> entry : mContext.getSharedPreferences(mContext.getPackageName(),
-                Context.MODE_PRIVATE).getAll().entrySet()) {
+        for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) {
             pw.print("  "); pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 0e21457..67cc788 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -17,16 +17,19 @@
 package com.android.systemui.statusbar.policy;
 
 import android.content.ComponentName;
+import android.net.Uri;
 import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 
 public interface ZenModeController {
     void addCallback(Callback callback);
     void removeCallback(Callback callback);
-    void setZen(int zen);
+    void setZen(int zen, Uri conditionId, String reason);
     int getZen();
     void requestConditions(boolean request);
-    void setExitCondition(Condition exitCondition);
-    Condition getExitCondition();
+    ZenRule getManualRule();
+    ZenModeConfig getConfig();
     long getNextAlarm();
     void setUserId(int userId);
     boolean isZenAvailable();
@@ -35,10 +38,11 @@
 
     public static class Callback {
         public void onZenChanged(int zen) {}
-        public void onExitConditionChanged(Condition exitCondition) {}
         public void onConditionsChanged(Condition[] conditions) {}
         public void onNextAlarmChanged() {}
         public void onZenAvailableChanged(boolean available) {}
         public void onEffectsSupressorChanged() {}
+        public void onManualRuleChanged(ZenRule rule) {}
+        public void onConfigChanged(ZenModeConfig config) {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index bea0c86..830a197 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -33,6 +33,7 @@
 import android.service.notification.Condition;
 import android.service.notification.IConditionListener;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 import android.util.Log;
 import android.util.Slog;
 
@@ -40,6 +41,7 @@
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
+import java.util.Objects;
 
 /** Platform implementation of the zen mode controller. **/
 public class ZenModeControllerImpl implements ZenModeController {
@@ -58,6 +60,7 @@
     private int mUserId;
     private boolean mRequesting;
     private boolean mRegistered;
+    private ZenModeConfig mConfig;
 
     public ZenModeControllerImpl(Context context, Handler handler) {
         mContext = context;
@@ -70,12 +73,13 @@
         mConfigSetting = new GlobalSetting(mContext, handler, Global.ZEN_MODE_CONFIG_ETAG) {
             @Override
             protected void handleValueChanged(int value) {
-                fireExitConditionChanged();
+                updateZenModeConfig();
             }
         };
+        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        mConfig = mNoMan.getZenModeConfig();
         mModeSetting.setListening(true);
         mConfigSetting.setListening(true);
-        mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mSetupObserver = new SetupObserver(handler);
         mSetupObserver.register();
@@ -97,8 +101,8 @@
     }
 
     @Override
-    public void setZen(int zen) {
-        mModeSetting.setValue(zen);
+    public void setZen(int zen, Uri conditionId, String reason) {
+        mNoMan.setZenMode(zen, conditionId, reason);
     }
 
     @Override
@@ -116,13 +120,13 @@
     }
 
     @Override
-    public void setExitCondition(Condition exitCondition) {
-        mNoMan.setZenModeCondition(exitCondition);
+    public ZenRule getManualRule() {
+        return mConfig == null ? null : mConfig.manualRule;
     }
 
     @Override
-    public Condition getExitCondition() {
-        return mNoMan.getZenModeCondition();
+    public ZenModeConfig getConfig() {
+        return mConfig;
     }
 
     @Override
@@ -185,11 +189,15 @@
         }
     }
 
-    private void fireExitConditionChanged() {
-        final Condition exitCondition = getExitCondition();
-        if (DEBUG) Slog.d(TAG, "exitCondition changed: " + exitCondition);
+    private void fireManualRuleChanged(ZenRule rule) {
         for (Callback cb : mCallbacks) {
-            cb.onExitConditionChanged(exitCondition);
+            cb.onManualRuleChanged(rule);
+        }
+    }
+
+    private void fireConfigChanged(ZenModeConfig config) {
+        for (Callback cb : mCallbacks) {
+            cb.onConfigChanged(config);
         }
     }
 
@@ -203,6 +211,17 @@
                 mConditions.values().toArray(new Condition[mConditions.values().size()]));
     }
 
+    private void updateZenModeConfig() {
+        final ZenModeConfig config = mNoMan.getZenModeConfig();
+        if (Objects.equals(config, mConfig)) return;
+        final ZenRule oldRule = mConfig != null ? mConfig.manualRule : null;
+        mConfig = config;
+        fireConfigChanged(config);
+        final ZenRule newRule = config != null ? config.manualRule : null;
+        if (Objects.equals(oldRule, newRule)) return;
+        fireManualRuleChanged(newRule);
+    }
+
     private final IConditionListener mListener = new IConditionListener.Stub() {
         @Override
         public void onConditionsReceived(Condition[] conditions) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Util.java b/packages/SystemUI/src/com/android/systemui/volume/Util.java
index 78baf67..216a4da 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Util.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Util.java
@@ -21,7 +21,6 @@
 import android.media.VolumeProvider;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.PlaybackState;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
 import android.view.View;
 import android.widget.TextView;
 
@@ -145,10 +144,6 @@
         return HMMAA.format(new Date(millis));
     }
 
-    public static String getShortTime(DowntimeInfo info) {
-        return ((info.endHour + 1) % 12) + ":" + (info.endMinute < 10 ? " " : "") + info.endMinute;
-    }
-
     public static void setText(TextView tv, CharSequence text) {
         if (Objects.equals(tv.getText(), text)) return;
         tv.setText(text);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index d8b3965..539fec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -38,7 +38,6 @@
 import android.os.SystemClock;
 import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenModeConfig.DowntimeInfo;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.SparseBooleanArray;
@@ -606,14 +605,6 @@
                     text = mContext.getString(R.string.volume_dnd_ends_at,
                             Util.getShortTime(countdown));
                     action = mContext.getString(R.string.volume_end_now);
-                } else {
-                    final DowntimeInfo info = ZenModeConfig.tryParseDowntimeConditionId(mState.
-                            exitCondition.id);
-                    if (info != null) {
-                        text = mContext.getString(R.string.volume_dnd_ends_at,
-                                Util.getShortTime(info));
-                        action = mContext.getString(R.string.volume_end_now);
-                    }
                 }
             }
             if (text == null) {
@@ -700,7 +691,8 @@
         final int iconRes =
                 isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
                 : ss.routedToBluetooth ?
-                        (ss.muted ? R.drawable.ic_volume_bt_mute : R.drawable.ic_volume_bt)
+                        (ss.muted ? R.drawable.ic_volume_media_bt_mute
+                                : R.drawable.ic_volume_media_bt)
                 : mAutomute && ss.level == 0 ? row.iconMuteRes
                 : (ss.muted ? row.iconMuteRes : row.iconRes);
         if (iconRes != row.cachedIconRes) {
@@ -712,9 +704,9 @@
         }
         row.iconState =
                 iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
-                : (iconRes == R.drawable.ic_volume_bt_mute || iconRes == row.iconMuteRes)
+                : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
                         ? Events.ICON_STATE_MUTE
-                : (iconRes == R.drawable.ic_volume_bt || iconRes == row.iconRes)
+                : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
                         ? Events.ICON_STATE_UNMUTE
                 : Events.ICON_STATE_UNKNOWN;
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 265e2c6..5bc8c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -41,6 +41,7 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -393,8 +394,15 @@
         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
     }
 
+    private Condition getExitCondition() {
+        final ZenModeConfig config = mNoMan.getZenModeConfig();
+        return config == null ? null
+                : config.manualRule == null ? null
+                : config.manualRule.condition;
+    }
+
     private boolean updateExitConditionW() {
-        final Condition exitCondition = mNoMan.getZenModeCondition();
+        final Condition exitCondition = getExitCondition();
         if (Objects.equals(mState.exitCondition, exitCondition)) return false;
         mState.exitCondition = exitCondition;
         return true;
@@ -476,12 +484,12 @@
     }
 
     private void onSetExitConditionW(Condition condition) {
-        mNoMan.setZenModeCondition(condition);
+        mNoMan.setZenMode(mState.zenMode, condition != null ? condition.id : null, TAG);
     }
 
     private void onSetZenModeW(int mode) {
         if (D.BUG) Log.d(TAG, "onSetZenModeW " + mode);
-        mNoMan.setZenMode(mode);
+        mNoMan.setZenMode(mode, null, TAG);
     }
 
     private void onDismissRequestedW(int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index f99eb6d..ef8257c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -17,10 +17,11 @@
 
 import android.animation.LayoutTransition;
 import android.animation.ValueAnimator;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.provider.Settings.Global;
-import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -34,6 +35,8 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.ZenModeController;
 
+import java.util.Objects;
+
 /**
  * Switch bar + zen mode panel (conditions) attached to the bottom of the volume dialog.
  */
@@ -57,6 +60,7 @@
     private TextView mSummaryLine2;
     private boolean mFooterExpanded;
     private int mZen = -1;
+    private ZenModeConfig mConfig;
     private Callback mCallback;
 
     public ZenFooter(Context context, AttributeSet attrs) {
@@ -102,8 +106,8 @@
                 setZen(zen);
             }
             @Override
-            public void onExitConditionChanged(Condition exitCondition) {
-                update();
+            public void onConfigChanged(ZenModeConfig config) {
+                setConfig(config);
             }
         });
         mSwitchBar.setOnClickListener(new OnClickListener() {
@@ -129,6 +133,7 @@
             }
         });
         mZen = mController.getZen();
+        mConfig = mController.getConfig();
         update();
     }
 
@@ -138,6 +143,12 @@
         update();
     }
 
+    private void setConfig(ZenModeConfig config) {
+        if (Objects.equals(mConfig, config)) return;
+        mConfig = config;
+        update();
+    }
+
     public boolean isZen() {
         return isZenPriority() || isZenAlarms() || isZenNone();
     }
@@ -196,7 +207,9 @@
                 : isZenNone() ? mContext.getString(R.string.interruption_level_none)
                 : null;
         Util.setText(mSummaryLine1, line1);
-        Util.setText(mSummaryLine2, mZenModePanel.getExitConditionText());
+        final String line2 = ZenModeConfig.getConditionSummary(mContext, mConfig,
+                ActivityManager.getCurrentUser());
+        Util.setText(mSummaryLine2, line2);
     }
 
     private final OnCheckedChangeListener mCheckedListener = new OnCheckedChangeListener() {
@@ -208,7 +221,7 @@
                         : Global.ZEN_MODE_OFF;
                 mZen = newZen;  // this one's optimistic
                 setFooterExpanded(isChecked);
-                mController.setZen(newZen);
+                mController.setZen(newZen, null, TAG);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index cb6c29f..f6d4c36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -32,6 +32,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -157,7 +158,6 @@
         }
         mZenButtons.getChildAt(3).setVisibility(mEmbedded ? GONE : VISIBLE);
         mZenEmbeddedDivider.setVisibility(mEmbedded ? VISIBLE : GONE);
-        setExpanded(mEmbedded);
         updateWidgets();
     }
 
@@ -278,7 +278,7 @@
         if (expanded == mExpanded) return;
         if (DEBUG) Log.d(mTag, "setExpanded " + expanded);
         mExpanded = expanded;
-        if (mExpanded) {
+        if (mExpanded && isShown()) {
             ensureSelection();
         }
         updateWidgets();
@@ -299,7 +299,7 @@
             });
         }
         if (mRequestingConditions) {
-            mTimeCondition = parseExistingTimeCondition(mExitCondition);
+            mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition);
             if (mTimeCondition != null) {
                 mBucketIndex = -1;
             } else {
@@ -327,10 +327,9 @@
         for (int i = 0; i < mMaxConditions; i++) {
             mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false));
         }
-        setExitCondition(mController.getExitCondition());
         refreshExitConditionText();
         mSessionZen = getSelectedZen(-1);
-        handleUpdateZen(mController.getZen());
+        handleUpdateManualRule(mController.getManualRule());
         if (DEBUG) Log.d(mTag, "init mExitCondition=" + mExitCondition);
         hideAllConditions();
         mController.addCallback(mZenCallback);
@@ -352,6 +351,10 @@
         return condition != null ? condition.id : null;
     }
 
+    private Uri getRealConditionId(Condition condition) {
+        return isForever(condition) ? null : getConditionId(condition);
+    }
+
     private static boolean sameConditionId(Condition lhs, Condition rhs) {
         return lhs == null ? rhs == null : rhs != null && lhs.id.equals(rhs.id);
     }
@@ -360,18 +363,18 @@
         return condition == null ? null : condition.copy();
     }
 
-    public String getExitConditionText() {
-        return mExitConditionText;
+    private void refreshExitConditionText() {
+        mExitConditionText = getExitConditionText(mContext, mExitCondition);
     }
 
-    private void refreshExitConditionText() {
-        if (mExitCondition == null) {
-            mExitConditionText = foreverSummary();
-        } else if (isCountdown(mExitCondition)) {
-            final Condition condition = parseExistingTimeCondition(mExitCondition);
-            mExitConditionText = condition != null ? condition.summary : foreverSummary();
+    public static String getExitConditionText(Context context, Condition exitCondition) {
+        if (exitCondition == null) {
+            return foreverSummary(context);
+        } else if (isCountdown(exitCondition)) {
+            final Condition condition = parseExistingTimeCondition(context, exitCondition);
+            return condition != null ? condition.summary : foreverSummary(context);
         } else {
-            mExitConditionText = mExitCondition.summary;
+            return exitCondition.summary;
         }
     }
 
@@ -386,9 +389,16 @@
         mIconPulser.start(noneButton);
     }
 
+    private void handleUpdateManualRule(ZenRule rule) {
+        final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF;
+        handleUpdateZen(zen);
+        final Condition c = rule != null ? rule.condition : null;
+        handleExitConditionChanged(c);
+    }
+
     private void handleUpdateZen(int zen) {
         if (mSessionZen != -1 && mSessionZen != zen) {
-            setExpanded(mEmbedded || zen != Global.ZEN_MODE_OFF);
+            setExpanded(mEmbedded && isShown() || !mEmbedded && zen != Global.ZEN_MODE_OFF);
             mSessionZen = zen;
         }
         mZenButtons.setSelectedValue(zen);
@@ -402,6 +412,20 @@
         }
     }
 
+    private void handleExitConditionChanged(Condition exitCondition) {
+        setExitCondition(exitCondition);
+        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
+        final int N = getVisibleConditions();
+        for (int i = 0; i < N; i++) {
+            final ConditionTag tag = getConditionTagAt(i);
+            if (tag != null) {
+                if (sameConditionId(tag.condition, mExitCondition)) {
+                    bind(exitCondition, mZenConditions.getChildAt(i));
+                }
+            }
+        }
+    }
+
     private Condition getSelectedCondition() {
         final int N = getVisibleConditions();
         for (int i = 0; i < N; i++) {
@@ -447,14 +471,14 @@
                 ? mSubheadWarningColor : mSubheadColor);
     }
 
-    private Condition parseExistingTimeCondition(Condition condition) {
+    private static Condition parseExistingTimeCondition(Context context, Condition condition) {
         if (condition == null) return null;
         final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
         if (time == 0) return null;
         final long now = System.currentTimeMillis();
         final long span = time - now;
         if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
-        return ZenModeConfig.toTimeCondition(mContext,
+        return ZenModeConfig.toTimeCondition(context,
                 time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser());
     }
 
@@ -514,18 +538,18 @@
             mZenConditions.getChildAt(i).setVisibility(GONE);
         }
         // ensure something is selected
-        if (mExpanded) {
+        if (mExpanded && isShown()) {
             ensureSelection();
         }
     }
 
     private Condition forever() {
-        return new Condition(mForeverId, foreverSummary(), "", "", 0 /*icon*/, Condition.STATE_TRUE,
-                0 /*flags*/);
+        return new Condition(mForeverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+                Condition.STATE_TRUE, 0 /*flags*/);
     }
 
-    private String foreverSummary() {
-        return mContext.getString(com.android.internal.R.string.zen_mode_forever);
+    private static String foreverSummary(Context context) {
+        return context.getString(com.android.internal.R.string.zen_mode_forever);
     }
 
     private ConditionTag getConditionTagAt(int index) {
@@ -574,21 +598,7 @@
         }
     }
 
-    private void handleExitConditionChanged(Condition exitCondition) {
-        setExitCondition(exitCondition);
-        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitCondition);
-        final int N = getVisibleConditions();
-        for (int i = 0; i < N; i++) {
-            final ConditionTag tag = getConditionTagAt(i);
-            if (tag != null) {
-                if (sameConditionId(tag.condition, mExitCondition)) {
-                    bind(exitCondition, mZenConditions.getChildAt(i));
-                }
-            }
-        }
-    }
-
-    private boolean isCountdown(Condition c) {
+    private static boolean isCountdown(Condition c) {
         return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
     }
 
@@ -770,17 +780,21 @@
 
     private void select(final Condition condition) {
         if (DEBUG) Log.d(mTag, "select " + condition);
-        final boolean isForever = isForever(condition);
+        if (mSessionZen == -1 || mSessionZen == Global.ZEN_MODE_OFF) {
+            if (DEBUG) Log.d(mTag, "Ignoring condition selection outside of manual zen");
+            return;
+        }
+        final Uri realConditionId = getRealConditionId(condition);
         if (mController != null) {
             AsyncTask.execute(new Runnable() {
                 @Override
                 public void run() {
-                    mController.setExitCondition(isForever ? null : condition);
+                    mController.setZen(mSessionZen, realConditionId, TAG + ".selectCondition");
                 }
             });
         }
         setExitCondition(condition);
-        if (isForever) {
+        if (realConditionId == null) {
             mPrefs.setMinuteIndex(-1);
         } else if (isCountdown(condition) && mBucketIndex != -1) {
             mPrefs.setMinuteIndex(mBucketIndex);
@@ -808,24 +822,19 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         @Override
-        public void onZenChanged(int zen) {
-            mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
-        }
-        @Override
         public void onConditionsChanged(Condition[] conditions) {
             mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
         }
 
         @Override
-        public void onExitConditionChanged(Condition exitCondition) {
-            mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitCondition).sendToTarget();
+        public void onManualRuleChanged(ZenRule rule) {
+            mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget();
         }
     };
 
     private final class H extends Handler {
         private static final int UPDATE_CONDITIONS = 1;
-        private static final int EXIT_CONDITION_CHANGED = 2;
-        private static final int UPDATE_ZEN = 3;
+        private static final int MANUAL_RULE_CHANGED = 2;
 
         private H() {
             super(Looper.getMainLooper());
@@ -835,10 +844,8 @@
         public void handleMessage(Message msg) {
             if (msg.what == UPDATE_CONDITIONS) {
                 handleUpdateConditions((Condition[]) msg.obj);
-            } else if (msg.what == EXIT_CONDITION_CHANGED) {
-                handleExitConditionChanged((Condition) msg.obj);
-            } else if (msg.what == UPDATE_ZEN) {
-                handleUpdateZen(msg.arg1);
+            } else if (msg.what == MANUAL_RULE_CHANGED) {
+                handleUpdateManualRule((ZenRule) msg.obj);
             }
         }
     }
@@ -930,12 +937,13 @@
     private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
         @Override
         public void onSelected(final Object value) {
-            if (value != null && mZenButtons.isShown()) {
+            if (value != null && mZenButtons.isShown() && isAttachedToWindow()) {
                 if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
+                final Uri realConditionId = getRealConditionId(mSessionExitCondition);
                 AsyncTask.execute(new Runnable() {
                     @Override
                     public void run() {
-                        mController.setZen((Integer) value);
+                        mController.setZen((Integer) value, realConditionId, TAG + ".selectZen");
                     }
                 });
             }
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index fd19d49..5fc1f93 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,6 +16,7 @@
 
 package android.renderscript;
 
+import java.io.File;
 import java.lang.reflect.Method;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
@@ -231,6 +232,11 @@
         validate();
         rsnContextSetPriority(mContext, p);
     }
+    native void rsnContextSetCacheDir(long con, String cacheDir);
+    synchronized void nContextSetCacheDir(String cacheDir) {
+        validate();
+        rsnContextSetCacheDir(mContext, cacheDir);
+    }
     native void rsnContextDump(long con, int bits);
     synchronized void nContextDump(int bits) {
         validate();
@@ -1325,6 +1331,14 @@
         if (rs.mContext == 0) {
             throw new RSDriverException("Failed to create RS context.");
         }
+
+        // set up cache directory for entire context
+        final String CACHE_PATH = "com.android.renderscript.cache";
+        File f = new File(RenderScriptCacheDir.mCacheDir, CACHE_PATH);
+        String mCachePath = f.getAbsolutePath();
+        f.mkdirs();
+        rs.nContextSetCacheDir(mCachePath);
+
         rs.mMessageThread = new MessageThread(rs);
         rs.mMessageThread.start();
         return rs;
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index 40fad38..a40233a 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -684,6 +684,17 @@
     rsContextSetPriority((RsContext)con, p);
 }
 
+static void
+nContextSetCacheDir(JNIEnv *_env, jobject _this, jlong con, jstring cacheDir)
+{
+    AutoJavaStringToUTF8 cacheDirUTF(_env, cacheDir);
+
+    if (kLogApi) {
+        ALOGD("ContextSetCacheDir, con(%p), cacheDir(%s)", (RsContext)con, cacheDirUTF.c_str());
+    }
+    rsContextSetCacheDir((RsContext)con, cacheDirUTF.c_str(), cacheDirUTF.length());
+}
+
 
 
 static void
@@ -2307,6 +2318,7 @@
 {"rsnContextCreateGL",               "(JIIIIIIIIIIIIFI)J",                    (void*)nContextCreateGL },
 {"rsnContextFinish",                 "(J)V",                                  (void*)nContextFinish },
 {"rsnContextSetPriority",            "(JI)V",                                 (void*)nContextSetPriority },
+{"rsnContextSetCacheDir",            "(JLjava/lang/String;)V",                (void*)nContextSetCacheDir },
 {"rsnContextSetSurface",             "(JIILandroid/view/Surface;)V",          (void*)nContextSetSurface },
 {"rsnContextDestroy",                "(J)V",                                  (void*)nContextDestroy },
 {"rsnContextDump",                   "(JI)V",                                 (void*)nContextDump },
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index b36f515..56f9942 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -18,14 +18,18 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.PendingIntent;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.service.persistentdata.IPersistentDataBlockService;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -428,6 +432,29 @@
         }
 
         @Override
+        public void wipeIfAllowed(Bundle bundle, PendingIntent pi) {
+            // Should only be called by owner
+            if (UserHandle.getCallingUserId() != UserHandle.USER_OWNER) {
+                throw new SecurityException("Only the Owner is allowed to wipe");
+            }
+            // Caller must be able to query the the state of the PersistentDataBlock
+            enforcePersistentDataBlockAccess();
+            String allowedPackage = mContext.getResources()
+                    .getString(R.string.config_persistentDataPackageName);
+            Intent intent = new Intent();
+            intent.setPackage(allowedPackage);
+            intent.setAction(PersistentDataBlockManager.ACTION_WIPE_IF_ALLOWED);
+            intent.putExtras(bundle);
+            intent.putExtra(PersistentDataBlockManager.EXTRA_WIPE_IF_ALLOWED_CALLBACK, pi);
+            long id = Binder.clearCallingIdentity();
+            try {
+                mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+            } finally {
+                restoreCallingIdentity(id);
+            }
+        }
+
+        @Override
         public void setOemUnlockEnabled(boolean enabled) {
             // do not allow monkey to flip the flag
             if (ActivityManager.isUserAMonkey()) {
@@ -450,10 +477,7 @@
 
         @Override
         public int getDataBlockSize() {
-            if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
-                    != PackageManager.PERMISSION_GRANTED) {
-                enforceUid(Binder.getCallingUid());
-            }
+            enforcePersistentDataBlockAccess();
 
             DataInputStream inputStream;
             try {
@@ -475,6 +499,13 @@
             }
         }
 
+        private void enforcePersistentDataBlockAccess() {
+            if (mContext.checkCallingPermission(Manifest.permission.ACCESS_PDB_STATE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                enforceUid(Binder.getCallingUid());
+            }
+        }
+
         @Override
         public long getMaximumDataBlockSize() {
             long actualSize = getBlockDeviceSize() - HEADER_SIZE - 1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 14be293..0581e22 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -100,6 +100,7 @@
 import com.google.android.collect.Maps;
 
 import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -13947,7 +13948,7 @@
             } else if ("-h".equals(opt)) {
                 pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
                 pw.println("  -a: include all available information for each process.");
-                pw.println("  -d: include dalvik details when dumping process details.");
+                pw.println("  -d: include dalvik details.");
                 pw.println("  -c: dump in a compact machine-parseable representation.");
                 pw.println("  --oom: only show processes organized by oom adj.");
                 pw.println("  --local: only collect details locally, don't call process.");
@@ -14034,6 +14035,8 @@
         final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
         long nativePss = 0;
         long dalvikPss = 0;
+        long[] dalvikSubitemPss = dumpDalvik ? new long[Debug.MemoryInfo.NUM_DVK_STATS] :
+                EmptyArray.LONG;
         long otherPss = 0;
         long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
 
@@ -14111,6 +14114,9 @@
 
                     nativePss += mi.nativePss;
                     dalvikPss += mi.dalvikPss;
+                    for (int j=0; j<dalvikSubitemPss.length; j++) {
+                        dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                    }
                     otherPss += mi.otherPss;
                     for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
                         long mem = mi.getOtherPss(j);
@@ -14169,6 +14175,10 @@
 
                         nativePss += mi.nativePss;
                         dalvikPss += mi.dalvikPss;
+                        for (int j=0; j<dalvikSubitemPss.length; j++) {
+                            dalvikSubitemPss[j] += mi.getOtherPss(
+                                    Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                        }
                         otherPss += mi.otherPss;
                         for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
                             long mem = mi.getOtherPss(j);
@@ -14187,7 +14197,16 @@
             ArrayList<MemItem> catMems = new ArrayList<MemItem>();
 
             catMems.add(new MemItem("Native", "Native", nativePss, -1));
-            catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+            final MemItem dalvikItem = new MemItem("Dalvik", "Dalvik", dalvikPss, -2);
+            if (dalvikSubitemPss.length > 0) {
+                dalvikItem.subitems = new ArrayList<MemItem>();
+                for (int j=0; j<dalvikSubitemPss.length; j++) {
+                    final String name = Debug.MemoryInfo.getOtherLabel(
+                            Debug.MemoryInfo.NUM_OTHER_STATS + j);
+                    dalvikItem.subitems.add(new MemItem(name, name, dalvikSubitemPss[j], j));
+                }
+            }
+            catMems.add(dalvikItem);
             catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
             for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
                 String label = Debug.MemoryInfo.getOtherLabel(j);
@@ -15479,8 +15498,9 @@
         }
 
         synchronized (this) {
-            if (callerApp != null && callerApp.pid == 0) {
-                // Caller already died
+            if (callerApp != null && (callerApp.thread == null
+                    || callerApp.thread.asBinder() != caller.asBinder())) {
+                // Original caller already died
                 return null;
             }
             ReceiverList rl
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index c97bdb6..a3c9738 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -348,6 +348,10 @@
         private int mActualBacklight = INITIAL_BACKLIGHT;
         private boolean mChangeInProgress;
 
+        public PhotonicModulator() {
+            super("PhotonicModulator");
+        }
+
         public boolean setState(int state, int backlight) {
             synchronized (mLock) {
                 if (state != mPendingState || backlight != mPendingBacklight) {
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 9dcc529..2da9d8e 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -106,7 +106,8 @@
                 mMode = mode;
                 mOnMS = onMS;
                 mOffMS = offMS;
-                Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", " + color + ")");
+                Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x"
+                        + Integer.toHexString(color) + ")");
                 try {
                     setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
                 } finally {
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/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index e029c58..4696771 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -28,6 +28,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
 import java.io.PrintWriter;
@@ -56,8 +57,10 @@
     // Guarded by synchronized(this).
     private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
     private final SQLiteLog mSQLiteLog;
+    private final Context mContext;
 
     public NotificationUsageStats(Context context) {
+        mContext = context;
         mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
     }
 
@@ -103,6 +106,8 @@
      * Called when the user dismissed the notification via the UI.
      */
     public synchronized void registerDismissedByUser(NotificationRecord notification) {
+        MetricsLogger.histogram(mContext, "note_dismiss_longevity",
+                (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
         notification.stats.onDismiss();
         for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
             stats.numDismissedByUser++;
@@ -117,6 +122,8 @@
      * Called when the user clicked the notification in the UI.
      */
     public synchronized void registerClickedByUser(NotificationRecord notification) {
+        MetricsLogger.histogram(mContext, "note_click_longevity",
+                (int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
         notification.stats.onClick();
         for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
             stats.numClickedByUser++;
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/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 5eb318b..10f1696 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import android.util.LruCache;
 import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -244,6 +245,7 @@
 
         if (pendingLookups.isEmpty()) {
             if (INFO) Slog.i(TAG, "final affinity: " + affinity);
+            if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
             return null;
         }
 
@@ -453,6 +455,8 @@
                 Slog.d(TAG, "Validation finished in " + (System.currentTimeMillis() - timeStartMs) +
                         "ms");
             }
+
+            if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
         }
 
         @Override
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..2aaeb9d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -0,0 +1,279 @@
+/**
+ * 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.ArrayMap;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Objects;
+
+public class ZenModeFiltering {
+    private static final String TAG = ZenModeHelper.TAG;
+    private static final boolean DEBUG = ZenModeHelper.DEBUG;
+
+    static final RepeatCallers REPEAT_CALLERS = new RepeatCallers();
+
+    private final Context mContext;
+
+    private ComponentName mDefaultPhoneApp;
+
+    public ZenModeFiltering(Context context) {
+        mContext = context;
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mDefaultPhoneApp="); pw.println(mDefaultPhoneApp);
+        pw.print(prefix); pw.print("RepeatCallers.mThresholdMinutes=");
+        pw.println(REPEAT_CALLERS.mThresholdMinutes);
+        synchronized (REPEAT_CALLERS) {
+            if (!REPEAT_CALLERS.mCalls.isEmpty()) {
+                pw.print(prefix); pw.println("RepeatCallers.mCalls=");
+                for (int i = 0; i < REPEAT_CALLERS.mCalls.size(); i++) {
+                    pw.print(prefix); pw.print("  ");
+                    pw.print(REPEAT_CALLERS.mCalls.keyAt(i));
+                    pw.print(" at ");
+                    pw.println(ts(REPEAT_CALLERS.mCalls.valueAt(i)));
+                }
+            }
+        }
+    }
+
+    private static String ts(long time) {
+        return new Date(time) + " (" + time + ")";
+    }
+
+    /**
+     * @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(Context context, 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.allowRepeatCallers && REPEAT_CALLERS.isRepeat(context, extras)) return true;
+            if (!config.allowCalls) return false; // no other calls get through
+            if (validator != null) {
+                final float contactAffinity = validator.getContactAffinity(userHandle, extras,
+                        contactsTimeoutMs, timeoutAffinity);
+                return audienceMatches(config, contactAffinity);
+            }
+        }
+        return true;
+    }
+
+    private static Bundle extras(NotificationRecord record) {
+        return record != null && record.sbn != null && record.sbn.getNotification() != null
+                ? record.sbn.getNotification().extras : null;
+    }
+
+    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.allowRepeatCallers
+                            && REPEAT_CALLERS.isRepeat(mContext, extras(record))) {
+                        ZenLog.traceNotIntercepted(record, "repeatCaller");
+                        return false;
+                    }
+                    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;
+        }
+    }
+
+    private static class RepeatCallers {
+        private final ArrayMap<String, Long> mCalls = new ArrayMap<>();
+        private int mThresholdMinutes;
+
+        private synchronized boolean isRepeat(Context context, Bundle extras) {
+            if (mThresholdMinutes <= 0) {
+                mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer
+                        .config_zen_repeat_callers_threshold);
+            }
+            if (mThresholdMinutes <= 0 || extras == null) return false;
+            final String peopleString = peopleString(extras);
+            if (peopleString == null) return false;
+            final long now = System.currentTimeMillis();
+            final int N = mCalls.size();
+            for (int i = N - 1; i >= 0; i--) {
+                final long time = mCalls.valueAt(i);
+                if (time > now || (now - time) > mThresholdMinutes * 1000 * 60) {
+                    mCalls.removeAt(i);
+                }
+            }
+            final boolean isRepeat = mCalls.containsKey(peopleString);
+            mCalls.put(peopleString, now);
+            return isRepeat;
+        }
+
+        private static String peopleString(Bundle extras) {
+            final String[] extraPeople = ValidateNotificationPeople.getExtraPeople(extras);
+            if (extraPeople == null || extraPeople.length == 0) return null;
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < extraPeople.length; i++) {
+                String extraPerson = extraPeople[i];
+                if (extraPerson == null) continue;
+                extraPerson = extraPerson.trim();
+                if (extraPerson.isEmpty()) continue;
+                if (sb.length() > 0) {
+                    sb.append('|');
+                }
+                sb.append(extraPerson);
+            }
+            return sb.length() == 0 ? null : sb.toString();
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 1775df2..e5925fe 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(mContext, 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("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
+        mFiltering.dump(pw, prefix);
+        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,repeatCallers=%s,events=%s,from=%s,messages=%s,reminders=%s)\n",
+                config.allowCalls, config.allowRepeatCallers, 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() {
@@ -270,7 +304,8 @@
         applyRestrictions(muteNotifications, USAGE_NOTIFICATION);
 
         // call restrictions
-        final boolean muteCalls = zen && !mConfig.allowCalls || mEffectsSuppressed;
+        final boolean muteCalls = zen && !mConfig.allowCalls && !mConfig.allowRepeatCallers
+                || mEffectsSuppressed;
         applyRestrictions(muteCalls, USAGE_NOTIFICATION_RINGTONE);
 
         // alarm restrictions
@@ -288,43 +323,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 +350,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 +362,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 +585,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 +619,5 @@
         void onConfigChanged() {}
         void onZenModeChanged() {}
     }
+
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 591dbee..89fa320 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -528,6 +529,15 @@
             params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
         }
 
+        // Only system components can circumvent runtime permissions when installing.
+        if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+                && mContext.checkCallingOrSelfPermission(Manifest.permission
+                .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+            throw new SecurityException("You need the "
+                    + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+                    + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+        }
+
         // Defensively resize giant app icons
         if (params.appIcon != null) {
             final ActivityManager am = (ActivityManager) mContext.getSystemService(
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dadc872..11e1ccf 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -632,7 +632,7 @@
                         + verificationId + " packageName:" + packageName);
                 return;
             }
-            Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId: "
+            Slog.d(TAG, "Updating IntentFilterVerificationInfo for verificationId:"
                     + verificationId);
 
             synchronized (mPackages) {
@@ -3955,10 +3955,9 @@
                     Collections.sort(result, mResolvePrioritySorter);
                 }
                 result = filterIfNotPrimaryUser(result, userId);
-                if (result.size() > 1) {
+                if (result.size() > 1 && hasWebURI(intent)) {
                     return filterCandidatesWithDomainPreferedActivitiesLPr(result);
                 }
-
                 return result;
             }
             final PackageParser.Package pkg = mPackages.get(pkgName);
@@ -3990,16 +3989,30 @@
         return resolveInfos;
     }
 
+    private static boolean hasWebURI(Intent intent) {
+        if (intent.getData() == null) {
+            return false;
+        }
+        final String scheme = intent.getScheme();
+        if (TextUtils.isEmpty(scheme)) {
+            return false;
+        }
+        return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS);
+    }
+
     private List<ResolveInfo> filterCandidatesWithDomainPreferedActivitiesLPr(
             List<ResolveInfo> candidates) {
         if (DEBUG_PREFERRED) {
             Slog.v("TAG", "Filtering results with prefered activities. Candidates count: " +
                     candidates.size());
         }
+
         final int userId = UserHandle.getCallingUserId();
         ArrayList<ResolveInfo> result = new ArrayList<ResolveInfo>();
+        ArrayList<ResolveInfo> undefinedList = new ArrayList<ResolveInfo>();
         ArrayList<ResolveInfo> neverList = new ArrayList<ResolveInfo>();
         ArrayList<ResolveInfo> matchAllList = new ArrayList<ResolveInfo>();
+
         synchronized (mPackages) {
             final int count = candidates.size();
             // First, try to use the domain prefered App
@@ -4010,11 +4023,12 @@
                 if (ps != null) {
                     // Try to get the status from User settings first
                     int status = getDomainVerificationStatusLPr(ps, userId);
-                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS ||
-                            status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+                    if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS) {
                         result.add(info);
                     } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
                         neverList.add(info);
+                    } else if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
+                        undefinedList.add(info);
                     }
                     // Add to the special match all list (Browser use case)
                     if (info.handleAllWebDataURI) {
@@ -4024,15 +4038,16 @@
             }
             // If there is nothing selected, add all candidates and remove the ones that the User
             // has explicitely put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state and
-            // also remove any .
-            // If there is still none after this pass, add all Browser Apps and let the User decide
-            // with the Disambiguation dialog if there are several ones.
+            // also remove any Browser Apps ones.
+            // If there is still none after this pass, add all undefined one and Browser Apps and
+            // let the User decide with the Disambiguation dialog if there are several ones.
             if (result.size() == 0) {
                 result.addAll(candidates);
             }
             result.removeAll(neverList);
             result.removeAll(matchAllList);
             if (result.size() == 0) {
+                result.addAll(undefinedList);
                 result.addAll(matchAllList);
             }
         }
@@ -8597,6 +8612,15 @@
             user = new UserHandle(userId);
         }
 
+        // Only system components can circumvent runtime permissions when installing.
+        if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
+                && mContext.checkCallingOrSelfPermission(Manifest.permission
+                .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
+            throw new SecurityException("You need the "
+                    + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+                    + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
+        }
+
         verificationParams.setInstallerUid(callingUid);
 
         final File originFile = new File(originPath);
@@ -8754,7 +8778,6 @@
         long callingId = Binder.clearCallingIdentity();
         try {
             boolean sendAdded = false;
-            Bundle extras = new Bundle(1);
 
             // writer
             synchronized (mPackages) {
@@ -11284,6 +11307,12 @@
             return;
         }
 
+        final boolean hasDomainURLs = hasDomainURLs(pkg);
+        if (!hasDomainURLs) {
+            Slog.d(TAG, "No domain URLs, so no need to verify any IntentFilter!");
+            return;
+        }
+
         Slog.d(TAG, "Checking for userId:" + userId + " if any IntentFilter from the " + size
                 + " Activities needs verification ...");
 
@@ -11291,21 +11320,25 @@
         int count = 0;
         final String packageName = pkg.packageName;
         ArrayList<String> allHosts = new ArrayList<>();
+
         synchronized (mPackages) {
             for (PackageParser.Activity a : pkg.activities) {
                 for (ActivityIntentInfo filter : a.intents) {
-                    boolean needFilterVerification = filter.needsVerification() &&
-                            !filter.isVerified();
-                    if (needFilterVerification && needNetworkVerificationLPr(filter)) {
+                    boolean needsFilterVerification = filter.needsVerification();
+                    if (needsFilterVerification && needsNetworkVerificationLPr(filter)) {
                         Slog.d(TAG, "Verification needed for IntentFilter:" + filter.toString());
                         mIntentFilterVerifier.addOneIntentFilterVerification(
                                 verifierUid, userId, verificationId, filter, packageName);
                         count++;
-                    } else {
-                        Slog.d(TAG, "No verification needed for IntentFilter:" + filter.toString());
+                    } else if (!needsFilterVerification) {
+                        Slog.d(TAG, "No verification needed for IntentFilter:"
+                                + filter.toString());
                         if (hasValidDomains(filter)) {
                             allHosts.addAll(filter.getHostsList());
                         }
+                    } else {
+                        Slog.d(TAG, "Verification already done for IntentFilter:"
+                                + filter.toString());
                     }
                 }
             }
@@ -11317,15 +11350,14 @@
                     + (count > 1 ? "s" : "") +  " for userId:" + userId + "!");
         } else {
             Slog.d(TAG, "No need to start any IntentFilter verification!");
-            if (allHosts.size() > 0 && hasDomainURLs(pkg) &&
-                    mSettings.createIntentFilterVerificationIfNeededLPw(
-                            packageName, allHosts) != null) {
+            if (allHosts.size() > 0 && mSettings.createIntentFilterVerificationIfNeededLPw(
+                    packageName, allHosts) != null) {
                 scheduleWriteSettingsLocked();
             }
         }
     }
 
-    private boolean needNetworkVerificationLPr(ActivityIntentInfo filter) {
+    private boolean needsNetworkVerificationLPr(ActivityIntentInfo filter) {
         final ComponentName cn  = filter.activity.getComponentName();
         final String packageName = cn.getPackageName();
 
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 1ff6fb8..c75a1d3 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -34,6 +34,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -52,14 +53,15 @@
  */
 public final class SELinuxMMAC {
 
-    private static final String TAG = "SELinuxMMAC";
+    static final String TAG = "SELinuxMMAC";
 
     private static final boolean DEBUG_POLICY = false;
     private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+    private static final boolean DEBUG_POLICY_ORDER = DEBUG_POLICY || false;
 
     // All policy stanzas read from mac_permissions.xml. This is also the lock
     // to synchronize access during policy load and access attempts.
-    private static final List<Policy> sPolicies = new ArrayList<Policy>();
+    private static List<Policy> sPolicies = new ArrayList<>();
 
     // Data policy override version file.
     private static final String DATA_VERSION_FILE =
@@ -112,17 +114,9 @@
      *         were loaded successfully; no partial loading is possible.
      */
     public static boolean readInstallPolicy() {
-        // Temp structure to hold the rules while we parse the xml file. We add
-        // all the rules once we know there's no problems.
+        // Temp structure to hold the rules while we parse the xml file
         List<Policy> policies = new ArrayList<>();
 
-        // A separate structure to hold the default stanza. We need to add this to
-        // the end of the policies list structure.
-        Policy defaultPolicy = null;
-
-        // Track sets of known policy certs so we can enforce rules across stanzas.
-        Set<Set<Signature>> knownCerts = new HashSet<>();
-
         FileReader policyFile = null;
         XmlPullParser parser = Xml.newPullParser();
         try {
@@ -138,31 +132,15 @@
                     continue;
                 }
 
-                String tagName = parser.getName();
-                if ("signer".equals(tagName)) {
-                    Policy signerPolicy = readSignerOrThrow(parser);
-                    // Return of a Policy instance ensures certain invariants have
-                    // passed, however, we still want to do some cross policy checking.
-                    // Thus, check that we haven't seen the certs in another stanza.
-                    Set<Signature> certs = signerPolicy.getSignatures();
-                    if (knownCerts.contains(certs)) {
-                        String msg = "Separate stanzas have identical certs";
-                        throw new IllegalStateException(msg);
-                    }
-                    knownCerts.add(certs);
-                    policies.add(signerPolicy);
-                } else if ("default".equals(tagName)) {
-                    Policy defPolicy = readDefaultOrThrow(parser);
-                    // Return of a Policy instance ensures certain invariants have
-                    // passed, however, we still want to do some cross policy checking.
-                    // Thus, check that we haven't already seen a default stanza.
-                    if (defaultPolicy != null) {
-                        String msg = "Multiple default stanzas identified";
-                        throw new IllegalStateException(msg);
-                    }
-                    defaultPolicy = defPolicy;
-                } else {
-                    skip(parser);
+                switch (parser.getName()) {
+                    case "signer":
+                        policies.add(readSignerOrThrow(parser));
+                        break;
+                    case "default":
+                        policies.add(readDefaultOrThrow(parser));
+                        break;
+                    default:
+                        skip(parser);
                 }
             }
         } catch (IllegalStateException | IllegalArgumentException |
@@ -182,15 +160,22 @@
             IoUtils.closeQuietly(policyFile);
         }
 
-        // Add the default policy to the end if there is one. This will ensure that
-        // the default stanza is consulted last when performing policy lookups.
-        if (defaultPolicy != null) {
-            policies.add(defaultPolicy);
+        // Now sort the policy stanzas
+        PolicyComparator policySort = new PolicyComparator();
+        Collections.sort(policies, policySort);
+        if (policySort.foundDuplicate()) {
+            Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
+            return false;
         }
 
         synchronized (sPolicies) {
-            sPolicies.clear();
-            sPolicies.addAll(policies);
+            sPolicies = policies;
+
+            if (DEBUG_POLICY_ORDER) {
+                for (Policy policy : sPolicies) {
+                    Slog.d(TAG, "Policy: " + policy.toString());
+                }
+            }
         }
 
         return true;
@@ -494,9 +479,23 @@
  * of invariants before being built and returned. Each instance can be guaranteed to
  * hold one valid policy stanza as outlined in the external/sepolicy/mac_permissions.xml
  * file.
- * </p>
+ * <p>
  * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
- * signer based Policy instance.
+ * signer based Policy instance with only inner package name refinements.
+ * </p>
+ * <pre>
+ * {@code
+ * Policy policy = new Policy.PolicyBuilder()
+ *         .addSignature("308204a8...")
+ *         .addSignature("483538c8...")
+ *         .addInnerPackageMapOrThrow("com.foo.", "bar")
+ *         .addInnerPackageMapOrThrow("com.foo.other", "bar")
+ *         .build();
+ * }
+ * </pre>
+ * <p>
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * signer based Policy instance with only a global seinfo tag.
  * </p>
  * <pre>
  * {@code
@@ -504,20 +503,18 @@
  *         .addSignature("308204a8...")
  *         .addSignature("483538c8...")
  *         .setGlobalSeinfoOrThrow("paltform")
- *         .addInnerPackageMapOrThrow("com.foo.", "bar")
- *         .addInnerPackageMapOrThrow("com.foo.other", "bar")
  *         .build();
  * }
  * </pre>
  * <p>
- * An example of how to use {@link Policy.PolicyBuilder} to create a default based Policy
- * instance.
+ * The following is an example of how to use {@link Policy.PolicyBuilder} to create a
+ * default based Policy instance.
  * </p>
  * <pre>
  * {@code
  * Policy policy = new Policy.PolicyBuilder()
  *         .setAsDefaultPolicy()
- *         .setGlobalSeinfoOrThrow("defualt")
+ *         .setGlobalSeinfoOrThrow("default")
  *         .build();
  * }
  * </pre>
@@ -548,6 +545,65 @@
     }
 
     /**
+     * Return whether this policy object represents a default stanza.
+     *
+     * @return A boolean indicating if this object represents a default policy stanza.
+     */
+    public boolean isDefaultStanza() {
+        return mDefaultStanza;
+    }
+
+    /**
+     * Return whether this policy object contains package name mapping refinements.
+     *
+     * @return A boolean indicating if this object has inner package name mappings.
+     */
+    public boolean hasInnerPackages() {
+        return !mPkgMap.isEmpty();
+    }
+
+    /**
+     * Return the mapping of all package name refinements.
+     *
+     * @return A Map object whose keys are the package names and whose values are
+     *         the seinfo assignments.
+     */
+    public Map<String, String> getInnerPackages() {
+        return mPkgMap;
+    }
+
+    /**
+     * Return whether the policy object has a global seinfo tag attached.
+     *
+     * @return A boolean indicating if this stanza has a global seinfo tag.
+     */
+    public boolean hasGlobalSeinfo() {
+        return mSeinfo != null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (mDefaultStanza) {
+            sb.append("defaultStanza=true ");
+        }
+
+        for (Signature cert : mCerts) {
+            sb.append("cert=" + cert.toCharsString().substring(0, 11) + "... ");
+        }
+
+        if (mSeinfo != null) {
+            sb.append("seinfo=" + mSeinfo);
+        }
+
+        for (String name : mPkgMap.keySet()) {
+            sb.append(" " + name + "=" + mPkgMap.get(name));
+        }
+
+        return sb.toString();
+    }
+
+    /**
      * <p>
      * Determine the seinfo value to assign to an apk. The appropriate seinfo value
      * is determined using the following steps:
@@ -620,7 +676,7 @@
         }
 
         /**
-         * Sets this stanza as a defualt stanza. All policy stanzas are assumed to
+         * Sets this stanza as a default stanza. All policy stanzas are assumed to
          * be signer stanzas unless this method is explicitly called. Default stanzas
          * are treated differently with respect to allowable child tags, ordering and
          * when and how policy decisions are enforced.
@@ -754,7 +810,7 @@
          *        <ul>
          *           <li> at least one cert must be found </li>
          *           <li> either a global seinfo value is present OR at least one
-         *           inner package mapping must be present. </li>
+         *           inner package mapping must be present BUT not both. </li>
          *        </ul>
          *      </li>
          *    </ul>
@@ -783,9 +839,9 @@
                     String err = "Missing certs with signer tag. Expecting at least one.";
                     throw new IllegalStateException(err);
                 }
-                if ((p.mSeinfo == null) && (p.mPkgMap.isEmpty())) {
-                    String err = "Missing seinfo OR package tags with signer tag. At " +
-                            "least one must be present.";
+                if (!(p.mSeinfo == null ^ p.mPkgMap.isEmpty())) {
+                    String err = "Only seinfo tag XOR package tags are allowed within " +
+                            "a signer stanza.";
                     throw new IllegalStateException(err);
                 }
             }
@@ -794,3 +850,58 @@
         }
     }
 }
+
+/**
+ * Comparision imposing an ordering on Policy objects. It is understood that Policy
+ * objects can only take one of three forms and ordered according to the following
+ * set of rules most specific to least.
+ * <ul>
+ *   <li> signer stanzas with inner package mappings </li>
+ *   <li> signer stanzas with global seinfo tags </li>
+ *   <li> default stanza </li>
+ * </ul>
+ * This comparison also checks for duplicate entries on the input selectors. Any
+ * found duplicates will be flagged and can be checked with {@link #foundDuplicate}.
+ */
+
+final class PolicyComparator implements Comparator<Policy> {
+
+    private boolean duplicateFound = false;
+
+    public boolean foundDuplicate() {
+        return duplicateFound;
+    }
+
+    @Override
+    public int compare(Policy p1, Policy p2) {
+
+        // Give precedence to signature stanzas over default stanzas
+        if (p1.isDefaultStanza() != p2.isDefaultStanza()) {
+            return p1.isDefaultStanza() ? 1 : -1;
+        }
+
+        // Give precedence to stanzas with inner package mappings
+        if (p1.hasInnerPackages() != p2.hasInnerPackages()) {
+            return p1.hasInnerPackages() ? -1 : 1;
+        }
+
+        // Check for duplicate entries
+        if (p1.getSignatures().equals(p2.getSignatures())) {
+            // Checks if default stanza or a signer w/o inner package names
+            if (p1.hasGlobalSeinfo()) {
+                duplicateFound = true;
+                Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+            }
+
+            // Look for common inner package name mappings
+            final Map<String, String> p1Packages = p1.getInnerPackages();
+            final Map<String, String> p2Packages = p2.getInnerPackages();
+            if (!Collections.disjoint(p1Packages.keySet(), p2Packages.keySet())) {
+                duplicateFound = true;
+                Slog.e(SELinuxMMAC.TAG, "Duplicate policy entry: " + p1.toString());
+            }
+        }
+
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ed14569..936840a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -990,7 +990,7 @@
                     launchHomeFromHotKey();
                     break;
                 case SHORT_PRESS_POWER_GO_HOME:
-                    launchHomeFromHotKey();
+                    launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
                     break;
             }
         }
@@ -1068,7 +1068,7 @@
                         PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
                 break;
             case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
-                launchHomeFromHotKey(false /* awakenDreams */);
+                launchHomeFromHotKey(false /* awakenDreams */, true /*respectKeyguard*/);
                 mPowerManager.goToSleep(event.getEventTime(),
                         PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
                 break;
@@ -3059,50 +3059,56 @@
     }
 
     void launchHomeFromHotKey() {
-        launchHomeFromHotKey(true /* awakenFromDreams */);
+        launchHomeFromHotKey(true /* awakenFromDreams */, true /*respectKeyguard*/);
     }
 
     /**
      * A home key -> launch home action was detected.  Take the appropriate action
      * given the situation with the keyguard.
      */
-    void launchHomeFromHotKey(final boolean awakenFromDreams) {
-        if (isKeyguardShowingAndNotOccluded()) {
-            // don't launch home if keyguard showing
-        } else if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
-            // when in keyguard restricted mode, must first verify unlock
-            // before launching home
-            mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
-                @Override
-                public void onKeyguardExitResult(boolean success) {
-                    if (success) {
-                        try {
-                            ActivityManagerNative.getDefault().stopAppSwitches();
-                        } catch (RemoteException e) {
+    void launchHomeFromHotKey(final boolean awakenFromDreams, final boolean respectKeyguard) {
+        if (respectKeyguard) {
+            if (isKeyguardShowingAndNotOccluded()) {
+                // don't launch home if keyguard showing
+                return;
+            }
+
+            if (!mHideLockScreen && mKeyguardDelegate.isInputRestricted()) {
+                // when in keyguard restricted mode, must first verify unlock
+                // before launching home
+                mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
+                    @Override
+                    public void onKeyguardExitResult(boolean success) {
+                        if (success) {
+                            try {
+                                ActivityManagerNative.getDefault().stopAppSwitches();
+                            } catch (RemoteException e) {
+                            }
+                            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+                            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                         }
-                        sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
-                        startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
                     }
-                }
-            });
+                });
+                return;
+            }
+        }
+
+        // no keyguard stuff to worry about, just launch home!
+        try {
+            ActivityManagerNative.getDefault().stopAppSwitches();
+        } catch (RemoteException e) {
+        }
+        if (mRecentsVisible) {
+            // Hide Recents and notify it to launch Home
+            if (awakenFromDreams) {
+                awakenDreams();
+            }
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+            hideRecentApps(false, true);
         } else {
-            // no keyguard stuff to worry about, just launch home!
-            try {
-                ActivityManagerNative.getDefault().stopAppSwitches();
-            } catch (RemoteException e) {
-            }
-            if (mRecentsVisible) {
-                // Hide Recents and notify it to launch Home
-                if (awakenFromDreams) {
-                    awakenDreams();
-                }
-                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
-                hideRecentApps(false, true);
-            } else {
-                // Otherwise, just launch Home
-                sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
-                startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
-            }
+            // Otherwise, just launch Home
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_HOME_KEY);
+            startDockOrHome(true /*fromHomeKey*/, awakenFromDreams);
         }
     }
 
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 2728af1..8f0c6c8 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -469,10 +469,12 @@
         if (enabled && mPeripheralMidiDevice == null) {
             Bundle properties = new Bundle();
             Resources r = mContext.getResources();
+            properties.putString(MidiDeviceInfo.PROPERTY_NAME, r.getString(
+                    com.android.internal.R.string.usb_midi_peripheral_name));
             properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, r.getString(
                     com.android.internal.R.string.usb_midi_peripheral_manufacturer_name));
             properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, r.getString(
-                    com.android.internal.R.string.usb_midi_peripheral_model_name));
+                    com.android.internal.R.string.usb_midi_peripheral_product_name));
             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
             mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device);
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 70ac268..24bdb7a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -157,6 +157,34 @@
             in PendingIntent deliveryIntent);
 
     /**
+     * Send a data SMS. Only for use internally.
+     *
+     * @param smsc the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param data the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applicaitons,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param subId the subId id.
+     */
+    void sendDataForSubscriberWithSelfPermissions(int subId, String callingPkg, in String destAddr,
+            in String scAddr, in int destPort, in byte[] data, in PendingIntent sentIntent,
+            in PendingIntent deliveryIntent);
+
+    /**
      * Send an SMS.
      *
      * @param smsc the SMSC to send the message through, or NULL for the
@@ -211,6 +239,34 @@
             in PendingIntent deliveryIntent);
 
     /**
+     * Send an SMS. Internal use only.
+     *
+     * @param smsc the SMSC to send the message through, or NULL for the
+     *  default SMSC
+     * @param text the body of the message to send
+     * @param sentIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is sucessfully sent, or failed.
+     *  The result code will be <code>Activity.RESULT_OK<code> for success,
+     *  or one of these errors:<br>
+     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
+     *  <code>RESULT_ERROR_NULL_PDU</code><br>
+     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+     *  the extra "errorCode" containing a radio technology specific value,
+     *  generally only useful for troubleshooting.<br>
+     *  The per-application based SMS control checks sentIntent. If sentIntent
+     *  is NULL the caller will be checked against all unknown applications,
+     *  which cause smaller number of SMS to be sent in checking period.
+     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+     *  broadcast when the message is delivered to the recipient.  The
+     *  raw pdu of the status report is in the extended data ("pdu").
+     * @param subId the subId on which the SMS has to be sent.
+     */
+    void sendTextForSubscriberWithSelfPermissions(in int subId, String callingPkg,
+            in String destAddr, in String scAddr, in String text, in PendingIntent sentIntent,
+            in PendingIntent deliveryIntent);
+
+    /**
      * Inject an SMS PDU into the android platform.
      *
      * @param pdu is the byte array of pdu to be injected into android application framework
diff --git a/test-runner/src/android/test/mock/MockCursor.java b/test-runner/src/android/test/mock/MockCursor.java
index a37c6eb..28fa0f8 100644
--- a/test-runner/src/android/test/mock/MockCursor.java
+++ b/test-runner/src/android/test/mock/MockCursor.java
@@ -35,162 +35,209 @@
  * </P>
  */
 public class MockCursor implements Cursor {
+    @Override
     public int getColumnCount() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getColumnIndex(String columnName) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getColumnIndexOrThrow(String columnName) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public String getColumnName(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public String[] getColumnNames() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getCount() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isNull(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getInt(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public long getLong(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public short getShort(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public float getFloat(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public double getDouble(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public byte[] getBlob(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public String getString(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
+    public void setExtras(Bundle extras) {
+        throw new UnsupportedOperationException("unimplemented mock method");
+    }
+
+    @Override
     public Bundle getExtras() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getPosition() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isAfterLast() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isBeforeFirst() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isFirst() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isLast() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean move(int offset) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean moveToFirst() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean moveToLast() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean moveToNext() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean moveToPrevious() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean moveToPosition(int position) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
+    @Deprecated
     public void deactivate() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void close() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean isClosed() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
+    @Deprecated
     public boolean requery() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void registerContentObserver(ContentObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void registerDataSetObserver(DataSetObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public Bundle respond(Bundle extras) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public boolean getWantsAllOnMoveCalls() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void setNotificationUri(ContentResolver cr, Uri uri) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public Uri getNotificationUri() {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void unregisterContentObserver(ContentObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public void unregisterDataSetObserver(DataSetObserver observer) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
 
+    @Override
     public int getType(int columnIndex) {
         throw new UnsupportedOperationException("unimplemented mock method");
     }
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 9cea176..14f558e 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -29,12 +29,14 @@
 	BinaryResourceParser.cpp \
 	ConfigDescription.cpp \
 	Files.cpp \
+	Flag.cpp \
 	JavaClassGenerator.cpp \
 	Linker.cpp \
 	Locale.cpp \
 	Logger.cpp \
 	ManifestParser.cpp \
 	ManifestValidator.cpp \
+	Png.cpp \
 	ResChunkPullParser.cpp \
 	Resolver.cpp \
 	Resource.cpp \
@@ -69,7 +71,10 @@
 	XliffXmlPullParser_test.cpp \
 	XmlFlattener_test.cpp
 
-cIncludes :=
+cIncludes := \
+	external/libpng \
+	external/libz
+
 hostLdLibs :=
 
 hostStaticLibs := \
@@ -78,7 +83,8 @@
 	liblog \
 	libcutils \
 	libexpat \
-	libziparchive-host
+	libziparchive-host \
+	libpng
 
 ifneq ($(strip $(USE_MINGW)),)
 	hostStaticLibs += libz
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
new file mode 100644
index 0000000..b1ee8e7
--- /dev/null
+++ b/tools/aapt2/Flag.cpp
@@ -0,0 +1,109 @@
+#include "Flag.h"
+#include "StringPiece.h"
+
+#include <functional>
+#include <iomanip>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+struct Flag {
+    std::string name;
+    std::string description;
+    std::function<void(const StringPiece&)> action;
+    bool required;
+    bool* flagResult;
+    bool parsed;
+};
+
+static std::vector<Flag> sFlags;
+static std::vector<std::string> sArgs;
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), action, false, nullptr, false });
+}
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), action, true, nullptr, false });
+}
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result) {
+    sFlags.push_back(
+            Flag{ name.toString(), description.toString(), {}, false, result, false });
+}
+
+static void usageAndDie(const StringPiece& command) {
+    std::cerr << command << " [options]";
+    for (const Flag& flag : sFlags) {
+        if (flag.required) {
+            std::cerr << " " << flag.name << " arg";
+        }
+    }
+    std::cerr << " files..." << std::endl << std::endl << "Options:" << std::endl;
+
+    for (const Flag& flag : sFlags) {
+        std::string command = flag.name;
+        if (!flag.flagResult) {
+            command += " arg ";
+        }
+        std::cerr << "  " << std::setw(30) << std::left << command
+                  << flag.description << std::endl;
+    }
+    exit(1);
+}
+
+void parse(int argc, char** argv, const StringPiece& command) {
+    for (int i = 0; i < argc; i++) {
+        const StringPiece arg(argv[i]);
+        if (*arg.data() != '-') {
+            sArgs.emplace_back(arg.toString());
+            continue;
+        }
+
+        bool match = false;
+        for (Flag& flag : sFlags) {
+            if (arg == flag.name) {
+                match = true;
+                flag.parsed = true;
+                if (flag.flagResult) {
+                    *flag.flagResult = true;
+                } else {
+                    i++;
+                    if (i >= argc) {
+                        std::cerr << flag.name << " missing argument." << std::endl
+                                  << std::endl;
+                        usageAndDie(command);
+                    }
+                    flag.action(argv[i]);
+                }
+                break;
+            }
+        }
+
+        if (!match) {
+            std::cerr << "unknown option '" << arg << "'." << std::endl << std::endl;
+            usageAndDie(command);
+        }
+    }
+
+    for (const Flag& flag : sFlags) {
+        if (flag.required && !flag.parsed) {
+            std::cerr << "missing required flag " << flag.name << std::endl << std::endl;
+            usageAndDie(command);
+        }
+    }
+}
+
+const std::vector<std::string>& getArgs() {
+    return sArgs;
+}
+
+} // namespace flag
+} // namespace aapt
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
new file mode 100644
index 0000000..32f5f2c
--- /dev/null
+++ b/tools/aapt2/Flag.h
@@ -0,0 +1,28 @@
+#ifndef AAPT_FLAG_H
+#define AAPT_FLAG_H
+
+#include "StringPiece.h"
+
+#include <functional>
+#include <string>
+#include <vector>
+
+namespace aapt {
+namespace flag {
+
+void requiredFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action);
+
+void optionalFlag(const StringPiece& name, const StringPiece& description,
+                  std::function<void(const StringPiece&)> action);
+
+void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+
+void parse(int argc, char** argv, const StringPiece& command);
+
+const std::vector<std::string>& getArgs();
+
+} // namespace flag
+} // namespace aapt
+
+#endif // AAPT_FLAG_H
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index cfc5874..3a4b444c 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -18,10 +18,12 @@
 #include "BigBuffer.h"
 #include "BinaryResourceParser.h"
 #include "Files.h"
+#include "Flag.h"
 #include "JavaClassGenerator.h"
 #include "Linker.h"
 #include "ManifestParser.h"
 #include "ManifestValidator.h"
+#include "Png.h"
 #include "ResourceParser.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
@@ -41,6 +43,7 @@
 #include <iostream>
 #include <sstream>
 #include <sys/stat.h>
+#include <utils/Errors.h>
 
 using namespace aapt;
 
@@ -107,12 +110,12 @@
  * Collect files from 'root', filtering out any files that do not
  * match the FileFilter 'filter'.
  */
-bool walkTree(const StringPiece& root, const FileFilter& filter,
-        std::vector<Source>& outEntries) {
+bool walkTree(const Source& root, const FileFilter& filter,
+              std::vector<Source>* outEntries) {
     bool error = false;
 
-    for (const std::string& dirName : listFiles(root)) {
-        std::string dir(root.toString());
+    for (const std::string& dirName : listFiles(root.path)) {
+        std::string dir = root.path;
         appendPath(&dir, dirName);
 
         FileType ft = getFileType(dir);
@@ -134,13 +137,11 @@
             }
 
             if (ft != FileType::kRegular) {
-                Logger::error(Source{ file })
-                    << "not a regular file."
-                    << std::endl;
+                Logger::error(Source{ file }) << "not a regular file." << std::endl;
                 error = true;
                 continue;
             }
-            outEntries.emplace_back(Source{ file });
+            outEntries->push_back(Source{ file });
         }
     }
     return !error;
@@ -171,9 +172,6 @@
 }
 
 bool loadResTable(android::ResTable* table, const Source& source) {
-    // For NO_ERROR (which on Windows is a MACRO).
-    using namespace android;
-
     std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
     if (!ifs) {
         Logger::error(source) << strerror(errno) << std::endl;
@@ -190,7 +188,7 @@
     char* buf = new char[dataSize];
     ifs.read(buf, dataSize);
 
-    bool result = table->add(buf, dataSize, -1, true) == NO_ERROR;
+    bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
 
     delete [] buf;
     return result;
@@ -323,13 +321,6 @@
         }
     }
 
-    std::unique_ptr<FileReference> fileResource = makeFileReference(
-            table->getValueStringPool(),
-            util::utf16ToUtf8(name.entry) + ".xml",
-            name.type,
-            config);
-    table->addResource(name, config, source.line(0), std::move(fileResource));
-
     for (size_t level : sdkLevels) {
         Logger::note(source)
                 << "creating v" << level << " versioned file."
@@ -347,14 +338,15 @@
     return true;
 }
 
-struct CompileXml {
+struct CompileItem {
     Source source;
     ResourceName name;
     ConfigDescription config;
+    std::string extension;
 };
 
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
-                const Source& outputSource, std::queue<CompileXml>* queue) {
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
+                const Source& outputSource, std::queue<CompileItem>* queue) {
     std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
         Logger::error(item.source) << strerror(errno) << std::endl;
@@ -376,7 +368,7 @@
     if (minStrippedSdk.value() > 0) {
         // Something was stripped, so let's generate a new file
         // with the version of the smallest SDK version stripped.
-        CompileXml newWork = item;
+        CompileItem newWork = item;
         newWork.config.sdkVersion = minStrippedSdk.value();
         queue->push(newWork);
     }
@@ -394,9 +386,51 @@
     return true;
 }
 
+bool compilePng(const Source& source, const Source& output) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::ofstream out(output.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::string err;
+    Png png;
+    if (!png.process(source, in, out, {}, &err)) {
+        Logger::error(source) << err << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool copyFile(const Source& source, const Source& output) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::ofstream out(output.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (out << in.rdbuf()) {
+        Logger::error(output) << strerror(errno) << std::endl;
+        return true;
+    }
+    return false;
+}
+
 struct AaptOptions {
     enum class Phase {
-        LegacyFull,
+        Full,
         Collect,
         Link,
         Compile,
@@ -411,16 +445,26 @@
     // The location of the manifest file.
     Source manifest;
 
-    // The files to process.
-    std::vector<Source> sources;
+    // The source directories to walk and find resource files.
+    std::vector<Source> sourceDirs;
+
+    // The resource files to process and collect.
+    std::vector<Source> collectFiles;
+
+    // The binary table files to link.
+    std::vector<Source> linkFiles;
+
+    // The resource files to compile.
+    std::vector<Source> compileFiles;
 
     // The libraries these files may reference.
     std::vector<Source> libraries;
 
-    // Output directory.
+    // Output path. This can be a directory or file
+    // depending on the phase.
     Source output;
 
-    // Whether to generate a Java Class.
+    // Directory to in which to generate R.java.
     Maybe<Source> generateJavaClass;
 
     // Whether to output verbose details about
@@ -428,9 +472,8 @@
     bool verbose = false;
 };
 
-bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
-    using namespace android;
-
+bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
+                            const AaptOptions& options) {
     Source outSource = options.output;
     appendPath(&outSource.path, "AndroidManifest.xml");
 
@@ -461,8 +504,8 @@
         p += b.size;
     }
 
-    ResXMLTree tree;
-    if (tree.setTo(data.get(), outBuffer.size()) != NO_ERROR) {
+    android::ResXMLTree tree;
+    if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
         return false;
     }
 
@@ -496,246 +539,117 @@
     return parser.parse(source, pullParser, outInfo);
 }
 
-/**
- * Parses legacy options and walks the source directories collecting
- * files to process.
- */
-bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions &options) {
-    options.phase = AaptOptions::Phase::LegacyFull;
-
-    std::vector<StringPiece> sourceDirs;
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "-S") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-S missing argument." << std::endl;
-                return false;
-            }
-            sourceDirs.push_back(*argsIter);
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "-M") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-M missing argument." << std::endl;
-                return false;
-            }
-
-            if (!options.manifest.path.empty()) {
-                Logger::error() << "multiple -M flags are not allowed." << std::endl;
-                return false;
-            }
-            options.manifest.path = argsIter->toString();
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-J") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-J missing argument." << std::endl;
-                return false;
-            }
-            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else {
-            Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
-            return false;
-        }
-
-        ++argsIter;
+static AaptOptions prepareArgs(int argc, char** argv) {
+    if (argc < 2) {
+        std::cerr << "no command specified." << std::endl;
+        exit(1);
     }
 
-    if (options.manifest.path.empty()) {
-        Logger::error() << "must specify manifest file with -M." << std::endl;
-        return false;
-    }
+    const StringPiece command(argv[1]);
+    argc -= 2;
+    argv += 2;
 
-    // Load the App's package name, etc.
-    if (!loadAppInfo(options.manifest, &options.appInfo)) {
-        return false;
-    }
+    AaptOptions options;
 
-    /**
-     * Set up the file filter to ignore certain files.
-     */
-    const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
-    FileFilter fileFilter;
-    if (customIgnore && customIgnore[0]) {
-        fileFilter.setPattern(customIgnore);
+    StringPiece outputDescription = "place output in file";
+    if (command == "package") {
+        options.phase = AaptOptions::Phase::Full;
+        outputDescription = "place output in directory";
+    } else if (command == "collect") {
+        options.phase = AaptOptions::Phase::Collect;
+    } else if (command == "link") {
+        options.phase = AaptOptions::Phase::Link;
+    } else if (command == "compile") {
+        options.phase = AaptOptions::Phase::Compile;
+        outputDescription = "place output in directory";
     } else {
-        fileFilter.setPattern(
-                "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+        std::cerr << "invalid command '" << command << "'." << std::endl;
+        exit(1);
     }
 
-    /*
-     * Enumerate the files in each source directory.
-     */
-    for (const StringPiece& source : sourceDirs) {
-        if (!walkTree(source, fileFilter, options.sources)) {
-            return false;
+    if (options.phase == AaptOptions::Phase::Full) {
+        flag::requiredFlag("-S", "add a directory in which to find resources",
+                [&options](const StringPiece& arg) {
+                    options.sourceDirs.push_back(Source{ arg.toString() });
+                });
+
+        flag::requiredFlag("-M", "path to AndroidManifest.xml",
+                [&options](const StringPiece& arg) {
+                    options.manifest = Source{ arg.toString() };
+                });
+
+        flag::optionalFlag("-I", "add an Android APK to link against",
+                [&options](const StringPiece& arg) {
+                    options.libraries.push_back(Source{ arg.toString() });
+                });
+
+        flag::optionalFlag("--java", "directory in which to generate R.java",
+                [&options](const StringPiece& arg) {
+                    options.generateJavaClass = Source{ arg.toString() };
+                });
+
+    } else {
+        flag::requiredFlag("--package", "Android package name",
+                [&options](const StringPiece& arg) {
+                    options.appInfo.package = util::utf8ToUtf16(arg);
+                });
+
+        if (options.phase != AaptOptions::Phase::Collect) {
+            flag::optionalFlag("-I", "add an Android APK to link against",
+                    [&options](const StringPiece& arg) {
+                        options.libraries.push_back(Source{ arg.toString() });
+                    });
+        }
+
+        if (options.phase == AaptOptions::Phase::Link) {
+            flag::optionalFlag("--java", "directory in which to generate R.java",
+                    [&options](const StringPiece& arg) {
+                        options.generateJavaClass = Source{ arg.toString() };
+                    });
         }
     }
-    return true;
-}
 
-bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Collect;
+    // Common flags for all steps.
+    flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+        options.output = Source{ arg.toString() };
+    });
+    flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
 
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
+    // Build the command string for output (eg. "aapt2 compile").
+    std::string fullCommand = "aapt2";
+    fullCommand += " ";
+    fullCommand += command.toString();
+
+    // Actually read the command line flags.
+    flag::parse(argc, argv, fullCommand);
+
+    // Copy all the remaining arguments.
+    if (options.phase == AaptOptions::Phase::Collect) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.collectFiles.push_back(Source{ arg });
         }
-        ++argsIter;
-    }
-    return true;
-}
-
-bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Link;
-
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "--java") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--java missing argument." << std::endl;
-                return false;
-            }
-            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
+    } else if (options.phase == AaptOptions::Phase::Compile) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.compileFiles.push_back(Source{ arg });
         }
-        ++argsIter;
-    }
-    return true;
-}
-
-bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
-        const std::vector<StringPiece>::const_iterator argsEndIter,
-        AaptOptions& options) {
-    options.phase = AaptOptions::Phase::Compile;
-
-    while (argsIter != argsEndIter) {
-        if (*argsIter == "--package") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "--package missing argument." << std::endl;
-                return false;
-            }
-            options.appInfo.package = util::utf8ToUtf16(*argsIter);
-        } else if (*argsIter == "-o") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-o missing argument." << std::endl;
-                return false;
-            }
-            options.output = Source{ argsIter->toString() };
-        } else if (*argsIter == "-I") {
-            ++argsIter;
-            if (argsIter == argsEndIter) {
-                Logger::error() << "-I missing argument." << std::endl;
-                return false;
-            }
-            options.libraries.push_back(Source{ argsIter->toString() });
-        } else if (*argsIter == "-v") {
-            options.verbose = true;
-        } else if (argsIter->data()[0] != '-') {
-            options.sources.push_back(Source{ argsIter->toString() });
-        } else {
-            Logger::error()
-                    << "unknown option '"
-                    << *argsIter
-                    << "'."
-                    << std::endl;
-            return false;
+    } else if (options.phase == AaptOptions::Phase::Link) {
+        for (const std::string& arg : flag::getArgs()) {
+            options.linkFiles.push_back(Source{ arg });
         }
-        ++argsIter;
     }
-    return true;
+    return options;
 }
 
-struct CollectValuesItem {
-    Source source;
-    ConfigDescription config;
-};
-
-bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
-    std::ifstream in(item.source.path, std::ifstream::binary);
+static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+                          const ConfigDescription& config) {
+    std::ifstream in(source.path, std::ifstream::binary);
     if (!in) {
-        Logger::error(item.source) << strerror(errno) << std::endl;
+        Logger::error(source) << strerror(errno) << std::endl;
         return false;
     }
 
     std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    ResourceParser parser(table, item.source, item.config, xmlParser);
+    ResourceParser parser(table, source, config, xmlParser);
     return parser.parse();
 }
 
@@ -750,7 +664,7 @@
  * Resource file paths are expected to look like:
  * [--/res/]type[-config]/name
  */
-Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
     std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
     if (parts.size() < 2) {
         Logger::error(source) << "bad resource path." << std::endl;
@@ -792,326 +706,48 @@
     };
 }
 
-static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                     const AaptOptions& options) {
+bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
+           const std::shared_ptr<Resolver>& resolver) {
+    const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Compile);
+    const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
+            options->phase == AaptOptions::Phase::Collect ||
+            options->phase == AaptOptions::Phase::Link);
+    const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
+
+    // Build the output table path.
+    Source outputTable = options->output;
+    if (options->phase == AaptOptions::Phase::Full) {
+        appendPath(&outputTable.path, "resources.arsc");
+    }
+
     bool error = false;
-    std::queue<CompileXml> xmlCompileQueue;
+    std::queue<CompileItem> compileQueue;
 
-    //
-    // Read values XML files and XML/PNG files.
-    // Need to parse the resource type/config/filename.
-    //
-    for (const Source& source : options.sources) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        const ResourcePathData& pathData = maybePathData.value();
-        if (pathData.resourceDir == u"values") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting values..." << std::endl;
-            }
-
-            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
-            continue;
-        }
-
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting XML..." << std::endl;
-            }
-
-            error |= !collectXml(table, source, resourceName, pathData.config);
-            xmlCompileQueue.push(CompileXml{
-                    source,
-                    resourceName,
-                    pathData.config
-            });
+    // If source directories were specified, walk them looking for resource files.
+    if (!options->sourceDirs.empty()) {
+        const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+        FileFilter fileFilter;
+        if (customIgnore && customIgnore[0]) {
+            fileFilter.setPattern(customIgnore);
         } else {
-            std::unique_ptr<FileReference> fileReference = makeFileReference(
-                    table->getValueStringPool(),
-                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
-                    *type, pathData.config);
-
-            error |= !table->addResource(resourceName, pathData.config, source.line(0),
-                                         std::move(fileReference));
+            fileFilter.setPattern(
+                    "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
         }
-    }
 
-    if (error) {
-        return false;
-    }
-
-    versionStylesForCompat(table);
-
-    //
-    // Verify all references and data types.
-    //
-    Linker linker(table, resolver);
-    if (!linker.linkAndValidate()) {
-        Logger::error()
-                << "linking failed."
-                << std::endl;
-        return false;
-    }
-
-    const auto& unresolvedRefs = linker.getUnresolvedReferences();
-    if (!unresolvedRefs.empty()) {
-        for (const auto& entry : unresolvedRefs) {
-            for (const auto& source : entry.second) {
-                Logger::error(source)
-                        << "unresolved symbol '"
-                        << entry.first
-                        << "'."
-                        << std::endl;
+        for (const Source& source : options->sourceDirs) {
+            if (!walkTree(source, fileFilter, &options->collectFiles)) {
+                return false;
             }
         }
-        return false;
     }
 
-    //
-    // Compile the XML files.
-    //
-    while (!xmlCompileQueue.empty()) {
-        const CompileXml& item = xmlCompileQueue.front();
-
-        // Create the output path from the resource name.
-        std::stringstream outputPath;
-        outputPath << item.name.type;
-        if (item.config != ConfigDescription{}) {
-            outputPath << "-" << item.config.toString();
-        }
-
-        Source outSource = options.output;
-        appendPath(&outSource.path, "res");
-        appendPath(&outSource.path, outputPath.str());
-
-        if (!mkdirs(outSource.path)) {
-            Logger::error(outSource) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
-        if (options.verbose) {
-            Logger::note(outSource) << "compiling XML file." << std::endl;
-        }
-
-        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
-        xmlCompileQueue.pop();
-    }
-
-    if (error) {
-        return false;
-    }
-
-    //
-    // Compile the AndroidManifest.xml file.
-    //
-    if (!compileAndroidManifest(resolver, options)) {
-        return false;
-    }
-
-    //
-    // Generate the Java R class.
-    //
-    if (options.generateJavaClass) {
-        Source outPath = options.generateJavaClass.value();
-        if (options.verbose) {
-            Logger::note()
-                    << "writing symbols to "
-                    << outPath
-                    << "."
-                    << std::endl;
-        }
-
-        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
-            appendPath(&outPath.path, part);
-        }
-
-        if (!mkdirs(outPath.path)) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&outPath.path, "R.java");
-
-        std::ofstream fout(outPath.path);
-        if (!fout) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
-        if (!generator.generate(fout)) {
-            Logger::error(outPath)
-                    << generator.getError()
-                    << "."
-                    << std::endl;
-            return false;
-        }
-    }
-
-    //
-    // Flatten resource table.
-    //
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = false;
-        TableFlattener flattener(tableOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
-            return false;
-        }
-
-        if (options.verbose) {
-            Logger::note()
-                    << "Final resource table size="
-                    << util::formatSize(buffer.size())
-                    << std::endl;
-        }
-
-        std::string outTable(options.output.path);
-        appendPath(&outTable, "resources.arsc");
-
-        std::ofstream fout(outTable, std::ofstream::binary);
-        if (!fout) {
-            Logger::error(Source{outTable})
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
-        }
-
-        if (!util::writeAll(fout, buffer)) {
-            Logger::error(Source{outTable})
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
-        }
-        fout.flush();
-    }
-    return true;
-}
-
-static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                      const AaptOptions& options) {
-    bool error = false;
-
-    //
-    // Read values XML files and XML/PNG files.
-    // Need to parse the resource type/config/filename.
-    //
-    for (const Source& source : options.sources) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        const ResourcePathData& pathData = maybePathData.value();
-        if (pathData.resourceDir == u"values") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting values..." << std::endl;
-            }
-
-            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
-            continue;
-        }
-
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            if (options.verbose) {
-                Logger::note(source) << "collecting XML..." << std::endl;
-            }
-
-            error |= !collectXml(table, source, resourceName, pathData.config);
-        } else {
-            std::unique_ptr<FileReference> fileReference = makeFileReference(
-                    table->getValueStringPool(),
-                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
-                    *type,
-                    pathData.config);
-            error |= !table->addResource(resourceName, pathData.config, source.line(0),
-                                         std::move(fileReference));
-        }
-    }
-
-    if (error) {
-        return false;
-    }
-
-    Linker linker(table, resolver);
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
-
-    //
-    // Flatten resource table->
-    //
-    if (table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = true;
-        TableFlattener flattener(tableOptions);
-        if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
-            return false;
-        }
-
-        std::ofstream fout(options.output.path, std::ofstream::binary);
-        if (!fout) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
-        }
-
-        if (!util::writeAll(fout, buffer)) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
-            return false;
-        }
-        fout.flush();
-    }
-    return true;
-}
-
-static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                   const AaptOptions& options) {
-    bool error = false;
-
-    for (const Source& source : options.sources) {
+    // Load all binary resource tables.
+    for (const Source& source : options->linkFiles) {
         error |= !loadBinaryResourceTable(table, source);
     }
 
@@ -1119,41 +755,164 @@
         return false;
     }
 
-    versionStylesForCompat(table);
+    // Collect all the resource files.
+    // Need to parse the resource type/config/filename.
+    for (const Source& source : options->collectFiles) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
 
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            if (options->verbose) {
+                Logger::note(source) << "collecting values..." << std::endl;
+            }
+
+            error |= !collectValues(table, source, pathData.config);
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+                                  << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+
+        // Add the file name to the resource table.
+        std::unique_ptr<FileReference> fileReference = makeFileReference(
+                table->getValueStringPool(),
+                util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                *type, pathData.config);
+        error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                     std::move(fileReference));
+
+        if (pathData.extension == "xml") {
+            error |= !collectXml(table, source, resourceName, pathData.config);
+        }
+
+        compileQueue.push(
+                CompileItem{ source, resourceName, pathData.config, pathData.extension });
+    }
+
+    if (error) {
+        return false;
+    }
+
+    // Version all styles referencing attributes outside of their specified SDK version.
+    if (versionStyles) {
+        versionStylesForCompat(table);
+    }
+
+    // Verify that all references are valid.
     Linker linker(table, resolver);
     if (!linker.linkAndValidate()) {
         return false;
     }
 
-    const auto& unresolvedRefs = linker.getUnresolvedReferences();
-    if (!unresolvedRefs.empty()) {
-        for (const auto& entry : unresolvedRefs) {
-            for (const auto& source : entry.second) {
-                Logger::error(source)
-                        << "unresolved symbol '"
-                        << entry.first
-                        << "'."
-                        << std::endl;
+    // Verify that all symbols exist.
+    if (verifyNoMissingSymbols) {
+        const auto& unresolvedRefs = linker.getUnresolvedReferences();
+        if (!unresolvedRefs.empty()) {
+            for (const auto& entry : unresolvedRefs) {
+                for (const auto& source : entry.second) {
+                    Logger::error(source) << "unresolved symbol '" << entry.first << "'."
+                                          << std::endl;
+                }
             }
+            return false;
         }
-        return false;
     }
 
-    //
-    // Generate the Java R class.
-    //
-    if (options.generateJavaClass) {
-        Source outPath = options.generateJavaClass.value();
-        if (options.verbose) {
-            Logger::note()
-                    << "writing symbols to "
-                    << outPath
-                    << "."
-                    << std::endl;
+    // Compile files.
+    if (compileFiles) {
+        // First process any input compile files.
+        for (const Source& source : options->compileFiles) {
+            Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+            if (!maybePathData) {
+                return false;
+            }
+
+            const ResourcePathData& pathData = maybePathData.value();
+            const ResourceType* type = parseResourceType(pathData.resourceDir);
+            if (!type) {
+                Logger::error(source) << "invalid resource type '" << pathData.resourceDir
+                                      << "'." << std::endl;
+                return false;
+            }
+
+            ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+            compileQueue.push(
+                    CompileItem{ source, resourceName, pathData.config, pathData.extension });
         }
 
-        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+        // Now process the actual compile queue.
+        for (; !compileQueue.empty(); compileQueue.pop()) {
+            const CompileItem& item = compileQueue.front();
+
+            // Create the output directory path from the resource type and config.
+            std::stringstream outputPath;
+            outputPath << item.name.type;
+            if (item.config != ConfigDescription{}) {
+                outputPath << "-" << item.config.toString();
+            }
+
+            Source outSource = options->output;
+            appendPath(&outSource.path, "res");
+            appendPath(&outSource.path, outputPath.str());
+
+            // Make the directory.
+            if (!mkdirs(outSource.path)) {
+                Logger::error(outSource) << strerror(errno) << std::endl;
+                return false;
+            }
+
+            // Add the file name to the directory path.
+            appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
+
+            if (item.extension == "xml") {
+                if (options->verbose) {
+                    Logger::note(outSource) << "compiling XML file." << std::endl;
+                }
+
+                error |= !compileXml(resolver, item, outSource, &compileQueue);
+            } else if (item.extension == "png" || item.extension == "9.png") {
+                if (options->verbose) {
+                    Logger::note(outSource) << "compiling png file." << std::endl;
+                }
+
+                error |= !compilePng(item.source, outSource);
+            } else {
+                error |= !copyFile(item.source, outSource);
+            }
+        }
+
+        if (error) {
+            return false;
+        }
+    }
+
+    // Compile and validate the AndroidManifest.xml.
+    if (!options->manifest.path.empty()) {
+        if (!compileAndroidManifest(resolver, *options)) {
+            return false;
+        }
+    }
+
+    // Generate the Java class file.
+    if (options->generateJavaClass) {
+        Source outPath = options->generateJavaClass.value();
+        if (options->verbose) {
+            Logger::note() << "writing symbols to " << outPath << "." << std::endl;
+        }
+
+        // Build the output directory from the package name.
+        // Eg. com.android.app -> com/android/app
+        const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
+        for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
             appendPath(&outPath.path, part);
         }
 
@@ -1170,52 +929,37 @@
             return false;
         }
 
-        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        JavaClassGenerator generator(table, {});
         if (!generator.generate(fout)) {
-            Logger::error(outPath)
-                    << generator.getError()
-                    << "."
-                    << std::endl;
+            Logger::error(outPath) << generator.getError() << "." << std::endl;
             return false;
         }
     }
 
-    //
-    // Flatten resource table.
-    //
-    if (table->begin() != table->end()) {
+    // Flatten the resource table.
+    if (flattenTable && table->begin() != table->end()) {
         BigBuffer buffer(1024);
         TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = false;
+        tableOptions.useExtendedChunks = useExtendedChunks;
         TableFlattener flattener(tableOptions);
         if (!flattener.flatten(&buffer, *table)) {
-            Logger::error()
-                    << "failed to flatten resource table->"
-                    << std::endl;
+            Logger::error() << "failed to flatten resource table." << std::endl;
             return false;
         }
 
-        if (options.verbose) {
-            Logger::note()
-                    << "Final resource table size="
-                    << util::formatSize(buffer.size())
-                    << std::endl;
+        if (options->verbose) {
+            Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+                           << std::endl;
         }
 
-        std::ofstream fout(options.output.path, std::ofstream::binary);
+        std::ofstream fout(outputTable.path, std::ofstream::binary);
         if (!fout) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
+            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
             return false;
         }
 
         if (!util::writeAll(fout, buffer)) {
-            Logger::error(options.output)
-                    << strerror(errno)
-                    << "."
-                    << std::endl;
+            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
             return false;
         }
         fout.flush();
@@ -1223,134 +967,24 @@
     return true;
 }
 
-static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
-                      const AaptOptions& options) {
-    std::queue<CompileXml> xmlCompileQueue;
-
-    for (const Source& source : options.sources) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
-            return false;
-        }
-
-        ResourcePathData& pathData = maybePathData.value();
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source)
-                    << "invalid resource type '"
-                    << pathData.resourceDir
-                    << "'."
-                    << std::endl;
-            return false;
-        }
-
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
-        if (pathData.extension == "xml") {
-            xmlCompileQueue.push(CompileXml{
-                    source,
-                    resourceName,
-                    pathData.config
-            });
-        } else {
-            // TODO(adamlesinski): Handle images here.
-        }
-    }
-
-    bool error = false;
-    while (!xmlCompileQueue.empty()) {
-        const CompileXml& item = xmlCompileQueue.front();
-
-        // Create the output path from the resource name.
-        std::stringstream outputPath;
-        outputPath << item.name.type;
-        if (item.config != ConfigDescription{}) {
-            outputPath << "-" << item.config.toString();
-        }
-
-        Source outSource = options.output;
-        appendPath(&outSource.path, "res");
-        appendPath(&outSource.path, outputPath.str());
-
-        if (!mkdirs(outSource.path)) {
-            Logger::error(outSource) << strerror(errno) << std::endl;
-            return false;
-        }
-
-        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
-
-        if (options.verbose) {
-            Logger::note(outSource) << "compiling XML file." << std::endl;
-        }
-
-        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
-        xmlCompileQueue.pop();
-    }
-    return !error;
-}
-
 int main(int argc, char** argv) {
     Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+    AaptOptions options = prepareArgs(argc, argv);
 
-    std::vector<StringPiece> args;
-    args.reserve(argc - 1);
-    for (int i = 1; i < argc; i++) {
-        args.emplace_back(argv[i], strlen(argv[i]));
+    // If we specified a manifest, go ahead and load the package name from the manifest.
+    if (!options.manifest.path.empty()) {
+        if (!loadAppInfo(options.manifest, &options.appInfo)) {
+            return false;
+        }
     }
 
-    if (args.empty()) {
-        Logger::error() << "no command specified." << std::endl;
-        return 1;
-    }
-
-    AaptOptions options;
-
-    // Check the command we're running.
-    const StringPiece& command = args.front();
-    if (command == "package") {
-        if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "collect") {
-        if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "link") {
-        if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else if (command == "compile") {
-        if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
-            return 1;
-        }
-    } else {
-        Logger::error() << "unknown command '" << command << "'." << std::endl;
-        return 1;
-    }
-
-    //
     // Verify we have some common options set.
-    //
-
-    if (options.sources.empty()) {
-        Logger::error() << "no sources specified." << std::endl;
-        return false;
-    }
-
-    if (options.output.path.empty()) {
-        Logger::error() << "no output directory specified." << std::endl;
-        return false;
-    }
-
     if (options.appInfo.package.empty()) {
         Logger::error() << "no package name specified." << std::endl;
         return false;
     }
 
-
-    //
-    // Every phase needs a resource table and a resolver/linker.
-    //
-
+    // Every phase needs a resource table.
     std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
     table->setPackage(options.appInfo.package);
     if (options.appInfo.package == u"android") {
@@ -1359,9 +993,7 @@
         table->setPackageId(0x7f);
     }
 
-    //
     // Load the included libraries.
-    //
     std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
     for (const Source& source : options.libraries) {
         if (util::stringEndsWith(source.path, ".arsc")) {
@@ -1393,33 +1025,9 @@
     // Make the resolver that will cache IDs for us.
     std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
 
-    //
-    // Dispatch to the real phase here.
-    //
-
-    bool result = true;
-    switch (options.phase) {
-        case AaptOptions::Phase::LegacyFull:
-            result = doLegacy(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Collect:
-            result = doCollect(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Link:
-            result = doLink(table, resolver, options);
-            break;
-
-        case AaptOptions::Phase::Compile:
-            result = doCompile(table, resolver, options);
-            break;
-    }
-
-    if (!result) {
-        Logger::error()
-                << "aapt exiting with failures."
-                << std::endl;
+    // Do the work.
+    if (!doAll(&options, table, resolver)) {
+        Logger::error() << "aapt exiting with failures." << std::endl;
         return 1;
     }
     return 0;
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
new file mode 100644
index 0000000..dd753f1
--- /dev/null
+++ b/tools/aapt2/Png.cpp
@@ -0,0 +1,1284 @@
+/*
+ * 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.
+ */
+
+#include "Logger.h"
+#include "Png.h"
+#include "Source.h"
+#include "Util.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <iostream>
+#include <png.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <zlib.h>
+
+namespace aapt {
+
+constexpr bool kDebug = false;
+constexpr size_t kPngSignatureSize = 8u;
+
+struct PngInfo {
+    ~PngInfo() {
+        for (png_bytep row : rows) {
+            if (row != nullptr) {
+                delete[] row;
+            }
+        }
+
+        delete[] xDivs;
+        delete[] yDivs;
+    }
+
+    void* serialize9Patch() {
+        void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs,
+                                                              colors.data());
+        reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile();
+        return serialized;
+    }
+
+    uint32_t width = 0;
+    uint32_t height = 0;
+    std::vector<png_bytep> rows;
+
+    bool is9Patch = false;
+    android::Res_png_9patch info9Patch;
+    int32_t* xDivs = nullptr;
+    int32_t* yDivs = nullptr;
+    std::vector<uint32_t> colors;
+
+    // Layout padding.
+    bool haveLayoutBounds = false;
+    int32_t layoutBoundsLeft;
+    int32_t layoutBoundsTop;
+    int32_t layoutBoundsRight;
+    int32_t layoutBoundsBottom;
+
+    // Round rect outline description.
+    int32_t outlineInsetsLeft;
+    int32_t outlineInsetsTop;
+    int32_t outlineInsetsRight;
+    int32_t outlineInsetsBottom;
+    float outlineRadius;
+    uint8_t outlineAlpha;
+};
+
+static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) {
+    std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr));
+    if (!input->read(reinterpret_cast<char*>(data), length)) {
+        png_error(readPtr, strerror(errno));
+    }
+}
+
+static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
+    std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+    if (!output->write(reinterpret_cast<const char*>(data), length)) {
+        png_error(writePtr, strerror(errno));
+    }
+}
+
+static void flushDataToStream(png_structp writePtr) {
+    std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
+    if (!output->flush()) {
+        png_error(writePtr, strerror(errno));
+    }
+}
+
+static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
+    SourceLogger* logger = reinterpret_cast<SourceLogger*>(png_get_error_ptr(readPtr));
+    logger->warn() << warningMessage << "." << std::endl;
+}
+
+
+static bool readPng(png_structp readPtr, png_infop infoPtr, PngInfo* outInfo,
+                    std::string* outError) {
+    if (setjmp(png_jmpbuf(readPtr))) {
+        *outError = "failed reading png";
+        return false;
+    }
+
+    png_set_sig_bytes(readPtr, kPngSignatureSize);
+    png_read_info(readPtr, infoPtr);
+
+    int colorType, bitDepth, interlaceType, compressionType;
+    png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType,
+                 &interlaceType, &compressionType, nullptr);
+
+    if (colorType == PNG_COLOR_TYPE_PALETTE) {
+        png_set_palette_to_rgb(readPtr);
+    }
+
+    if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
+        png_set_expand_gray_1_2_4_to_8(readPtr);
+    }
+
+    if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) {
+        png_set_tRNS_to_alpha(readPtr);
+    }
+
+    if (bitDepth == 16) {
+        png_set_strip_16(readPtr);
+    }
+
+    if (!(colorType & PNG_COLOR_MASK_ALPHA)) {
+        png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER);
+    }
+
+    if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        png_set_gray_to_rgb(readPtr);
+    }
+
+    png_set_interlace_handling(readPtr);
+    png_read_update_info(readPtr, infoPtr);
+
+    const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr);
+    outInfo->rows.resize(outInfo->height);
+    for (size_t i = 0; i < outInfo->height; i++) {
+        outInfo->rows[i] = new png_byte[rowBytes];
+    }
+
+    png_read_image(readPtr, outInfo->rows.data());
+    png_read_end(readPtr, infoPtr);
+    return true;
+}
+
+static void checkNinePatchSerialization(android::Res_png_9patch* inPatch,  void* data) {
+    size_t patchSize = inPatch->serializedSize();
+    void* newData = malloc(patchSize);
+    memcpy(newData, data, patchSize);
+    android::Res_png_9patch* outPatch = inPatch->deserialize(newData);
+    outPatch->fileToDevice();
+    // deserialization is done in place, so outPatch == newData
+    assert(outPatch == newData);
+    assert(outPatch->numXDivs == inPatch->numXDivs);
+    assert(outPatch->numYDivs == inPatch->numYDivs);
+    assert(outPatch->paddingLeft == inPatch->paddingLeft);
+    assert(outPatch->paddingRight == inPatch->paddingRight);
+    assert(outPatch->paddingTop == inPatch->paddingTop);
+    assert(outPatch->paddingBottom == inPatch->paddingBottom);
+/*    for (int i = 0; i < outPatch->numXDivs; i++) {
+        assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]);
+    }
+    for (int i = 0; i < outPatch->numYDivs; i++) {
+        assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]);
+    }
+    for (int i = 0; i < outPatch->numColors; i++) {
+        assert(outPatch->getColors()[i] == inPatch->getColors()[i]);
+    }*/
+    free(newData);
+}
+
+/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) {
+    int i, j, rr, gg, bb, aa;
+
+    int bpp;
+    if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) {
+        bpp = 1;
+    } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        bpp = 2;
+    } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) {
+        // We use a padding byte even when there is no alpha
+        bpp = 4;
+    } else {
+        printf("Unknown color type %d.\n", color_type);
+    }
+
+    for (j = 0; j < h; j++) {
+        const png_byte* row = rows[j];
+        for (i = 0; i < w; i++) {
+            rr = row[0];
+            gg = row[1];
+            bb = row[2];
+            aa = row[3];
+            row += bpp;
+
+            if (i == 0) {
+                printf("Row %d:", j);
+            }
+            switch (bpp) {
+            case 1:
+                printf(" (%d)", rr);
+                break;
+            case 2:
+                printf(" (%d %d", rr, gg);
+                break;
+            case 3:
+                printf(" (%d %d %d)", rr, gg, bb);
+                break;
+            case 4:
+                printf(" (%d %d %d %d)", rr, gg, bb, aa);
+                break;
+            }
+            if (i == (w - 1)) {
+                printf("\n");
+            }
+        }
+    }
+}*/
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define ABS(a)   ((a)<0?-(a):(a))
+
+static void analyze_image(SourceLogger* logger, const PngInfo& imageInfo, int grayscaleTolerance,
+                          png_colorp rgbPalette, png_bytep alphaPalette,
+                          int *paletteEntries, bool *hasTransparency, int *colorType,
+                          png_bytepp outRows) {
+    int w = imageInfo.width;
+    int h = imageInfo.height;
+    int i, j, rr, gg, bb, aa, idx;
+    uint32_t colors[256], col;
+    int num_colors = 0;
+    int maxGrayDeviation = 0;
+
+    bool isOpaque = true;
+    bool isPalette = true;
+    bool isGrayscale = true;
+
+    // Scan the entire image and determine if:
+    // 1. Every pixel has R == G == B (grayscale)
+    // 2. Every pixel has A == 255 (opaque)
+    // 3. There are no more than 256 distinct RGBA colors
+
+    if (kDebug) {
+        printf("Initial image data:\n");
+        //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA);
+    }
+
+    for (j = 0; j < h; j++) {
+        const png_byte* row = imageInfo.rows[j];
+        png_bytep out = outRows[j];
+        for (i = 0; i < w; i++) {
+            rr = *row++;
+            gg = *row++;
+            bb = *row++;
+            aa = *row++;
+
+            int odev = maxGrayDeviation;
+            maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation);
+            maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation);
+            if (maxGrayDeviation > odev) {
+                if (kDebug) {
+                    printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n",
+                            maxGrayDeviation, i, j, rr, gg, bb, aa);
+                }
+            }
+
+            // Check if image is really grayscale
+            if (isGrayscale) {
+                if (rr != gg || rr != bb) {
+                    if (kDebug) {
+                        printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n",
+                                i, j, rr, gg, bb, aa);
+                    }
+                    isGrayscale = false;
+                }
+            }
+
+            // Check if image is really opaque
+            if (isOpaque) {
+                if (aa != 0xff) {
+                    if (kDebug) {
+                        printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n",
+                                i, j, rr, gg, bb, aa);
+                    }
+                    isOpaque = false;
+                }
+            }
+
+            // Check if image is really <= 256 colors
+            if (isPalette) {
+                col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa);
+                bool match = false;
+                for (idx = 0; idx < num_colors; idx++) {
+                    if (colors[idx] == col) {
+                        match = true;
+                        break;
+                    }
+                }
+
+                // Write the palette index for the pixel to outRows optimistically
+                // We might overwrite it later if we decide to encode as gray or
+                // gray + alpha
+                *out++ = idx;
+                if (!match) {
+                    if (num_colors == 256) {
+                        if (kDebug) {
+                            printf("Found 257th color at %d, %d\n", i, j);
+                        }
+                        isPalette = false;
+                    } else {
+                        colors[num_colors++] = col;
+                    }
+                }
+            }
+        }
+    }
+
+    *paletteEntries = 0;
+    *hasTransparency = !isOpaque;
+    int bpp = isOpaque ? 3 : 4;
+    int paletteSize = w * h + bpp * num_colors;
+
+    if (kDebug) {
+        printf("isGrayscale = %s\n", isGrayscale ? "true" : "false");
+        printf("isOpaque = %s\n", isOpaque ? "true" : "false");
+        printf("isPalette = %s\n", isPalette ? "true" : "false");
+        printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n",
+                paletteSize, 2 * w * h, bpp * w * h);
+        printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance);
+    }
+
+    // Choose the best color type for the image.
+    // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel
+    // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations
+    //     is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA
+    // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently
+    //     small, otherwise use COLOR_TYPE_RGB{_ALPHA}
+    if (isGrayscale) {
+        if (isOpaque) {
+            *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel
+        } else {
+            // Use a simple heuristic to determine whether using a palette will
+            // save space versus using gray + alpha for each pixel.
+            // This doesn't take into account chunk overhead, filtering, LZ
+            // compression, etc.
+            if (isPalette && (paletteSize < 2 * w * h)) {
+                *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color
+            } else {
+                *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel
+            }
+        }
+    } else if (isPalette && (paletteSize < bpp * w * h)) {
+        *colorType = PNG_COLOR_TYPE_PALETTE;
+    } else {
+        if (maxGrayDeviation <= grayscaleTolerance) {
+            logger->note() << "forcing image to gray (max deviation = " << maxGrayDeviation
+                           << ")."
+                           << std::endl;
+            *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA;
+        } else {
+            *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+        }
+    }
+
+    // Perform postprocessing of the image or palette data based on the final
+    // color type chosen
+
+    if (*colorType == PNG_COLOR_TYPE_PALETTE) {
+        // Create separate RGB and Alpha palettes and set the number of colors
+        *paletteEntries = num_colors;
+
+        // Create the RGB and alpha palettes
+        for (int idx = 0; idx < num_colors; idx++) {
+            col = colors[idx];
+            rgbPalette[idx].red   = (png_byte) ((col >> 24) & 0xff);
+            rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff);
+            rgbPalette[idx].blue  = (png_byte) ((col >>  8) & 0xff);
+            alphaPalette[idx]     = (png_byte)  (col        & 0xff);
+        }
+    } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) {
+        // If the image is gray or gray + alpha, compact the pixels into outRows
+        for (j = 0; j < h; j++) {
+            const png_byte* row = imageInfo.rows[j];
+            png_bytep out = outRows[j];
+            for (i = 0; i < w; i++) {
+                rr = *row++;
+                gg = *row++;
+                bb = *row++;
+                aa = *row++;
+
+                if (isGrayscale) {
+                    *out++ = rr;
+                } else {
+                    *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f);
+                }
+                if (!isOpaque) {
+                    *out++ = aa;
+                }
+           }
+        }
+    }
+}
+
+static bool writePng(png_structp writePtr, png_infop infoPtr, PngInfo* info,
+                     int grayScaleTolerance, SourceLogger* logger, std::string* outError) {
+    if (setjmp(png_jmpbuf(writePtr))) {
+        *outError = "failed to write png";
+        return false;
+    }
+
+    uint32_t width, height;
+    int colorType, bitDepth, interlaceType, compressionType;
+
+    png_unknown_chunk unknowns[3];
+    unknowns[0].data = nullptr;
+    unknowns[1].data = nullptr;
+    unknowns[2].data = nullptr;
+
+    png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep));
+    if (outRows == (png_bytepp) 0) {
+        printf("Can't allocate output buffer!\n");
+        exit(1);
+    }
+    for (uint32_t i = 0; i < info->height; i++) {
+        outRows[i] = (png_bytep) malloc(2 * (int) info->width);
+        if (outRows[i] == (png_bytep) 0) {
+            printf("Can't allocate output buffer!\n");
+            exit(1);
+        }
+    }
+
+    png_set_compression_level(writePtr, Z_BEST_COMPRESSION);
+
+    if (kDebug) {
+        logger->note() << "writing image: w = " << info->width
+                       << ", h = " << info->height
+                       << std::endl;
+    }
+
+    png_color rgbPalette[256];
+    png_byte alphaPalette[256];
+    bool hasTransparency;
+    int paletteEntries;
+
+    analyze_image(logger, *info, grayScaleTolerance, rgbPalette, alphaPalette,
+                  &paletteEntries, &hasTransparency, &colorType, outRows);
+
+    // If the image is a 9-patch, we need to preserve it as a ARGB file to make
+    // sure the pixels will not be pre-dithered/clamped until we decide they are
+    if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB ||
+            colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) {
+        colorType = PNG_COLOR_TYPE_RGB_ALPHA;
+    }
+
+    if (kDebug) {
+        switch (colorType) {
+        case PNG_COLOR_TYPE_PALETTE:
+            logger->note() << "has " << paletteEntries
+                           << " colors" << (hasTransparency ? " (with alpha)" : "")
+                           << ", using PNG_COLOR_TYPE_PALLETTE."
+                           << std::endl;
+            break;
+        case PNG_COLOR_TYPE_GRAY:
+            logger->note() << "is opaque gray, using PNG_COLOR_TYPE_GRAY." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_GRAY_ALPHA:
+            logger->note() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_RGB:
+            logger->note() << "is opaque RGB, using PNG_COLOR_TYPE_RGB." << std::endl;
+            break;
+        case PNG_COLOR_TYPE_RGB_ALPHA:
+            logger->note() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA." << std::endl;
+            break;
+        }
+    }
+
+    png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType,
+                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+    if (colorType == PNG_COLOR_TYPE_PALETTE) {
+        png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries);
+        if (hasTransparency) {
+            png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0);
+        }
+        png_set_filter(writePtr, 0, PNG_NO_FILTERS);
+    } else {
+        png_set_filter(writePtr, 0, PNG_ALL_FILTERS);
+    }
+
+    if (info->is9Patch) {
+        int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0);
+        int pIndex = info->haveLayoutBounds ? 2 : 1;
+        int bIndex = 1;
+        int oIndex = 0;
+
+        // Chunks ordered thusly because older platforms depend on the base 9 patch data being last
+        png_bytep chunkNames = info->haveLayoutBounds
+                ? (png_bytep)"npOl\0npLb\0npTc\0"
+                : (png_bytep)"npOl\0npTc";
+
+        // base 9 patch data
+        if (kDebug) {
+            logger->note() << "adding 9-patch info..." << std::endl;
+        }
+        strcpy((char*)unknowns[pIndex].name, "npTc");
+        unknowns[pIndex].data = (png_byte*) info->serialize9Patch();
+        unknowns[pIndex].size = info->info9Patch.serializedSize();
+        // TODO: remove the check below when everything works
+        checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data);
+
+        // automatically generated 9 patch outline data
+        int chunkSize = sizeof(png_uint_32) * 6;
+        strcpy((char*)unknowns[oIndex].name, "npOl");
+        unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1);
+        png_byte outputData[chunkSize];
+        memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32));
+        ((float*) outputData)[4] = info->outlineRadius;
+        ((png_uint_32*) outputData)[5] = info->outlineAlpha;
+        memcpy(unknowns[oIndex].data, &outputData, chunkSize);
+        unknowns[oIndex].size = chunkSize;
+
+        // optional optical inset / layout bounds data
+        if (info->haveLayoutBounds) {
+            int chunkSize = sizeof(png_uint_32) * 4;
+            strcpy((char*)unknowns[bIndex].name, "npLb");
+            unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1);
+            memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize);
+            unknowns[bIndex].size = chunkSize;
+        }
+
+        for (int i = 0; i < chunkCount; i++) {
+            unknowns[i].location = PNG_HAVE_PLTE;
+        }
+        png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS,
+                                    chunkNames, chunkCount);
+        png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount);
+
+#if PNG_LIBPNG_VER < 10600
+        // Deal with unknown chunk location bug in 1.5.x and earlier.
+        png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE);
+        if (info->haveLayoutBounds) {
+            png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE);
+        }
+#endif
+    }
+
+    png_write_info(writePtr, infoPtr);
+
+    png_bytepp rows;
+    if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) {
+        if (colorType == PNG_COLOR_TYPE_RGB) {
+            png_set_filler(writePtr, 0, PNG_FILLER_AFTER);
+        }
+        rows = info->rows.data();
+    } else {
+        rows = outRows;
+    }
+    png_write_image(writePtr, rows);
+
+    if (kDebug) {
+        printf("Final image data:\n");
+        //dump_image(info->width, info->height, rows, colorType);
+    }
+
+    png_write_end(writePtr, infoPtr);
+
+    for (uint32_t i = 0; i < info->height; i++) {
+        free(outRows[i]);
+    }
+    free(outRows);
+    free(unknowns[0].data);
+    free(unknowns[1].data);
+    free(unknowns[2].data);
+
+    png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType,
+                 &compressionType, nullptr);
+
+    if (kDebug) {
+        logger->note() << "image written: w = " << width << ", h = " << height
+                       << ", d = " << bitDepth << ", colors = " << colorType
+                       << ", inter = " << interlaceType << ", comp = " << compressionType
+                       << std::endl;
+    }
+    return true;
+}
+
+constexpr uint32_t kColorWhite = 0xffffffffu;
+constexpr uint32_t kColorTick = 0xff000000u;
+constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu;
+
+enum class TickType {
+    kNone,
+    kTick,
+    kLayoutBounds,
+    kBoth
+};
+
+static TickType tickType(png_bytep p, bool transparent, const char** outError) {
+    png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+
+    if (transparent) {
+        if (p[3] == 0) {
+            return TickType::kNone;
+        }
+        if (color == kColorLayoutBoundsTick) {
+            return TickType::kLayoutBounds;
+        }
+        if (color == kColorTick) {
+            return TickType::kTick;
+        }
+
+        // Error cases
+        if (p[3] != 0xff) {
+            *outError = "Frame pixels must be either solid or transparent "
+                        "(not intermediate alphas)";
+            return TickType::kNone;
+        }
+
+        if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+            *outError = "Ticks in transparent frame must be black or red";
+        }
+        return TickType::kTick;
+    }
+
+    if (p[3] != 0xFF) {
+        *outError = "White frame must be a solid color (no alpha)";
+    }
+    if (color == kColorWhite) {
+        return TickType::kNone;
+    }
+    if (color == kColorTick) {
+        return TickType::kTick;
+    }
+    if (color == kColorLayoutBoundsTick) {
+        return TickType::kLayoutBounds;
+    }
+
+    if (p[0] != 0 || p[1] != 0 || p[2] != 0) {
+        *outError = "Ticks in white frame must be black or red";
+        return TickType::kNone;
+    }
+    return TickType::kTick;
+}
+
+enum class TickState {
+    kStart,
+    kInside1,
+    kOutside1
+};
+
+static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required,
+                               int32_t* outLeft, int32_t* outRight, const char** outError,
+                               uint8_t* outDivs, bool multipleAllowed) {
+    *outLeft = *outRight = -1;
+    TickState state = TickState::kStart;
+    bool found = false;
+
+    for (int i = 1; i < width - 1; i++) {
+        if (tickType(row+i*4, transparent, outError) == TickType::kTick) {
+            if (state == TickState::kStart ||
+                (state == TickState::kOutside1 && multipleAllowed)) {
+                *outLeft = i-1;
+                *outRight = width-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TickState::kInside1;
+            } else if (state == TickState::kOutside1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outLeft = i;
+                return false;
+            }
+        } else if (!*outError) {
+            if (state == TickState::kInside1) {
+                // We're done with this div.  Move on to the next.
+                *outRight = i-1;
+                outRight += 2;
+                outLeft += 2;
+                state = TickState::kOutside1;
+            }
+        } else {
+            *outLeft = i;
+            return false;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outLeft = -1;
+        return false;
+    }
+    return true;
+}
+
+static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent,
+                             bool required, int32_t* outTop, int32_t* outBottom,
+                             const char** outError, uint8_t* outDivs, bool multipleAllowed) {
+    *outTop = *outBottom = -1;
+    TickState state = TickState::kStart;
+    bool found = false;
+
+    for (int i = 1; i < height - 1; i++) {
+        if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) {
+            if (state == TickState::kStart ||
+                (state == TickState::kOutside1 && multipleAllowed)) {
+                *outTop = i-1;
+                *outBottom = height-2;
+                found = true;
+                if (outDivs != NULL) {
+                    *outDivs += 2;
+                }
+                state = TickState::kInside1;
+            } else if (state == TickState::kOutside1) {
+                *outError = "Can't have more than one marked region along edge";
+                *outTop = i;
+                return false;
+            }
+        } else if (!*outError) {
+            if (state == TickState::kInside1) {
+                // We're done with this div.  Move on to the next.
+                *outBottom = i-1;
+                outTop += 2;
+                outBottom += 2;
+                state = TickState::kOutside1;
+            }
+        } else {
+            *outTop = i;
+            return false;
+        }
+    }
+
+    if (required && !found) {
+        *outError = "No marked region found along edge";
+        *outTop = -1;
+        return false;
+    }
+    return true;
+}
+
+static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent,
+                                           bool /* required */, int32_t* outLeft,
+                                           int32_t* outRight, const char** outError) {
+    *outLeft = *outRight = 0;
+
+    // Look for left tick
+    if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) {
+        // Starting with a layout padding tick
+        int i = 1;
+        while (i < width - 1) {
+            (*outLeft)++;
+            i++;
+            if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+
+    // Look for right tick
+    if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) {
+        // Ending with a layout padding tick
+        int i = width - 2;
+        while (i > 1) {
+            (*outRight)++;
+            i--;
+            if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+    return true;
+}
+
+static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent,
+                                         bool /* required */, int32_t* outTop, int32_t* outBottom,
+                                         const char** outError) {
+    *outTop = *outBottom = 0;
+
+    // Look for top tick
+    if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) {
+        // Starting with a layout padding tick
+        int i = 1;
+        while (i < height - 1) {
+            (*outTop)++;
+            i++;
+            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+
+    // Look for bottom tick
+    if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) {
+        // Ending with a layout padding tick
+        int i = height - 2;
+        while (i > 1) {
+            (*outBottom)++;
+            i--;
+            if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) {
+                break;
+            }
+        }
+    }
+    return true;
+}
+
+static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY,
+                           int dX, int dY, int* outInset) {
+    uint8_t maxOpacity = 0;
+    int inset = 0;
+    *outInset = 0;
+    for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) {
+        png_byte* color = rows[y] + x * 4;
+        uint8_t opacity = color[3];
+        if (opacity > maxOpacity) {
+            maxOpacity = opacity;
+            *outInset = inset;
+        }
+        if (opacity == 0xff) return;
+    }
+}
+
+static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) {
+    uint8_t maxAlpha = 0;
+    for (int x = startX; x < endX; x++) {
+        uint8_t alpha = (row + x * 4)[3];
+        if (alpha > maxAlpha) maxAlpha = alpha;
+    }
+    return maxAlpha;
+}
+
+static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) {
+    uint8_t maxAlpha = 0;
+    for (int y = startY; y < endY; y++) {
+        uint8_t alpha = (rows[y] + offsetX * 4)[3];
+        if (alpha > maxAlpha) maxAlpha = alpha;
+    }
+    return maxAlpha;
+}
+
+static void getOutline(PngInfo* image) {
+    int midX = image->width / 2;
+    int midY = image->height / 2;
+    int endX = image->width - 2;
+    int endY = image->height - 2;
+
+    // find left and right extent of nine patch content on center row
+    if (image->width > 4) {
+        findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft);
+        findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0,
+                       &image->outlineInsetsRight);
+    } else {
+        image->outlineInsetsLeft = 0;
+        image->outlineInsetsRight = 0;
+    }
+
+    // find top and bottom extent of nine patch content on center column
+    if (image->height > 4) {
+        findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop);
+        findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1,
+                       &image->outlineInsetsBottom);
+    } else {
+        image->outlineInsetsTop = 0;
+        image->outlineInsetsBottom = 0;
+    }
+
+    int innerStartX = 1 + image->outlineInsetsLeft;
+    int innerStartY = 1 + image->outlineInsetsTop;
+    int innerEndX = endX - image->outlineInsetsRight;
+    int innerEndY = endY - image->outlineInsetsBottom;
+    int innerMidX = (innerEndX + innerStartX) / 2;
+    int innerMidY = (innerEndY + innerStartY) / 2;
+
+    // assuming the image is a round rect, compute the radius by marching
+    // diagonally from the top left corner towards the center
+    image->outlineAlpha = std::max(
+            maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX),
+            maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY));
+
+    int diagonalInset = 0;
+    findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1,
+                   &diagonalInset);
+
+    /* Determine source radius based upon inset:
+     *     sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r
+     *     sqrt(2) * r = sqrt(2) * i + r
+     *     (sqrt(2) - 1) * r = sqrt(2) * i
+     *     r = sqrt(2) / (sqrt(2) - 1) * i
+     */
+    image->outlineRadius = 3.4142f * diagonalInset;
+
+    if (kDebug) {
+        printf("outline insets %d %d %d %d, rad %f, alpha %x\n",
+                image->outlineInsetsLeft,
+                image->outlineInsetsTop,
+                image->outlineInsetsRight,
+                image->outlineInsetsBottom,
+                image->outlineRadius,
+                image->outlineAlpha);
+    }
+}
+
+static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) {
+    png_bytep color = rows[top] + left*4;
+
+    if (left > right || top > bottom) {
+        return android::Res_png_9patch::TRANSPARENT_COLOR;
+    }
+
+    while (top <= bottom) {
+        for (int i = left; i <= right; i++) {
+            png_bytep p = rows[top]+i*4;
+            if (color[3] == 0) {
+                if (p[3] != 0) {
+                    return android::Res_png_9patch::NO_COLOR;
+                }
+            } else if (p[0] != color[0] || p[1] != color[1] ||
+                    p[2] != color[2] || p[3] != color[3]) {
+                return android::Res_png_9patch::NO_COLOR;
+            }
+        }
+        top++;
+    }
+
+    if (color[3] == 0) {
+        return android::Res_png_9patch::TRANSPARENT_COLOR;
+    }
+    return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2];
+}
+
+static bool do9Patch(PngInfo* image, std::string* outError) {
+    image->is9Patch = true;
+
+    int W = image->width;
+    int H = image->height;
+    int i, j;
+
+    const int maxSizeXDivs = W * sizeof(int32_t);
+    const int maxSizeYDivs = H * sizeof(int32_t);
+    int32_t* xDivs = image->xDivs = new int32_t[W];
+    int32_t* yDivs = image->yDivs = new int32_t[H];
+    uint8_t numXDivs = 0;
+    uint8_t numYDivs = 0;
+
+    int8_t numColors;
+    int numRows;
+    int numCols;
+    int top;
+    int left;
+    int right;
+    int bottom;
+    memset(xDivs, -1, maxSizeXDivs);
+    memset(yDivs, -1, maxSizeYDivs);
+    image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1;
+    image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1;
+    image->layoutBoundsLeft = image->layoutBoundsRight = 0;
+    image->layoutBoundsTop = image->layoutBoundsBottom = 0;
+
+    png_bytep p = image->rows[0];
+    bool transparent = p[3] == 0;
+    bool hasColor = false;
+
+    const char* errorMsg = nullptr;
+    int errorPixel = -1;
+    const char* errorEdge = nullptr;
+
+    int colorIndex = 0;
+    std::vector<png_bytep> newRows;
+
+    // Validate size...
+    if (W < 3 || H < 3) {
+        errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels";
+        goto getout;
+    }
+
+    // Validate frame...
+    if (!transparent &&
+            (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) {
+        errorMsg = "Must have one-pixel frame that is either transparent or white";
+        goto getout;
+    }
+
+    // Find left and right of sizing areas...
+    if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs,
+                            true)) {
+        errorPixel = xDivs[0];
+        errorEdge = "top";
+        goto getout;
+    }
+
+    // Find top and bottom of sizing areas...
+    if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1],
+                          &errorMsg, &numYDivs, true)) {
+        errorPixel = yDivs[0];
+        errorEdge = "left";
+        goto getout;
+    }
+
+    // Copy patch size data into image...
+    image->info9Patch.numXDivs = numXDivs;
+    image->info9Patch.numYDivs = numYDivs;
+
+    // Find left and right of padding area...
+    if (!getHorizontalTicks(image->rows[H-1], W, transparent, false,
+                            &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight,
+                            &errorMsg, nullptr, false)) {
+        errorPixel = image->info9Patch.paddingLeft;
+        errorEdge = "bottom";
+        goto getout;
+    }
+
+    // Find top and bottom of padding area...
+    if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+                          &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom,
+                          &errorMsg, nullptr, false)) {
+        errorPixel = image->info9Patch.paddingTop;
+        errorEdge = "right";
+        goto getout;
+    }
+
+    // Find left and right of layout padding...
+    getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false,
+                                   &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg);
+
+    getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false,
+                                 &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg);
+
+    image->haveLayoutBounds = image->layoutBoundsLeft != 0
+                               || image->layoutBoundsRight != 0
+                               || image->layoutBoundsTop != 0
+                               || image->layoutBoundsBottom != 0;
+
+    if (image->haveLayoutBounds) {
+        if (kDebug) {
+            printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop,
+                    image->layoutBoundsRight, image->layoutBoundsBottom);
+        }
+    }
+
+    // use opacity of pixels to estimate the round rect outline
+    getOutline(image);
+
+    // If padding is not yet specified, take values from size.
+    if (image->info9Patch.paddingLeft < 0) {
+        image->info9Patch.paddingLeft = xDivs[0];
+        image->info9Patch.paddingRight = W - 2 - xDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight;
+    }
+    if (image->info9Patch.paddingTop < 0) {
+        image->info9Patch.paddingTop = yDivs[0];
+        image->info9Patch.paddingBottom = H - 2 - yDivs[1];
+    } else {
+        // Adjust value to be correct!
+        image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom;
+    }
+
+/*    if (kDebug) {
+        printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName,
+                xDivs[0], xDivs[1],
+                yDivs[0], yDivs[1]);
+        printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName,
+                image->info9Patch.paddingLeft, image->info9Patch.paddingRight,
+                image->info9Patch.paddingTop, image->info9Patch.paddingBottom);
+    }*/
+
+    // Remove frame from image.
+    newRows.resize(H - 2);
+    for (i = 0; i < H - 2; i++) {
+        newRows[i] = image->rows[i + 1];
+        memmove(newRows[i], newRows[i] + 4, (W - 2) * 4);
+    }
+    image->rows.swap(newRows);
+
+    image->width -= 2;
+    W = image->width;
+    image->height -= 2;
+    H = image->height;
+
+    // Figure out the number of rows and columns in the N-patch
+    numCols = numXDivs + 1;
+    if (xDivs[0] == 0) {  // Column 1 is strechable
+        numCols--;
+    }
+    if (xDivs[numXDivs - 1] == W) {
+        numCols--;
+    }
+    numRows = numYDivs + 1;
+    if (yDivs[0] == 0) {  // Row 1 is strechable
+        numRows--;
+    }
+    if (yDivs[numYDivs - 1] == H) {
+        numRows--;
+    }
+
+    // Make sure the amount of rows and columns will fit in the number of
+    // colors we can use in the 9-patch format.
+    if (numRows * numCols > 0x7F) {
+        errorMsg = "Too many rows and columns in 9-patch perimeter";
+        goto getout;
+    }
+
+    numColors = numRows * numCols;
+    image->info9Patch.numColors = numColors;
+    image->colors.resize(numColors);
+
+    // Fill in color information for each patch.
+
+    uint32_t c;
+    top = 0;
+
+    // The first row always starts with the top being at y=0 and the bottom
+    // being either yDivs[1] (if yDivs[0]=0) of yDivs[0].  In the former case
+    // the first row is stretchable along the Y axis, otherwise it is fixed.
+    // The last row always ends with the bottom being bitmap.height and the top
+    // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or
+    // yDivs[numYDivs-1]. In the former case the last row is stretchable along
+    // the Y axis, otherwise it is fixed.
+    //
+    // The first and last columns are similarly treated with respect to the X
+    // axis.
+    //
+    // The above is to help explain some of the special casing that goes on the
+    // code below.
+
+    // The initial yDiv and whether the first row is considered stretchable or
+    // not depends on whether yDiv[0] was zero or not.
+    for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) {
+        if (j == numYDivs) {
+            bottom = H;
+        } else {
+            bottom = yDivs[j];
+        }
+        left = 0;
+        // The initial xDiv and whether the first column is considered
+        // stretchable or not depends on whether xDiv[0] was zero or not.
+        for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) {
+            if (i == numXDivs) {
+                right = W;
+            } else {
+                right = xDivs[i];
+            }
+            c = getColor(image->rows.data(), left, top, right - 1, bottom - 1);
+            image->colors[colorIndex++] = c;
+            if (kDebug) {
+                if (c != android::Res_png_9patch::NO_COLOR) {
+                    hasColor = true;
+                }
+            }
+            left = right;
+        }
+        top = bottom;
+    }
+
+    assert(colorIndex == numColors);
+
+    if (kDebug && hasColor) {
+        for (i = 0; i < numColors; i++) {
+            if (i == 0) printf("Colors:\n");
+            printf(" #%08x", image->colors[i]);
+            if (i == numColors - 1) printf("\n");
+        }
+    }
+getout:
+    if (errorMsg) {
+        std::stringstream err;
+        err << "9-patch malformed: " << errorMsg;
+        if (!errorEdge) {
+            err << "." << std::endl;
+            if (errorPixel >= 0) {
+                err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge";
+            } else {
+                err << "Found along " << errorEdge << " edge";
+            }
+        }
+        *outError = err.str();
+        return false;
+    }
+    return true;
+}
+
+
+bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+                  const Options& options, std::string* outError) {
+    png_byte signature[kPngSignatureSize];
+
+    // Read the PNG signature first.
+    if (!input.read(reinterpret_cast<char*>(signature), kPngSignatureSize)) {
+        *outError = strerror(errno);
+        return false;
+    }
+
+    // If the PNG signature doesn't match, bail early.
+    if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) {
+        *outError = "not a valid png file";
+        return false;
+    }
+
+    SourceLogger logger(source);
+    bool result = false;
+    png_structp readPtr = nullptr;
+    png_infop infoPtr = nullptr;
+    png_structp writePtr = nullptr;
+    png_infop writeInfoPtr = nullptr;
+    PngInfo pngInfo = {};
+
+    readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+    if (!readPtr) {
+        *outError = "failed to allocate read ptr";
+        goto bail;
+    }
+
+    infoPtr = png_create_info_struct(readPtr);
+    if (!infoPtr) {
+        *outError = "failed to allocate info ptr";
+        goto bail;
+    }
+
+    png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(&logger), nullptr, logWarning);
+
+    // Set the read function to read from std::istream.
+    png_set_read_fn(readPtr, (png_voidp)&input, readDataFromStream);
+
+    if (!readPng(readPtr, infoPtr, &pngInfo, outError)) {
+        goto bail;
+    }
+
+    if (util::stringEndsWith(source.path, ".9.png")) {
+        if (!do9Patch(&pngInfo, outError)) {
+            goto bail;
+        }
+    }
+
+    writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr);
+    if (!writePtr) {
+        *outError = "failed to allocate write ptr";
+        goto bail;
+    }
+
+    writeInfoPtr = png_create_info_struct(writePtr);
+    if (!writeInfoPtr) {
+        *outError = "failed to allocate write info ptr";
+        goto bail;
+    }
+
+    png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
+
+    // Set the write function to write to std::ostream.
+    png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+
+    if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
+                  outError)) {
+        goto bail;
+    }
+
+    result = true;
+bail:
+    if (readPtr) {
+        png_destroy_read_struct(&readPtr, &infoPtr, nullptr);
+    }
+
+    if (writePtr) {
+        png_destroy_write_struct(&writePtr, &writeInfoPtr);
+    }
+    return result;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
new file mode 100644
index 0000000..bc80754
--- /dev/null
+++ b/tools/aapt2/Png.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef AAPT_PNG_H
+#define AAPT_PNG_H
+
+#include "Source.h"
+
+#include <iostream>
+#include <string>
+
+namespace aapt {
+
+struct Png {
+    struct Options {
+        int grayScaleTolerance = 0;
+    };
+
+    bool process(const Source& source, std::istream& input, std::ostream& output,
+                 const Options& options, std::string* outError);
+};
+
+} // namespace aapt
+
+#endif // AAPT_PNG_H
diff --git a/tools/aapt2/data/res/drawable/icon.png b/tools/aapt2/data/res/drawable/icon.png
new file mode 100644
index 0000000..4bff9b9
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/icon.png
Binary files differ
diff --git a/tools/aapt2/data/res/drawable/test.9.png b/tools/aapt2/data/res/drawable/test.9.png
new file mode 100644
index 0000000..33daa11
--- /dev/null
+++ b/tools/aapt2/data/res/drawable/test.9.png
Binary files differ
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 72ee343..df76bc9 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -518,7 +518,7 @@
             if m.name.startswith("create") and m.name.endswith("Intent"):
                 pass
             else:
-                error(clazz, m, "FW1", "Methods creating an Intent must be named createFooIntent()")
+                warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
 
 
 def verify_helper_classes(clazz):