Merge "AMS: add API to IProcessInfoServive for retrieving oom scores"
diff --git a/Android.mk b/Android.mk
index f5d5a11..d22273c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -261,6 +261,7 @@
 	core/java/android/view/IApplicationToken.aidl \
 	core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \
 	core/java/android/view/IAssetAtlas.aidl \
+	core/java/android/view/IDockDividerVisibilityListener.aidl \
 	core/java/android/view/IGraphicsStats.aidl \
 	core/java/android/view/IInputFilter.aidl \
 	core/java/android/view/IInputFilterHost.aidl \
diff --git a/api/current.txt b/api/current.txt
index 5153106..59b8fb3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7809,6 +7809,7 @@
     method public abstract java.lang.String getPackageResourcePath();
     method public abstract android.content.res.Resources getResources();
     method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public final java.lang.String getString(int);
     method public final java.lang.String getString(int, java.lang.Object...);
     method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
@@ -8344,6 +8346,7 @@
     field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+    field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -31403,7 +31406,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36203,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
diff --git a/api/system-current.txt b/api/system-current.txt
index 0e80c8f..f029568 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8052,6 +8052,7 @@
     method public abstract java.lang.String getPackageResourcePath();
     method public abstract android.content.res.Resources getResources();
     method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public final java.lang.String getString(int);
     method public final java.lang.String getString(int, java.lang.Object...);
     method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -8243,6 +8244,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
@@ -8602,6 +8604,7 @@
     field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+    field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -33519,7 +33522,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -38512,6 +38518,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
diff --git a/api/test-current.txt b/api/test-current.txt
index 5153106..59b8fb3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -7809,6 +7809,7 @@
     method public abstract java.lang.String getPackageResourcePath();
     method public abstract android.content.res.Resources getResources();
     method public abstract android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public final java.lang.String getString(int);
     method public final java.lang.String getString(int, java.lang.Object...);
     method public abstract java.lang.Object getSystemService(java.lang.String);
@@ -7991,6 +7992,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
@@ -8344,6 +8346,7 @@
     field public static final java.lang.String ACTION_USER_FOREGROUND = "android.intent.action.USER_FOREGROUND";
     field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE";
     field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
+    field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
     field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
     field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
@@ -31403,7 +31406,10 @@
   public static final class Telephony.Sms.Intents {
     method public static android.telephony.SmsMessage[] getMessagesFromIntent(android.content.Intent);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+    field public static final java.lang.String ACTION_DEFAULT_SMS_PACKAGE_CHANGED = "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+    field public static final java.lang.String ACTION_EXTERNAL_PROVIDER_CHANGE = "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
     field public static final java.lang.String DATA_SMS_RECEIVED_ACTION = "android.intent.action.DATA_SMS_RECEIVED";
+    field public static final java.lang.String EXTRA_IS_DEFAULT_SMS_APP = "android.provider.extra.IS_DEFAULT_SMS_APP";
     field public static final java.lang.String EXTRA_PACKAGE_NAME = "package";
     field public static final int RESULT_SMS_DUPLICATED = 5; // 0x5
     field public static final int RESULT_SMS_GENERIC_ERROR = 2; // 0x2
@@ -36197,6 +36203,7 @@
     method public java.lang.String getPackageResourcePath();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
+    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 273483a..468c145 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -178,15 +178,8 @@
  * </p>
  * <h3>Notification strategy</h3>
  * <p>
- * For each feedback type only one accessibility service is notified. Services are notified
- * in the order of registration. Hence, if two services are registered for the same
- * feedback type in the same package the first one wins. It is possible however, to
- * register a service as the default one for a given feedback type. In such a case this
- * service is invoked if no other service was interested in the event. In other words, default
- * services do not compete with other services and are notified last regardless of the
- * registration order. This enables "generic" accessibility services that work reasonably
- * well with most applications to coexist with "polished" ones that are targeted for
- * specific applications.
+ * All accessibility services are notified of all events they have requested, regardless of their
+ * feedback type.
  * </p>
  * <p class="note">
  * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f7aee75..f1a7de8 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3084,7 +3084,7 @@
     /** {@hide} */
     public static final int FLAG_OR_STOPPED = 1 << 0;
     /** {@hide} */
-    public static final int FLAG_WITH_AMNESIA = 1 << 1;
+    public static final int FLAG_AND_LOCKED = 1 << 1;
 
     /**
      * Return whether the given user is actively running.  This means that
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 9081ef8..b24bce3 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -798,21 +798,33 @@
         }
     }
 
-    private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
+    private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
+                            SparseArray<Fragment> lastInFragments, Fragment fragment) {
         if (fragment != null) {
             int containerId = fragment.mContainerId;
-            if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
-                    fragment.getView() != null && fragments.get(containerId) == null) {
-                fragments.put(containerId, fragment);
+            if (containerId != 0 && !fragment.isHidden()) {
+                if (fragment.isAdded() && fragment.getView() != null
+                        && firstOutFragments.get(containerId) == null) {
+                    firstOutFragments.put(containerId, fragment);
+                }
+                if (lastInFragments.get(containerId) == fragment) {
+                    lastInFragments.remove(containerId);
+                }
             }
         }
     }
 
-    private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
+    private void setLastIn(SparseArray<Fragment> firstOutFragments,
+            SparseArray<Fragment> lastInFragments, Fragment fragment) {
         if (fragment != null) {
             int containerId = fragment.mContainerId;
             if (containerId != 0) {
-                fragments.put(containerId, fragment);
+                if (!fragment.isAdded()) {
+                    lastInFragments.put(containerId, fragment);
+                }
+                if (firstOutFragments.get(containerId) == fragment) {
+                    firstOutFragments.remove(containerId);
+                }
             }
         }
     }
@@ -835,7 +847,7 @@
         while (op != null) {
             switch (op.cmd) {
                 case OP_ADD:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
@@ -845,29 +857,30 @@
                             if (f == null || old.mContainerId == f.mContainerId) {
                                 if (old == f) {
                                     f = null;
+                                    lastInFragments.remove(old.mContainerId);
                                 } else {
-                                    setFirstOut(firstOutFragments, old);
+                                    setFirstOut(firstOutFragments, lastInFragments, old);
                                 }
                             }
                         }
                     }
-                    setLastIn(lastInFragments, f);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 }
                 case OP_REMOVE:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_HIDE:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_SHOW:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_DETACH:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_ATTACH:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
             }
 
@@ -889,38 +902,38 @@
         if (!mManager.mContainer.onHasView()) {
             return; // nothing to see, so no transitions
         }
-        Op op = mHead;
+        Op op = mTail;
         while (op != null) {
             switch (op.cmd) {
                 case OP_ADD:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_REPLACE:
                     if (op.removed != null) {
                         for (int i = op.removed.size() - 1; i >= 0; i--) {
-                            setLastIn(lastInFragments, op.removed.get(i));
+                            setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
                         }
                     }
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_REMOVE:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_HIDE:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_SHOW:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_DETACH:
-                    setLastIn(lastInFragments, op.fragment);
+                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
                     break;
                 case OP_ATTACH:
-                    setFirstOut(firstOutFragments, op.fragment);
+                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
                     break;
             }
 
-            op = op.next;
+            op = op.prev;
         }
     }
 
@@ -957,6 +970,7 @@
      */
     private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
             SparseArray<Fragment> lastInFragments, boolean isBack) {
+        ensureFragmentsAreInitialized(lastInFragments);
         TransitionState state = new TransitionState();
 
         // Adding a non-existent target view makes sure that the transitions don't target
@@ -982,6 +996,20 @@
         return state;
     }
 
+    /**
+     * Ensure that fragments that are entering are at least at the CREATED state
+     * so that they may load Transitions using TransitionInflater.
+     */
+    private void ensureFragmentsAreInitialized(SparseArray<Fragment> lastInFragments) {
+        final int count = lastInFragments.size();
+        for (int i = 0; i < count; i++) {
+            final Fragment fragment = lastInFragments.valueAt(i);
+            if (fragment.mState < Fragment.CREATED) {
+                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
+            }
+        }
+    }
+
     private static Transition cloneTransition(Transition transition) {
         if (transition != null) {
             transition = transition.clone();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index bc7c3d0..c661107 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -129,7 +129,7 @@
     /**
      * Map from package name, to preference name, to cached preferences.
      */
-    private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
+    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefs;
 
     final ActivityThread mMainThread;
     final LoadedApk mPackageInfo;
@@ -327,34 +327,39 @@
 
     @Override
     public SharedPreferences getSharedPreferences(String name, int mode) {
+        // At least one application in the world actually passes in a null
+        // name.  This happened to work because when we generated the file name
+        // we would stringify it to "null.xml".  Nice.
+        if (mPackageInfo.getApplicationInfo().targetSdkVersion <
+                Build.VERSION_CODES.KITKAT) {
+            if (name == null) {
+                name = "null";
+            }
+        }
+
+        final File file = getSharedPrefsFile(name);
+        return getSharedPreferences(file, mode);
+    }
+
+    @Override
+    public SharedPreferences getSharedPreferences(File file, int mode) {
         SharedPreferencesImpl sp;
         synchronized (ContextImpl.class) {
             if (sSharedPrefs == null) {
-                sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
+                sSharedPrefs = new ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>>();
             }
 
             final String packageName = getPackageName();
-            ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
+            ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
             if (packagePrefs == null) {
-                packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
+                packagePrefs = new ArrayMap<File, SharedPreferencesImpl>();
                 sSharedPrefs.put(packageName, packagePrefs);
             }
 
-            // At least one application in the world actually passes in a null
-            // name.  This happened to work because when we generated the file name
-            // we would stringify it to "null.xml".  Nice.
-            if (mPackageInfo.getApplicationInfo().targetSdkVersion <
-                    Build.VERSION_CODES.KITKAT) {
-                if (name == null) {
-                    name = "null";
-                }
-            }
-
-            sp = packagePrefs.get(name);
+            sp = packagePrefs.get(file);
             if (sp == null) {
-                File prefsFile = getSharedPrefsFile(name);
-                sp = new SharedPreferencesImpl(prefsFile, mode);
-                packagePrefs.put(name, sp);
+                sp = new SharedPreferencesImpl(file, mode);
+                packagePrefs.put(file, sp);
                 return sp;
             }
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 74634a9..6c0c3e8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
@@ -45,7 +46,6 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
@@ -58,7 +58,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Constructor;
-import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -2969,7 +2968,6 @@
             Bitmap profileBadge = getProfileBadge();
 
             contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE);
-            contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE);
             contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE);
 
             if (profileBadge != null) {
@@ -2986,38 +2984,35 @@
             return false;
         }
 
-        private void shrinkLine3Text(RemoteViews contentView) {
-            float subTextSize = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_subtext_size);
-            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize);
-        }
-
-        private void unshrinkLine3Text(RemoteViews contentView) {
-            float regularTextSize = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.notification_text_size);
-            contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize);
-        }
-
         private void resetStandardTemplate(RemoteViews contentView) {
-            removeLargeIconBackground(contentView);
-            contentView.setViewPadding(R.id.icon, 0, 0, 0, 0);
-            contentView.setImageViewResource(R.id.icon, 0);
-            contentView.setInt(R.id.icon, "setBackgroundResource", 0);
+            resetNotificationHeader(contentView);
+            resetContentMargins(contentView);
             contentView.setViewVisibility(R.id.right_icon, View.GONE);
-            contentView.setInt(R.id.right_icon, "setBackgroundResource", 0);
-            contentView.setImageViewResource(R.id.right_icon, 0);
-            contentView.setImageViewResource(R.id.icon, 0);
             contentView.setTextViewText(R.id.title, null);
             contentView.setTextViewText(R.id.text, null);
-            unshrinkLine3Text(contentView);
-            contentView.setTextViewText(R.id.text2, null);
-            contentView.setViewVisibility(R.id.text2, View.GONE);
-            contentView.setViewVisibility(R.id.info, View.GONE);
-            contentView.setViewVisibility(R.id.time, View.GONE);
             contentView.setViewVisibility(R.id.line3, View.GONE);
-            contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
+            contentView.setViewVisibility(R.id.text_line_1, View.GONE);
             contentView.setViewVisibility(R.id.progress, View.GONE);
+        }
+
+        /**
+         * Resets the notification header to its original state
+         */
+        private void resetNotificationHeader(RemoteViews contentView) {
+            contentView.setImageViewResource(R.id.icon, 0);
+            contentView.setTextViewText(R.id.app_name_text, null);
             contentView.setViewVisibility(R.id.chronometer, View.GONE);
+            contentView.setViewVisibility(R.id.header_sub_text, View.GONE);
+            contentView.setViewVisibility(R.id.header_content_info, View.GONE);
+            contentView.setViewVisibility(R.id.number_of_children, View.GONE);
+            contentView.setViewVisibility(R.id.sub_text_divider, View.GONE);
+            contentView.setViewVisibility(R.id.content_info_divider, View.GONE);
+            contentView.setViewVisibility(R.id.time_divider, View.GONE);
+        }
+
+        private void resetContentMargins(RemoteViews contentView) {
+            contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+            contentView.setViewLayoutMarginEnd(R.id.line3, 0);
         }
 
         private RemoteViews applyStandardTemplate(int resId) {
@@ -3033,95 +3028,118 @@
             resetStandardTemplate(contentView);
 
             boolean showLine3 = false;
-            boolean showLine2 = false;
-            boolean contentTextInLine2 = false;
             final Bundle ex = mN.extras;
 
-            if (mN.mLargeIcon != null) {
-                contentView.setImageViewIcon(R.id.icon, mN.mLargeIcon);
-                processLargeLegacyIcon(mN.mLargeIcon, contentView);
-                contentView.setImageViewIcon(R.id.right_icon, mN.mSmallIcon);
-                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
-                processSmallRightIcon(mN.mSmallIcon, contentView);
-            } else { // small icon at left
-                contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
-                contentView.setViewVisibility(R.id.icon, View.VISIBLE);
-                processSmallIconAsLarge(mN.mSmallIcon, contentView);
-            }
+            bindNotificationHeader(contentView);
+            bindLargeIcon(contentView);
             if (ex.getCharSequence(EXTRA_TITLE) != null) {
                 contentView.setTextViewText(R.id.title,
                         processLegacyText(ex.getCharSequence(EXTRA_TITLE)));
             }
+            boolean showProgress = handleProgressBar(hasProgress, contentView, ex);
             if (ex.getCharSequence(EXTRA_TEXT) != null) {
-                contentView.setTextViewText(R.id.text,
+                contentView.setTextViewText(showProgress ? R.id.text_line_1 : R.id.text,
                         processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
+                if (showProgress) {
+                    contentView.setViewVisibility(R.id.text_line_1, View.VISIBLE);
+                }
+                showLine3 = !showProgress;
+            }
+            // We want to add badge to first line of text.
+            if (addProfileBadge(contentView, R.id.profile_badge_line3)) {
                 showLine3 = true;
             }
-            if (ex.getCharSequence(EXTRA_INFO_TEXT) != null) {
-                contentView.setTextViewText(R.id.info,
-                        processLegacyText(ex.getCharSequence(EXTRA_INFO_TEXT)));
-                contentView.setViewVisibility(R.id.info, View.VISIBLE);
-                showLine3 = true;
+            // Note getStandardView may hide line 3 again.
+            contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
+
+            return contentView;
+        }
+
+        private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) {
+            final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
+            final int progress = ex.getInt(EXTRA_PROGRESS, 0);
+            final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+            if (hasProgress && (max != 0 || ind)) {
+                contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE);
+                contentView.setProgressBar(
+                        R.id.progress, max, progress, ind);
+                contentView.setProgressBackgroundTintList(
+                        R.id.progress, ColorStateList.valueOf(mContext.getColor(
+                                R.color.notification_progress_background_color)));
+                if (mN.color != COLOR_DEFAULT) {
+                    ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
+                    contentView.setProgressTintList(R.id.progress, colorStateList);
+                    contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
+                }
+                return true;
+            } else {
+                contentView.setViewVisibility(R.id.progress, View.GONE);
+                return false;
+            }
+        }
+
+        private void bindLargeIcon(RemoteViews contentView) {
+            if (mN.mLargeIcon != null) {
+                contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+                contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
+                processLargeLegacyIcon(mN.mLargeIcon, contentView);
+                int endMargin = mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_content_picture_margin);
+                contentView.setViewLayoutMarginEnd(R.id.line1, endMargin);
+                contentView.setViewLayoutMarginEnd(R.id.line3, endMargin);
+                contentView.setViewLayoutMarginEnd(R.id.progress, endMargin);
+            }
+        }
+
+        private void bindNotificationHeader(RemoteViews contentView) {
+            bindSmallIcon(contentView);
+            bindChildCountColor(contentView);
+            bindHeaderAppName(contentView);
+            bindHeaderSubText(contentView);
+            bindContentInfo(contentView);
+            bindHeaderChronometerAndTime(contentView);
+            bindExpandButton(contentView);
+        }
+
+        private void bindChildCountColor(RemoteViews contentView) {
+            contentView.setTextColor(R.id.number_of_children, resolveColor());
+        }
+
+        private void bindContentInfo(RemoteViews contentView) {
+            boolean visible = false;
+            if (mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) {
+                contentView.setTextViewText(R.id.header_content_info,
+                        processLegacyText(mN.extras.getCharSequence(EXTRA_INFO_TEXT)));
+                contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+                visible = true;
             } else if (mN.number > 0) {
                 final int tooBig = mContext.getResources().getInteger(
                         R.integer.status_bar_notification_info_maxnum);
                 if (mN.number > tooBig) {
-                    contentView.setTextViewText(R.id.info, processLegacyText(
+                    contentView.setTextViewText(R.id.header_content_info, processLegacyText(
                             mContext.getResources().getString(
                                     R.string.status_bar_notification_info_overflow)));
                 } else {
-                    NumberFormat f = NumberFormat.getIntegerInstance();
-                    contentView.setTextViewText(R.id.info, processLegacyText(f.format(mN.number)));
+                    contentView.setTextViewText(R.id.header_content_info,
+                            processLegacyText(String.valueOf(mN.number)));
                 }
-                contentView.setViewVisibility(R.id.info, View.VISIBLE);
-                showLine3 = true;
-            } else {
-                contentView.setViewVisibility(R.id.info, View.GONE);
+                contentView.setViewVisibility(R.id.header_content_info, View.VISIBLE);
+                visible = true;
             }
-
-            // Need to show three lines?
-            if (ex.getCharSequence(EXTRA_SUB_TEXT) != null) {
-                contentView.setTextViewText(R.id.text,
-                        processLegacyText(ex.getCharSequence(EXTRA_SUB_TEXT)));
-                if (ex.getCharSequence(EXTRA_TEXT) != null) {
-                    contentView.setTextViewText(R.id.text2,
-                            processLegacyText(ex.getCharSequence(EXTRA_TEXT)));
-                    contentView.setViewVisibility(R.id.text2, View.VISIBLE);
-                    showLine2 = true;
-                    contentTextInLine2 = true;
-                } else {
-                    contentView.setViewVisibility(R.id.text2, View.GONE);
-                }
-            } else {
-                contentView.setViewVisibility(R.id.text2, View.GONE);
-                final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
-                final int progress = ex.getInt(EXTRA_PROGRESS, 0);
-                final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
-                if (hasProgress && (max != 0 || ind)) {
-                    contentView.setViewVisibility(R.id.progress, View.VISIBLE);
-                    contentView.setProgressBar(
-                            R.id.progress, max, progress, ind);
-                    contentView.setProgressBackgroundTintList(
-                            R.id.progress, ColorStateList.valueOf(mContext.getColor(
-                                    R.color.notification_progress_background_color)));
-                    if (mN.color != COLOR_DEFAULT) {
-                        ColorStateList colorStateList = ColorStateList.valueOf(mN.color);
-                        contentView.setProgressTintList(R.id.progress, colorStateList);
-                        contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList);
-                    }
-                    showLine2 = true;
-                } else {
-                    contentView.setViewVisibility(R.id.progress, View.GONE);
-                }
+            if (visible) {
+                contentView.setViewVisibility(R.id.content_info_divider, View.VISIBLE);
             }
-            if (showLine2) {
+        }
 
-                // need to shrink all the type to make sure everything fits
-                shrinkLine3Text(contentView);
-            }
+        private void bindExpandButton(RemoteViews contentView) {
+            contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+                    PorterDuff.Mode.SRC_ATOP, -1);
+        }
 
+        private void bindHeaderChronometerAndTime(RemoteViews contentView) {
             if (showsTimeOrChronometer()) {
-                if (ex.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
+                contentView.setViewVisibility(R.id.time_divider, View.VISIBLE);
+                if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) {
                     contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
                     contentView.setLong(R.id.chronometer, "setBase",
                             mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis()));
@@ -3131,26 +3149,42 @@
                     contentView.setLong(R.id.time, "setTime", mN.when);
                 }
             }
+        }
 
-            // Adjust padding depending on line count and font size.
-            contentView.setViewPadding(R.id.line1, 0,
-                    calculateTopPadding(mContext, hasThreeLines(),
-                            mContext.getResources().getConfiguration().fontScale),
-                    0, 0);
-
-            // We want to add badge to first line of text.
-            boolean addedBadge = addProfileBadge(contentView,
-                    contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
-            // If we added the badge to line 3 then we should show line 3.
-            if (addedBadge && !contentTextInLine2) {
-                showLine3 = true;
+        private void bindHeaderSubText(RemoteViews contentView) {
+            CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
+            if (subText == null && mStyle != null && mStyle.mSummaryTextSet
+                    && mStyle.hasSummaryInHeader()) {
+                subText = mStyle.mSummaryText;
             }
+            if (subText != null) {
+                // TODO: Remove the span entirely to only have the string with propper formating.
+                contentView.setTextViewText(R.id.header_sub_text, processLegacyText(subText));
+                contentView.setViewVisibility(R.id.header_sub_text, View.VISIBLE);
+                contentView.setViewVisibility(R.id.sub_text_divider, View.VISIBLE);
+            }
+        }
 
-            // Note getStandardView may hide line 3 again.
-            contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
-            contentView.setViewVisibility(R.id.overflow_divider,
-                    showLine3 ? View.VISIBLE : View.GONE);
-            return contentView;
+        private void bindHeaderAppName(RemoteViews contentView) {
+            PackageManager packageManager = mContext.getPackageManager();
+            ApplicationInfo info = null;
+            try {
+                info = packageManager.getApplicationInfo(mContext.getApplicationInfo().packageName,
+                        0);
+            } catch (final NameNotFoundException e) {
+                return;
+            }
+            CharSequence appName = info != null ? packageManager.getApplicationLabel(info)
+                    : null;
+            if (TextUtils.isEmpty(appName)) {
+                return;
+            }
+            contentView.setTextViewText(R.id.app_name_text, appName);
+        }
+
+        private void bindSmallIcon(RemoteViews contentView) {
+            contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+            processSmallIconColor(mN.mSmallIcon, contentView);
         }
 
         /**
@@ -3161,49 +3195,6 @@
             return mN.when != 0 && mN.extras.getBoolean(EXTRA_SHOW_WHEN);
         }
 
-        /**
-         * Logic to find out whether the notification is going to have three lines in the contracted
-         * layout. This is used to adjust the top padding.
-         *
-         * @return true if the notification is going to have three lines; false if the notification
-         *         is going to have one or two lines
-         */
-        private boolean hasThreeLines() {
-            final CharSequence subText = mN.extras.getCharSequence(EXTRA_SUB_TEXT);
-            final CharSequence text = mN.extras.getCharSequence(EXTRA_TEXT);
-            boolean contentTextInLine2 = subText != null && text != null;
-            boolean hasProgress = mStyle == null || mStyle.hasProgress();
-            // If we have content text in line 2, badge goes into line 2, or line 3 otherwise
-            boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2;
-            boolean hasLine3 = text != null || mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null
-                    || mN.number > 0 || badgeInLine3;
-            final Bundle ex = mN.extras;
-            final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0);
-            final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
-            boolean hasLine2 = (subText != null && text != null) ||
-                    (hasProgress && subText == null && (max != 0 || ind));
-            return hasLine2 && hasLine3;
-        }
-
-        /**
-         * @hide
-         */
-        public static int calculateTopPadding(Context ctx, boolean hasThreeLines,
-                float fontScale) {
-            int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines
-                    ? R.dimen.notification_top_pad_narrow
-                    : R.dimen.notification_top_pad);
-            int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines
-                    ? R.dimen.notification_top_pad_large_text_narrow
-                    : R.dimen.notification_top_pad_large_text);
-            float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f)
-                    / (LARGE_TEXT_SCALE - 1f);
-
-            // Linearly interpolate the padding between large and normal with the font scale ranging
-            // from 1f to LARGE_TEXT_SCALE
-            return Math.round((1 - largeFactor) * padding + largeFactor * largePadding);
-        }
-
         private void resetStandardTemplateWithActions(RemoteViews big) {
             big.setViewVisibility(R.id.actions, View.GONE);
             big.setViewVisibility(R.id.action_divider, View.GONE);
@@ -3250,18 +3241,46 @@
          * Construct a RemoteViews for the final big notification layout.
          */
         public RemoteViews makeBigContentView() {
+            RemoteViews result = null;
             if (mN.bigContentView != null) {
                 return mN.bigContentView;
             } else if (mStyle != null) {
-                final RemoteViews styleView = mStyle.makeBigContentView();
-                if (styleView != null) {
-                    return styleView;
-                }
+                result = mStyle.makeBigContentView();
             } else if (mActions.size() == 0) {
                 return null;
             }
+            if (result == null) {
+                result = applyStandardTemplateWithActions(getBigBaseLayoutResource());
+            } else {
+                hideLine1Text(result);
+            }
+            adaptNotificationHeaderForBigContentView(result);
+            return result;
+        }
 
-            return applyStandardTemplateWithActions(getBigBaseLayoutResource());
+        /**
+         * Construct a RemoteViews for the final notification header only
+         *
+         * @hide
+         */
+        public RemoteViews makeNotificationHeader() {
+            RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(),
+                    R.layout.notification_template_header);
+            resetNotificationHeader(header);
+            bindNotificationHeader(header);
+            return header;
+        }
+
+        private void hideLine1Text(RemoteViews result) {
+            result.setViewVisibility(R.id.text_line_1, View.GONE);
+        }
+
+        private void adaptNotificationHeaderForBigContentView(RemoteViews result) {
+            // We have to set the collapse button instead
+            result.setImageViewResource(R.id.expand_button, R.drawable.ic_arrow_up_14dp);
+            // Apply the color again
+            result.setDrawableParameters(R.id.expand_button, false, -1, resolveColor(),
+                    PorterDuff.Mode.SRC_ATOP, -1);
         }
 
         /**
@@ -3330,84 +3349,27 @@
         }
 
         /**
-         * Apply any necessary background to smallIcons being used in the largeIcon spot.
+         * Apply any necessariy colors to the small icon
          */
-        private void processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView) {
-            if (!isLegacy()) {
-                contentView.setDrawableParameters(R.id.icon, false, -1,
-                        0xFFFFFFFF,
+        private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) {
+            if (!isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon)) {
+                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
                         PorterDuff.Mode.SRC_ATOP, -1);
-                applyLargeIconBackground(contentView);
-            } else {
-                if (getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
-                    applyLargeIconBackground(contentView);
-                }
             }
         }
 
         /**
-         * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is,
+         * Make the largeIcon dark if it's a fake smallIcon (that is,
          * if it's grayscale).
          */
         // TODO: also check bounds, transparency, that sort of thing.
         private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) {
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
-                applyLargeIconBackground(contentView);
-            } else {
-                removeLargeIconBackground(contentView);
-            }
-        }
-
-        /**
-         * Add a colored circle behind the largeIcon slot.
-         */
-        private void applyLargeIconBackground(RemoteViews contentView) {
-            contentView.setInt(R.id.icon, "setBackgroundResource",
-                    R.drawable.notification_icon_legacy_bg);
-
-            contentView.setDrawableParameters(
-                    R.id.icon,
-                    true,
-                    -1,
-                    resolveColor(),
-                    PorterDuff.Mode.SRC_ATOP,
-                    -1);
-
-            int padding = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_large_icon_circle_padding);
-            contentView.setViewPadding(R.id.icon, padding, padding, padding, padding);
-        }
-
-        private void removeLargeIconBackground(RemoteViews contentView) {
-            contentView.setInt(R.id.icon, "setBackgroundResource", 0);
-        }
-
-        /**
-         * Recolor small icons when used in the R.id.right_icon slot.
-         */
-        private void processSmallRightIcon(Icon smallIcon, RemoteViews contentView) {
-            if (!isLegacy()) {
-                contentView.setDrawableParameters(R.id.right_icon, false, -1,
-                        0xFFFFFFFF,
+                // resolve color will fall back to the default when legacy
+                contentView.setDrawableParameters(R.id.icon, false, -1, resolveColor(),
                         PorterDuff.Mode.SRC_ATOP, -1);
             }
-            final boolean gray = isLegacy()
-                    && smallIcon.getType() == Icon.TYPE_RESOURCE
-                    && getColorUtil().isGrayscaleIcon(mContext, smallIcon.getResId());
-            if (!isLegacy() || gray) {
-                contentView.setInt(R.id.right_icon,
-                        "setBackgroundResource",
-                        R.drawable.notification_icon_legacy_bg);
-
-                contentView.setDrawableParameters(
-                        R.id.right_icon,
-                        true,
-                        -1,
-                        resolveColor(),
-                        PorterDuff.Mode.SRC_ATOP,
-                        -1);
-            }
         }
 
         private void sanitizeColor() {
@@ -3416,9 +3378,9 @@
             }
         }
 
-        private int resolveColor() {
+        int resolveColor() {
             if (mN.color == COLOR_DEFAULT) {
-                return mContext.getColor(R.color.notification_icon_bg_color);
+                return mContext.getColor(R.color.notification_icon_default_color);
             }
             return mN.color;
         }
@@ -3622,20 +3584,9 @@
                 contentView.setViewVisibility(R.id.line1, View.VISIBLE);
             }
 
-            // The last line defaults to the subtext, but can be replaced by mSummaryText
-            final CharSequence overflowText =
-                    mSummaryTextSet ? mSummaryText
-                                    : mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT);
-            if (overflowText != null) {
-                contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText));
-                contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE);
-                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
-            } else {
-                // Clear text in case we use the line to show the profile badge.
-                contentView.setTextViewText(R.id.text, "");
-                contentView.setViewVisibility(R.id.overflow_divider, View.GONE);
-                contentView.setViewVisibility(R.id.line3, View.GONE);
-            }
+            // Clear text in case we use the line to show the profile badge.
+            contentView.setTextViewText(com.android.internal.R.id.text, "");
+            contentView.setViewVisibility(com.android.internal.R.id.line3, View.GONE);
 
             return contentView;
         }
@@ -3666,19 +3617,6 @@
         }
 
         /**
-         * Changes the padding of the first line such that the big and small content view have the
-         * same top padding.
-         *
-         * @hide
-         */
-        protected void applyTopPadding(RemoteViews contentView) {
-            int topPadding = Builder.calculateTopPadding(mBuilder.mContext,
-                    mBuilder.hasThreeLines(),
-                    mBuilder.mContext.getResources().getConfiguration().fontScale);
-            contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0);
-        }
-
-        /**
          * Apply any style-specific extras to this notification before shipping it out.
          * @hide
          */
@@ -3739,6 +3677,14 @@
         protected boolean hasProgress() {
             return true;
         }
+
+        /**
+         * @hide
+         * @return Whether we should put the summary be put into the notification header
+         */
+        public boolean hasSummaryInHeader() {
+            return true;
+        }
     }
 
     /**
@@ -3846,6 +3792,16 @@
             }
 
             RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
+            if (mSummaryTextSet) {
+                contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText));
+                contentView.setViewVisibility(R.id.line3, View.VISIBLE);
+            }
+            int imageMinHeight = mBuilder.mContext.getResources().getDimensionPixelSize(
+                    R.dimen.notification_big_picture_content_min_height_with_picture);
+            // We need to make space for the right image, so we're enforcing a minheight if there
+            // is a picture.
+            int minHeight = (mBuilder.mN.mLargeIcon == null) ? 0 : imageMinHeight;
+            contentView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight);
 
             if (mBigLargeIconSet) {
                 mBuilder.mN.mLargeIcon = oldLargeIcon;
@@ -3853,12 +3809,7 @@
 
             contentView.setImageViewBitmap(R.id.big_picture, mPicture);
 
-            applyTopPadding(contentView);
-
-            boolean twoTextLines = mBuilder.getAllExtras().getCharSequence(EXTRA_SUB_TEXT) != null
-                    && mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT) != null;
-            mBuilder.addProfileBadge(contentView,
-                    twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3);
+            mBuilder.addProfileBadge(contentView, R.id.profile_badge_line3);
             return contentView;
         }
 
@@ -3887,6 +3838,14 @@
             }
             mPicture = extras.getParcelable(EXTRA_PICTURE);
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean hasSummaryInHeader() {
+            return false;
+        }
     }
 
     /**
@@ -3983,14 +3942,11 @@
             contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText));
             contentView.setViewVisibility(R.id.big_text, View.VISIBLE);
             contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines());
-            contentView.setViewVisibility(R.id.text2, View.GONE);
-
-            applyTopPadding(contentView);
-
-            mBuilder.shrinkLine3Text(contentView);
 
             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
 
+            contentView.setBoolean(R.id.big_text, "setHasImage", mBuilder.mN.mLargeIcon != null);
+
             return contentView;
         }
 
@@ -4005,11 +3961,6 @@
             if (hasSummary) {
                 lineCount -= LINES_CONSUMED_BY_SUMMARY;
             }
-
-            // If we have less top padding at the top, we can fit less lines.
-            if (!mBuilder.hasThreeLines()) {
-                lineCount--;
-            }
             return lineCount;
         }
     }
@@ -4105,8 +4056,6 @@
 
             mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText);
 
-            contentView.setViewVisibility(R.id.text2, View.GONE);
-
             int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
                     R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
 
@@ -4120,6 +4069,9 @@
             final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize(
                     R.dimen.notification_subtext_size);
             int i=0;
+            final float density = mBuilder.mContext.getResources().getDisplayMetrics().density;
+            int topPadding = (int) (5 * density);
+            int bottomPadding = (int) (13 * density);
             while (i < mTexts.size() && i < rowIds.length) {
                 CharSequence str = mTexts.get(i);
                 if (str != null && !str.equals("")) {
@@ -4129,24 +4081,29 @@
                         contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX,
                                 subTextSize);
                     }
+                    contentView.setViewPadding(rowIds[i], 0, topPadding, 0,
+                            i == rowIds.length - 1 || i == mTexts.size() - 1 ? bottomPadding : 0);
                 }
                 i++;
             }
-
-            contentView.setViewVisibility(R.id.inbox_end_pad,
-                    mTexts.size() > 0 ? View.VISIBLE : View.GONE);
-
-            contentView.setViewVisibility(R.id.inbox_more,
-                    mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
-
-            applyTopPadding(contentView);
-
-            mBuilder.shrinkLine3Text(contentView);
-
             mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template);
 
+            handleInboxImageMargin(contentView, rowIds[0]);
+
             return contentView;
         }
+
+        private void handleInboxImageMargin(RemoteViews contentView, int id) {
+            final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0);
+            final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE);
+            boolean hasProgress = max != 0 || ind;
+            int endMargin = 0;
+            if (mTexts.size() > 0 && mBuilder.mN.mLargeIcon != null && !hasProgress) {
+                endMargin = mBuilder.mContext.getResources().getDimensionPixelSize(
+                        R.dimen.notification_content_picture_margin);
+            }
+            contentView.setViewLayoutMarginEnd(id, endMargin);
+        }
     }
 
     /**
@@ -4278,14 +4235,13 @@
             }
         }
 
-        private RemoteViews generateMediaActionButton(Action action) {
+        private RemoteViews generateMediaActionButton(Action action, int color) {
             final boolean tombstone = (action.actionIntent == null);
             RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
                     R.layout.notification_material_media_action);
             button.setImageViewIcon(R.id.action0, action.getIcon());
-            button.setDrawableParameters(R.id.action0, false, -1,
-                    0xFFFFFFFF,
-                    PorterDuff.Mode.SRC_ATOP, -1);
+            button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
+                    -1);
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
@@ -4311,67 +4267,40 @@
                     }
 
                     final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]);
-                    final RemoteViews button = generateMediaActionButton(action);
+                    final RemoteViews button = generateMediaActionButton(action,
+                            mBuilder.resolveColor());
                     view.addView(com.android.internal.R.id.media_actions, button);
                 }
             }
-            styleText(view);
-            hideRightIcon(view);
+            handleImage(view  /* addPaddingToMainColumn */);
             return view;
         }
 
         private RemoteViews makeMediaBigContentView() {
             final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
-            RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount),
-                    false /* hasProgress */);
+            RemoteViews big = mBuilder.applyStandardTemplate(
+                    R.layout.notification_template_material_big_media,
+                    false);
 
             if (actionCount > 0) {
                 big.removeAllViews(com.android.internal.R.id.media_actions);
                 for (int i = 0; i < actionCount; i++) {
-                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i));
+                    final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i),
+                            mBuilder.resolveColor());
                     big.addView(com.android.internal.R.id.media_actions, button);
                 }
             }
-            styleText(big);
-            hideRightIcon(big);
-            applyTopPadding(big);
-            big.setViewVisibility(android.R.id.progress, View.GONE);
+            handleImage(big);
             return big;
         }
 
-        private int getBigLayoutResource(int actionCount) {
-            if (actionCount <= 3) {
-                return R.layout.notification_template_material_big_media_narrow;
-            } else {
-                return R.layout.notification_template_material_big_media;
+        private void handleImage(RemoteViews contentView) {
+            if (mBuilder.mN.mLargeIcon != null) {
+                contentView.setViewLayoutMarginEnd(R.id.line1, 0);
+                contentView.setViewLayoutMarginEnd(R.id.line3, 0);
             }
         }
 
-        private void hideRightIcon(RemoteViews contentView) {
-            contentView.setViewVisibility(R.id.right_icon, View.GONE);
-        }
-
-        /**
-         * Applies the special text colors for media notifications to all text views.
-         */
-        private void styleText(RemoteViews contentView) {
-            int primaryColor = mBuilder.mContext.getColor(
-                    R.color.notification_media_primary_color);
-            int secondaryColor = mBuilder.mContext.getColor(
-                    R.color.notification_media_secondary_color);
-            contentView.setTextColor(R.id.title, primaryColor);
-            if (mBuilder.showsTimeOrChronometer()) {
-                if (mBuilder.getAllExtras().getBoolean(EXTRA_SHOW_CHRONOMETER)) {
-                    contentView.setTextColor(R.id.chronometer, secondaryColor);
-                } else {
-                    contentView.setTextColor(R.id.time, secondaryColor);
-                }
-            }
-            contentView.setTextColor(R.id.text2, secondaryColor);
-            contentView.setTextColor(R.id.text, secondaryColor);
-            contentView.setTextColor(R.id.info, secondaryColor);
-        }
-
         /**
          * @hide
          */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a0102b6..b73fa50 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -625,8 +625,30 @@
      * @see #MODE_WORLD_READABLE
      * @see #MODE_WORLD_WRITEABLE
      */
-    public abstract SharedPreferences getSharedPreferences(String name,
-            int mode);
+    public abstract SharedPreferences getSharedPreferences(String name, int mode);
+
+    /**
+     * Retrieve and hold the contents of the preferences file, returning
+     * a SharedPreferences through which you can retrieve and modify its
+     * values.  Only one instance of the SharedPreferences object is returned
+     * to any callers for the same name, meaning they will see each other's
+     * edits as soon as they are made.
+     *
+     * @param file Desired preferences file. If a preferences file by this name
+     * does not exist, it will be created when you retrieve an
+     * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+     * @param mode Operating mode.  Use 0 or {@link #MODE_PRIVATE} for the
+     * default operation, {@link #MODE_WORLD_READABLE}
+     * and {@link #MODE_WORLD_WRITEABLE} to control permissions.
+     *
+     * @return The single {@link SharedPreferences} instance that can be used
+     *         to retrieve and modify the preference values.
+     *
+     * @see #MODE_PRIVATE
+     * @see #MODE_WORLD_READABLE
+     * @see #MODE_WORLD_WRITEABLE
+     */
+    public abstract SharedPreferences getSharedPreferences(File file, int mode);
 
     /**
      * Open a private file associated with this Context's application package
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index bec1b37..a345aae 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -172,6 +172,11 @@
     }
 
     @Override
+    public SharedPreferences getSharedPreferences(File file, int mode) {
+        return mBase.getSharedPreferences(file, mode);
+    }
+
+    @Override
     public FileInputStream openFileInput(String name)
         throws FileNotFoundException {
         return mBase.openFileInput(name);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 4a7cbc7..e25f1d7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2877,6 +2877,14 @@
             "android.intent.action.USER_SWITCHED";
 
     /**
+     * Broadcast Action: Sent when the credential-encrypted private storage has
+     * become unlocked for the target user. This is only sent to registered
+     * receivers, not manifest receivers.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
+
+    /**
      * Broadcast sent to the system when a user's information changes. Carries an extra
      * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
      * This is only sent to registered receivers, not manifest receivers. It is sent to all users.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1996e0f..65e5945 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -487,6 +487,13 @@
     public static final int PRIVATE_FLAG_AUTOPLAY = 1 << 7;
 
     /**
+     * When set, at least one component inside this application is encryption aware.
+     *
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE = 1 << 8;
+
+    /**
      * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
      * {@hide}
      */
@@ -1054,6 +1061,11 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ENCRYPTION_AWARE) != 0;
     }
 
+    /** @hide */
+    public boolean isPartiallyEncryptionAware() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE) != 0;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6fe1efd..b9a42eb 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -59,7 +59,7 @@
  *  {@hide}
  */
 interface IPackageManager {
-    boolean isPackageFrozen(String packageName);
+    void checkPackageStartable(String packageName, int userId);
     boolean isPackageAvailable(String packageName, int userId);
     PackageInfo getPackageInfo(String packageName, int flags, int userId);
     int getPackageUid(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 838da37..ecb1850 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -84,9 +84,10 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import java.util.jar.StrictJarFile;
 import java.util.zip.ZipEntry;
 
+import android.util.jar.StrictJarFile;
+
 /**
  * Parser for package files (APKs) on disk. This supports apps packaged either
  * as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
@@ -3262,6 +3263,11 @@
                     owner.applicationInfo.isEncryptionAware());
         }
 
+        if (a.info.encryptionAware) {
+            owner.applicationInfo.privateFlags |=
+                    ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+        }
+
         sa.recycle();
 
         if (receiver && (owner.applicationInfo.privateFlags
@@ -3663,6 +3669,10 @@
         p.info.encryptionAware = sa.getBoolean(
                 R.styleable.AndroidManifestProvider_encryptionAware,
                 owner.applicationInfo.isEncryptionAware());
+        if (p.info.encryptionAware) {
+            owner.applicationInfo.privateFlags |=
+                    ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+        }
 
         sa.recycle();
 
@@ -3947,6 +3957,10 @@
         s.info.encryptionAware = sa.getBoolean(
                 R.styleable.AndroidManifestService_encryptionAware,
                 owner.applicationInfo.isEncryptionAware());
+        if (s.info.encryptionAware) {
+            owner.applicationInfo.privateFlags |=
+                    ApplicationInfo.PRIVATE_FLAG_PARTIALLY_ENCRYPTION_AWARE;
+        }
 
         sa.recycle();
 
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java
index 6a392dd..014e73f 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/core/java/android/hardware/input/InputManagerInternal.java
@@ -16,8 +16,11 @@
 
 package android.hardware.input;
 
+import android.annotation.Nullable;
 import android.hardware.display.DisplayViewport;
 import android.view.InputEvent;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 
 /**
  * Input manager local system service interface.
@@ -39,4 +42,14 @@
      * watching for wake events.
      */
     public abstract void setInteractive(boolean interactive);
+
+    /**
+     * Notifies that InputMethodManagerService switched the current input method subtype.
+     *
+     * @param userId user id that indicates who is using the specified input method and subtype.
+     * @param inputMethodInfo {@code null} when no input method is selected.
+     * @param subtype {@code null} when {@code inputMethodInfo} does has no subtype.
+     */
+    public abstract void onInputMethodSubtypeChanged(int userId,
+            @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype);
 }
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 521df28..63f39c5 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -181,9 +181,12 @@
     private static final String LOG_TAG = "AsyncTask";
 
     private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
-    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
+    // We want at least 2 threads and at most 4 threads in the core pool,
+    // preferring to have 1 less than the CPU count to avoid saturating
+    // the CPU with background work
+    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
     private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
-    private static final int KEEP_ALIVE = 1;
+    private static final int KEEP_ALIVE_SECONDS = 30;
 
     private static final ThreadFactory sThreadFactory = new ThreadFactory() {
         private final AtomicInteger mCount = new AtomicInteger(1);
@@ -199,9 +202,15 @@
     /**
      * An {@link Executor} that can be used to execute tasks in parallel.
      */
-    public static final Executor THREAD_POOL_EXECUTOR
-            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
-                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
+    public static final Executor THREAD_POOL_EXECUTOR;
+
+    static {
+        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
+                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
+                sPoolWorkQueue, sThreadFactory);
+        threadPoolExecutor.allowCoreThreadTimeOut(true);
+        THREAD_POOL_EXECUTOR = threadPoolExecutor;
+    }
 
     /**
      * An {@link Executor} that executes tasks one at a time in serial
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0a149bb..f26693c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -929,9 +929,6 @@
             if (guest != null) {
                 Settings.Secure.putStringForUser(context.getContentResolver(),
                         Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
-
-                mService.setUserRestriction(DISALLOW_SMS, true, guest.id);
-                mService.setUserRestriction(DISALLOW_INSTALL_UNKNOWN_SOURCES, true, guest.id);
             }
         } catch (RemoteException re) {
             Log.w(TAG, "Could not create a user", re);
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 8ae899f..d53bb0d 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -67,7 +67,7 @@
  * @see DocumentsProvider
  */
 public final class DocumentsContract {
-    private static final String TAG = "Documents";
+    private static final String TAG = "DocumentsContract";
 
     // content://com.example/root/
     // content://com.example/root/sdcard/
@@ -591,6 +591,12 @@
      */
     public static final String EXTRA_ERROR = "error";
 
+    /**
+     * Optional result (I'm thinking boolean) answer to a question.
+     * {@hide}
+     */
+    public static final String EXTRA_RESULT = "result";
+
     /** {@hide} */
     public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
     /** {@hide} */
@@ -601,6 +607,8 @@
     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
     /** {@hide} */
     public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
+    /** {@hide} */
+    public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument";
 
     /** {@hide} */
     public static final String EXTRA_URI = "uri";
@@ -1025,6 +1033,24 @@
         return out.getParcelable(DocumentsContract.EXTRA_URI);
     }
 
+    /** {@hide} */
+    public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri,
+            Uri childDocumentUri) throws RemoteException {
+
+        final Bundle in = new Bundle();
+        in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri);
+        in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri);
+
+        final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in);
+        if (out == null) {
+            throw new RemoteException("Failed to get a reponse from isChildDocument query.");
+        }
+        if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) {
+            throw new RemoteException("Response did not include result field..");
+        }
+        return out.getBoolean(DocumentsContract.EXTRA_RESULT);
+    }
+
     /**
      * Change the display name of an existing document.
      * <p>
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index f01073b..e25ba35 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,11 +16,12 @@
 
 package android.provider;
 
+import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
 import static android.provider.DocumentsContract.buildDocumentUri;
 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
 import static android.provider.DocumentsContract.buildTreeDocumentUri;
@@ -688,6 +689,16 @@
             return super.call(method, arg, extras);
         }
 
+        try {
+            return callUnchecked(method, arg, extras);
+        } catch (FileNotFoundException e) {
+            throw new IllegalStateException("Failed call " + method, e);
+        }
+    }
+
+    private Bundle callUnchecked(String method, String arg, Bundle extras)
+            throws FileNotFoundException {
+
         final Context context = getContext();
         final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
         final String authority = documentUri.getAuthority();
@@ -697,109 +708,120 @@
             throw new SecurityException(
                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
         }
-        enforceTree(documentUri);
 
         final Bundle out = new Bundle();
-        try {
-            if (METHOD_CREATE_DOCUMENT.equals(method)) {
-                enforceWritePermissionInner(documentUri, getCallingPackage(), null);
 
-                final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
-                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
-                final String newDocumentId = createDocument(documentId, mimeType, displayName);
+        // If the URI is a tree URI performs some validation.
+        enforceTree(documentUri);
 
-                // No need to issue new grants here, since caller either has
-                // manage permission or a prefix grant. We might generate a
-                // tree style URI if that's how they called us.
+        if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
+            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+
+            final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String childAuthority = childUri.getAuthority();
+            final String childId = DocumentsContract.getDocumentId(childUri);
+
+            out.putBoolean(
+                    DocumentsContract.EXTRA_RESULT,
+                    mAuthority.equals(childAuthority)
+                            && isChildDocument(documentId, childId));
+
+        } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+
+            final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+            final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+            final String newDocumentId = createDocument(documentId, mimeType, displayName);
+
+            // No need to issue new grants here, since caller either has
+            // manage permission or a prefix grant. We might generate a
+            // tree style URI if that's how they called us.
+            final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                    newDocumentId);
+            out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+
+        } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+
+            final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+            final String newDocumentId = renameDocument(documentId, displayName);
+
+            if (newDocumentId != null) {
                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
                         newDocumentId);
+
+                // If caller came in with a narrow grant, issue them a
+                // narrow grant for the newly renamed document.
+                if (!isTreeUri(newDocumentUri)) {
+                    final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+                            documentUri);
+                    context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+                }
+
                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
 
-            } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
-                enforceWritePermissionInner(documentUri, getCallingPackage(), null);
-
-                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
-                final String newDocumentId = renameDocument(documentId, displayName);
-
-                if (newDocumentId != null) {
-                    final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
-                            newDocumentId);
-
-                    // If caller came in with a narrow grant, issue them a
-                    // narrow grant for the newly renamed document.
-                    if (!isTreeUri(newDocumentUri)) {
-                        final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
-                                documentUri);
-                        context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
-                    }
-
-                    out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
-
-                    // Original document no longer exists, clean up any grants
-                    revokeDocumentPermission(documentId);
-                }
-
-            } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
-                enforceWritePermissionInner(documentUri, getCallingPackage(), null);
-                deleteDocument(documentId);
-
-                // Document no longer exists, clean up any grants
-                revokeDocumentPermission(documentId);
-
-            } else if (METHOD_COPY_DOCUMENT.equals(method)) {
-                final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
-                final String targetId = DocumentsContract.getDocumentId(targetUri);
-
-                enforceReadPermissionInner(documentUri, getCallingPackage(), null);
-                enforceWritePermissionInner(targetUri, getCallingPackage(), null);
-
-                final String newDocumentId = copyDocument(documentId, targetId);
-
-                if (newDocumentId != null) {
-                    final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
-                            newDocumentId);
-
-                    if (!isTreeUri(newDocumentUri)) {
-                        final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
-                                documentUri);
-                        context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
-                    }
-
-                    out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
-                }
-
-            } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
-                final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
-                final String targetId = DocumentsContract.getDocumentId(targetUri);
-
-                enforceReadPermissionInner(documentUri, getCallingPackage(), null);
-                enforceWritePermissionInner(targetUri, getCallingPackage(), null);
-
-                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
-                final String newDocumentId = moveDocument(documentId, targetId);
-
-                if (newDocumentId != null) {
-                    final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
-                            newDocumentId);
-
-                    if (!isTreeUri(newDocumentUri)) {
-                        final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
-                                documentUri);
-                        context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
-                    }
-
-                    out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
-                }
-
                 // Original document no longer exists, clean up any grants
                 revokeDocumentPermission(documentId);
-
-            } else {
-                throw new UnsupportedOperationException("Method not supported " + method);
             }
-        } catch (FileNotFoundException e) {
-            throw new IllegalStateException("Failed call " + method, e);
+
+        } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            deleteDocument(documentId);
+
+            // Document no longer exists, clean up any grants
+            revokeDocumentPermission(documentId);
+
+        } else if (METHOD_COPY_DOCUMENT.equals(method)) {
+            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+            final String newDocumentId = copyDocument(documentId, targetId);
+
+            if (newDocumentId != null) {
+                final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                        newDocumentId);
+
+                if (!isTreeUri(newDocumentUri)) {
+                    final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+                            documentUri);
+                    context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+                }
+
+                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+            }
+
+        } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
+            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+            final String newDocumentId = moveDocument(documentId, targetId);
+
+            if (newDocumentId != null) {
+                final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                        newDocumentId);
+
+                if (!isTreeUri(newDocumentUri)) {
+                    final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+                            documentUri);
+                    context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+                }
+
+                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+            }
+
+            // Original document no longer exists, clean up any grants
+            revokeDocumentPermission(documentId);
+
+        } else {
+            throw new UnsupportedOperationException("Method not supported " + method);
         }
+
         return out;
     }
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f560f8a..c92382a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4420,8 +4420,19 @@
          * to receive changes in this value.
          */
         public static final String LOCATION_MODE = "location_mode";
+        /**
+         * Stores the previous location mode when {@link #LOCATION_MODE} is set to
+         * {@link #LOCATION_MODE_OFF}
+         * @hide
+         */
+        public static final String LOCATION_PREVIOUS_MODE = "location_previous_mode";
 
         /**
+         * Sets all location providers to the previous states before location was turned off.
+         * @hide
+         */
+        public static final int LOCATION_MODE_PREVIOUS = -1;
+        /**
          * Location access disabled.
          */
         public static final int LOCATION_MODE_OFF = 0;
@@ -5795,6 +5806,7 @@
             CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
             CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+            CLONE_TO_MANAGED_PROFILE.add(LOCATION_PREVIOUS_MODE);
             CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
             CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
             CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
@@ -5882,6 +5894,28 @@
         }
 
         /**
+         * Saves the current location mode into {@link #LOCATION_PREVIOUS_MODE}.
+         */
+        private static final boolean saveLocationModeForUser(ContentResolver cr, int userId) {
+            final int mode = getLocationModeForUser(cr, userId);
+            return putIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE, mode, userId);
+        }
+
+        /**
+         * Restores the current location mode from {@link #LOCATION_PREVIOUS_MODE}.
+         */
+        private static final boolean restoreLocationModeForUser(ContentResolver cr, int userId) {
+            int mode = getIntForUser(cr, Settings.Secure.LOCATION_PREVIOUS_MODE,
+                    LOCATION_MODE_HIGH_ACCURACY, userId);
+            // Make sure that the previous mode is never "off". Otherwise the user won't be able to
+            // turn on location any longer.
+            if (mode == LOCATION_MODE_OFF) {
+                mode = LOCATION_MODE_HIGH_ACCURACY;
+            }
+            return setLocationModeForUser(cr, mode, userId);
+        }
+
+        /**
          * Thread-safe method for setting the location mode to one of
          * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
          * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
@@ -5899,7 +5933,11 @@
                 boolean gps = false;
                 boolean network = false;
                 switch (mode) {
+                    case LOCATION_MODE_PREVIOUS:
+                        // Retrieve the actual mode and set to that mode.
+                        return restoreLocationModeForUser(cr, userId);
                     case LOCATION_MODE_OFF:
+                        saveLocationModeForUser(cr, userId);
                         break;
                     case LOCATION_MODE_SENSORS_ONLY:
                         gps = true;
diff --git a/core/java/android/security/net/config/ApplicationConfig.java b/core/java/android/security/net/config/ApplicationConfig.java
index 48359d47..b627641 100644
--- a/core/java/android/security/net/config/ApplicationConfig.java
+++ b/core/java/android/security/net/config/ApplicationConfig.java
@@ -144,18 +144,4 @@
             return sInstance;
         }
     }
-
-    /** @hide */
-    public static ApplicationConfig getPlatformDefault() {
-        return new ApplicationConfig(new ConfigSource() {
-            @Override
-            public NetworkSecurityConfig getDefaultConfig() {
-                return NetworkSecurityConfig.DEFAULT;
-            }
-            @Override
-            public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
-                return null;
-            }
-        });
-    }
 }
diff --git a/core/java/android/security/net/config/CertificateSource.java b/core/java/android/security/net/config/CertificateSource.java
index 386354d..2b7829e 100644
--- a/core/java/android/security/net/config/CertificateSource.java
+++ b/core/java/android/security/net/config/CertificateSource.java
@@ -22,4 +22,5 @@
 /** @hide */
 public interface CertificateSource {
     Set<X509Certificate> getCertificates();
+    X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
 }
diff --git a/core/java/android/security/net/config/CertificatesEntryRef.java b/core/java/android/security/net/config/CertificatesEntryRef.java
index 2ba38c21..1d15e19 100644
--- a/core/java/android/security/net/config/CertificatesEntryRef.java
+++ b/core/java/android/security/net/config/CertificatesEntryRef.java
@@ -30,6 +30,10 @@
         mOverridesPins = overridesPins;
     }
 
+    boolean overridesPins() {
+        return mOverridesPins;
+    }
+
     public Set<TrustAnchor> getTrustAnchors() {
         // TODO: cache this [but handle mutable sources]
         Set<TrustAnchor> anchors = new ArraySet<TrustAnchor>();
@@ -38,4 +42,13 @@
         }
         return anchors;
     }
+
+    public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) {
+        X509Certificate foundCert = mSource.findBySubjectAndPublicKey(cert);
+        if (foundCert == null) {
+            return null;
+        }
+
+        return new TrustAnchor(foundCert, mOverridesPins);
+    }
 }
diff --git a/core/java/android/security/net/config/DirectoryCertificateSource.java b/core/java/android/security/net/config/DirectoryCertificateSource.java
new file mode 100644
index 0000000..a261e06
--- /dev/null
+++ b/core/java/android/security/net/config/DirectoryCertificateSource.java
@@ -0,0 +1,139 @@
+/*
+ * 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.security.net.config;
+
+import android.os.Environment;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import libcore.io.IoUtils;
+
+import com.android.org.conscrypt.Hex;
+import com.android.org.conscrypt.NativeCrypto;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * {@link CertificateSource} based on a directory where certificates are stored as individual files
+ * named after a hash of their SubjectName for more efficient lookups.
+ * @hide
+ */
+abstract class DirectoryCertificateSource implements CertificateSource {
+    private final File mDir;
+    private final Object mLock = new Object();
+    private final CertificateFactory mCertFactory;
+
+    private Set<X509Certificate> mCertificates;
+
+    protected DirectoryCertificateSource(File caDir) {
+        mDir = caDir;
+        try {
+            mCertFactory = CertificateFactory.getInstance("X.509");
+        } catch (CertificateException e) {
+            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
+        }
+    }
+
+    protected abstract boolean isCertMarkedAsRemoved(String caFile);
+
+    @Override
+    public Set<X509Certificate> getCertificates() {
+        // TODO: loading all of these is wasteful, we should instead use a keystore style API.
+        synchronized (mLock) {
+            if (mCertificates != null) {
+                return mCertificates;
+            }
+
+            Set<X509Certificate> certs = new ArraySet<X509Certificate>();
+            if (mDir.isDirectory()) {
+                for (String caFile : mDir.list()) {
+                    if (isCertMarkedAsRemoved(caFile)) {
+                        continue;
+                    }
+                    X509Certificate cert = readCertificate(caFile);
+                    if (cert != null) {
+                        certs.add(cert);
+                    }
+                }
+            }
+            mCertificates = certs;
+            return mCertificates;
+        }
+    }
+
+    @Override
+    public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) {
+        return findCert(cert.getSubjectX500Principal(), new CertSelector() {
+            @Override
+            public boolean match(X509Certificate ca) {
+                return ca.getPublicKey().equals(cert.getPublicKey());
+            }
+        });
+    }
+
+    private static interface CertSelector {
+        boolean match(X509Certificate cert);
+    }
+
+    private X509Certificate findCert(X500Principal subj, CertSelector selector) {
+        String hash = getHash(subj);
+        for (int index = 0; index >= 0; index++) {
+            String fileName = hash + "." + index;
+            if (!new File(mDir, fileName).exists()) {
+                break;
+            }
+            if (isCertMarkedAsRemoved(fileName)) {
+                continue;
+            }
+            X509Certificate cert = readCertificate(fileName);
+            if (!subj.equals(cert.getSubjectX500Principal())) {
+                continue;
+            }
+            if (selector.match(cert)) {
+                return cert;
+            }
+        }
+        return null;
+    }
+
+    private String getHash(X500Principal name) {
+        int hash = NativeCrypto.X509_NAME_hash_old(name);
+        return Hex.intToHexString(hash, 8);
+    }
+
+    private X509Certificate readCertificate(String file) {
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(new File(mDir, file)));
+            return (X509Certificate) mCertFactory.generateCertificate(is);
+        } catch (CertificateException | IOException e) {
+            return null;
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+}
diff --git a/core/java/android/security/net/config/KeyStoreCertificateSource.java b/core/java/android/security/net/config/KeyStoreCertificateSource.java
index 9167a90..7a01a64 100644
--- a/core/java/android/security/net/config/KeyStoreCertificateSource.java
+++ b/core/java/android/security/net/config/KeyStoreCertificateSource.java
@@ -24,6 +24,8 @@
 import java.util.Enumeration;
 import java.util.Set;
 
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
 /**
  * {@link CertificateSource} which provides certificates from trusted certificate entries of a
  * {@link KeyStore}.
@@ -31,6 +33,7 @@
 class KeyStoreCertificateSource implements CertificateSource {
     private final Object mLock = new Object();
     private final KeyStore mKeyStore;
+    private TrustedCertificateIndex mIndex;
     private Set<X509Certificate> mCertificates;
 
     public KeyStoreCertificateSource(KeyStore ks) {
@@ -39,24 +42,42 @@
 
     @Override
     public Set<X509Certificate> getCertificates() {
+        ensureInitialized();
+        return mCertificates;
+    }
+
+    private void ensureInitialized() {
         synchronized (mLock) {
             if (mCertificates != null) {
-                return mCertificates;
+                return;
             }
+
             try {
+                TrustedCertificateIndex localIndex = new TrustedCertificateIndex();
                 Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
                 for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
                     String alias = en.nextElement();
                     X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
                     if (cert != null) {
                         certificates.add(cert);
+                        localIndex.index(cert);
                     }
                 }
+                mIndex = localIndex;
                 mCertificates = certificates;
-                return mCertificates;
             } catch (KeyStoreException e) {
                 throw new RuntimeException("Failed to load certificates from KeyStore", e);
             }
         }
     }
+
+    @Override
+    public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+        ensureInitialized();
+        java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+        if (anchor == null) {
+            return null;
+        }
+        return anchor.getTrustedCert();
+    }
 }
diff --git a/core/java/android/security/net/config/ManifestConfigSource.java b/core/java/android/security/net/config/ManifestConfigSource.java
new file mode 100644
index 0000000..bf1fb8a
--- /dev/null
+++ b/core/java/android/security/net/config/ManifestConfigSource.java
@@ -0,0 +1,100 @@
+/*
+ * 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.security.net.config;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.util.Pair;
+import java.util.Set;
+
+/** @hide */
+public class ManifestConfigSource implements ConfigSource {
+    public static final String META_DATA_NETWORK_SECURITY_CONFIG =
+            "android.security.net.config";
+    private static final boolean DBG = true;
+    private static final String LOG_TAG = "NetworkSecurityConfig";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    private ConfigSource mConfigSource;
+
+    public ManifestConfigSource(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+        return getConfigSource().getPerDomainConfigs();
+    }
+
+    @Override
+    public NetworkSecurityConfig getDefaultConfig() {
+        return getConfigSource().getDefaultConfig();
+    }
+
+    private ConfigSource getConfigSource() {
+        synchronized (mLock) {
+            if (mConfigSource != null) {
+                return mConfigSource;
+            }
+            ApplicationInfo info;
+            try {
+                info = mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(),
+                        PackageManager.GET_META_DATA);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new RuntimeException("Failed to look up ApplicationInfo", e);
+            }
+            int configResourceId = 0;
+            if (info != null && info.metaData != null) {
+                configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
+            }
+
+            ConfigSource source;
+            if (configResourceId != 0) {
+                boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+                if (DBG) {
+                    Log.d(LOG_TAG, "Using Network Security Config from resource "
+                            + mContext.getResources().getResourceEntryName(configResourceId)
+                            + " debugBuild: " + debugBuild);
+                }
+                source = new XmlConfigSource(mContext, configResourceId, debugBuild);
+            } else {
+                if (DBG) {
+                    Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
+                }
+                source = new DefaultConfigSource();
+            }
+            mConfigSource = source;
+            return mConfigSource;
+        }
+    }
+
+    private static final class DefaultConfigSource implements ConfigSource {
+        @Override
+        public NetworkSecurityConfig getDefaultConfig() {
+            return NetworkSecurityConfig.DEFAULT;
+        }
+
+        @Override
+        public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
+            return null;
+        }
+    }
+}
diff --git a/core/java/android/security/net/config/NetworkSecurityConfig.java b/core/java/android/security/net/config/NetworkSecurityConfig.java
index 9eab80c..2ab07b5 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfig.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfig.java
@@ -22,6 +22,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -53,6 +54,19 @@
         mHstsEnforced = hstsEnforced;
         mPins = pins;
         mCertificatesEntryRefs = certificatesEntryRefs;
+        // Sort the certificates entry refs so that all entries that override pins come before
+        // non-override pin entries. This allows us to handle the case where a certificate is in
+        // multiple entry refs by returning the certificate from the first entry ref.
+        Collections.sort(mCertificatesEntryRefs, new Comparator<CertificatesEntryRef>() {
+            @Override
+            public int compare(CertificatesEntryRef lhs, CertificatesEntryRef rhs) {
+                if (lhs.overridesPins()) {
+                    return rhs.overridesPins() ? 0 : -1;
+                } else {
+                    return rhs.overridesPins() ? 1 : 0;
+                }
+            }
+        });
     }
 
     public Set<TrustAnchor> getTrustAnchors() {
@@ -63,14 +77,15 @@
             // Merge trust anchors based on the X509Certificate.
             // If we see the same certificate in two TrustAnchors, one with overridesPins and one
             // without, the one with overridesPins wins.
+            // Because mCertificatesEntryRefs is sorted with all overridesPins anchors coming first
+            // this can be simplified to just using the first occurrence of a certificate.
             Map<X509Certificate, TrustAnchor> anchorMap = new ArrayMap<>();
             for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
                 Set<TrustAnchor> anchors = ref.getTrustAnchors();
                 for (TrustAnchor anchor : anchors) {
-                    if (anchor.overridesPins) {
-                        anchorMap.put(anchor.certificate, anchor);
-                    } else if (!anchorMap.containsKey(anchor.certificate)) {
-                        anchorMap.put(anchor.certificate, anchor);
+                    X509Certificate cert = anchor.certificate;
+                    if (!anchorMap.containsKey(cert)) {
+                        anchorMap.put(cert, anchor);
                     }
                 }
             }
@@ -108,6 +123,17 @@
         }
     }
 
+    /** @hide */
+    public TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
+        for (CertificatesEntryRef ref : mCertificatesEntryRefs) {
+            TrustAnchor anchor = ref.findBySubjectAndPublicKey(cert);
+            if (anchor != null) {
+                return anchor;
+            }
+        }
+        return null;
+    }
+
     /**
      * Return a {@link Builder} for the default {@code NetworkSecurityConfig}.
      *
diff --git a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
index ac762ef..5ebc7ac 100644
--- a/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
+++ b/core/java/android/security/net/config/NetworkSecurityConfigProvider.java
@@ -17,20 +17,13 @@
 package android.security.net.config;
 
 import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.util.Log;
 import java.security.Security;
 import java.security.Provider;
 
 /** @hide */
 public final class NetworkSecurityConfigProvider extends Provider {
-    private static final String LOG_TAG = "NetworkSecurityConfig";
     private static final String PREFIX =
             NetworkSecurityConfigProvider.class.getPackage().getName() + ".";
-    public static final String META_DATA_NETWORK_SECURITY_CONFIG =
-            "android.security.net.config";
-    private static final boolean DBG = true;
 
     public NetworkSecurityConfigProvider() {
         // TODO: More clever name than this
@@ -40,36 +33,7 @@
     }
 
     public static void install(Context context) {
-        ApplicationInfo info = null;
-        // TODO: This lookup shouldn't be done in the app startup path, it should be done lazily.
-        try {
-            info = context.getPackageManager().getApplicationInfo(context.getPackageName(),
-                    PackageManager.GET_META_DATA);
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new RuntimeException("Failed to look up ApplicationInfo", e);
-        }
-        int configResourceId = 0;
-        if (info != null && info.metaData != null) {
-            configResourceId = info.metaData.getInt(META_DATA_NETWORK_SECURITY_CONFIG);
-        }
-
-        ApplicationConfig config;
-        if (configResourceId != 0) {
-            boolean debugBuild = (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
-            if (DBG) {
-                Log.d(LOG_TAG, "Using Network Security Config from resource "
-                        + context.getResources().getResourceEntryName(configResourceId)
-                        + " debugBuild: " + debugBuild);
-            }
-            ConfigSource source = new XmlConfigSource(context, configResourceId, debugBuild);
-            config = new ApplicationConfig(source);
-        } else {
-            if (DBG) {
-                Log.d(LOG_TAG, "No Network Security Config specified, using platform default");
-            }
-            config = ApplicationConfig.getPlatformDefault();
-        }
-
+        ApplicationConfig config = new ApplicationConfig(new ManifestConfigSource(context));
         ApplicationConfig.setDefaultInstance(config);
         int pos = Security.insertProviderAt(new NetworkSecurityConfigProvider(), 1);
         if (pos != 1) {
diff --git a/core/java/android/security/net/config/NetworkSecurityTrustManager.java b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
index 2b860fa..6013c1e 100644
--- a/core/java/android/security/net/config/NetworkSecurityTrustManager.java
+++ b/core/java/android/security/net/config/NetworkSecurityTrustManager.java
@@ -133,14 +133,8 @@
             return false;
         }
         X509Certificate anchorCert = chain.get(chain.size() - 1);
-        TrustAnchor chainAnchor = null;
-        // TODO: faster lookup
-        for (TrustAnchor anchor : mNetworkSecurityConfig.getTrustAnchors()) {
-            if (anchor.certificate.equals(anchorCert)) {
-                chainAnchor = anchor;
-                break;
-            }
-        }
+        TrustAnchor chainAnchor =
+                mNetworkSecurityConfig.findTrustAnchorBySubjectAndPublicKey(anchorCert);
         if (chainAnchor == null) {
             throw new CertificateException("Trusted chain does not end in a TrustAnchor");
         }
diff --git a/core/java/android/security/net/config/ResourceCertificateSource.java b/core/java/android/security/net/config/ResourceCertificateSource.java
index 06dd9d4..b007f8f 100644
--- a/core/java/android/security/net/config/ResourceCertificateSource.java
+++ b/core/java/android/security/net/config/ResourceCertificateSource.java
@@ -27,26 +27,29 @@
 import java.util.Collection;
 import java.util.Set;
 
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
 /**
  * {@link CertificateSource} based on certificates contained in an application resource file.
  * @hide
  */
 public class ResourceCertificateSource implements CertificateSource {
-    private Set<X509Certificate> mCertificates;
-    private final int  mResourceId;
-    private Context mContext;
     private final Object mLock = new Object();
+    private final int  mResourceId;
+
+    private Set<X509Certificate> mCertificates;
+    private Context mContext;
+    private TrustedCertificateIndex mIndex;
 
     public ResourceCertificateSource(int resourceId, Context context) {
         mResourceId = resourceId;
         mContext = context.getApplicationContext();
     }
 
-    @Override
-    public Set<X509Certificate> getCertificates() {
+    private void ensureInitialized() {
         synchronized (mLock) {
             if (mCertificates != null) {
-                return mCertificates;
+                return;
             }
             Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
             Collection<? extends Certificate> certs;
@@ -61,12 +64,30 @@
             } finally {
                 IoUtils.closeQuietly(in);
             }
+            TrustedCertificateIndex indexLocal = new TrustedCertificateIndex();
             for (Certificate cert : certs) {
-                    certificates.add((X509Certificate) cert);
+                certificates.add((X509Certificate) cert);
+                indexLocal.index((X509Certificate) cert);
             }
             mCertificates = certificates;
+            mIndex = indexLocal;
             mContext = null;
-            return mCertificates;
         }
     }
+
+    @Override
+    public Set<X509Certificate> getCertificates() {
+        ensureInitialized();
+        return mCertificates;
+    }
+
+    @Override
+    public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+        ensureInitialized();
+        java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+        if (anchor == null) {
+            return null;
+        }
+        return anchor.getTrustedCert();
+    }
 }
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index 7649a97..abef7b4 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -18,29 +18,20 @@
 
 import android.os.Environment;
 import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
 
 /**
  * {@link CertificateSource} based on the system trusted CA store.
  * @hide
  */
-public class SystemCertificateSource implements CertificateSource {
+public final class SystemCertificateSource extends DirectoryCertificateSource {
     private static final SystemCertificateSource INSTANCE = new SystemCertificateSource();
-    private Set<X509Certificate> mSystemCerts = null;
-    private final Object mLock = new Object();
+    private final File mUserRemovedCaDir;
 
     private SystemCertificateSource() {
+        super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
+        File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+        mUserRemovedCaDir = new File(configDir, "cacerts-removed");
     }
 
     public static SystemCertificateSource getInstance() {
@@ -48,54 +39,7 @@
     }
 
     @Override
-    public Set<X509Certificate> getCertificates() {
-        // TODO: loading all of these is wasteful, we should instead use a keystore style API.
-        synchronized (mLock) {
-            if (mSystemCerts != null) {
-                return mSystemCerts;
-            }
-            CertificateFactory certFactory;
-            try {
-                certFactory = CertificateFactory.getInstance("X.509");
-            } catch (CertificateException e) {
-                throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
-            }
-
-            final String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
-            final File systemCaDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
-            final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
-            final File userRemovedCaDir = new File(configDir, "cacerts-removed");
-            // Sanity check
-            if (!systemCaDir.isDirectory()) {
-                throw new AssertionError(systemCaDir + " is not a directory");
-            }
-
-            Set<X509Certificate> systemCerts = new ArraySet<X509Certificate>();
-            for (String caFile : systemCaDir.list()) {
-                // Skip any CAs in the user's deleted directory.
-                if (new File(userRemovedCaDir, caFile).exists()) {
-                    continue;
-                }
-                InputStream is = null;
-                try {
-                    is = new BufferedInputStream(
-                            new FileInputStream(new File(systemCaDir, caFile)));
-                    systemCerts.add((X509Certificate) certFactory.generateCertificate(is));
-                } catch (CertificateException | IOException e) {
-                    // Don't rethrow to be consistent with conscrypt's cert loading code.
-                    continue;
-                } finally {
-                    IoUtils.closeQuietly(is);
-                }
-            }
-            mSystemCerts = systemCerts;
-            return mSystemCerts;
-        }
-    }
-
-    public void onCertificateStorageChange() {
-        synchronized (mLock) {
-            mSystemCerts = null;
-        }
+    protected boolean isCertMarkedAsRemoved(String caFile) {
+        return new File(mUserRemovedCaDir, caFile).exists();
     }
 }
diff --git a/core/java/android/security/net/config/UserCertificateSource.java b/core/java/android/security/net/config/UserCertificateSource.java
index e9d5aa1..1a7d924 100644
--- a/core/java/android/security/net/config/UserCertificateSource.java
+++ b/core/java/android/security/net/config/UserCertificateSource.java
@@ -18,29 +18,18 @@
 
 import android.os.Environment;
 import android.os.UserHandle;
-import android.util.ArraySet;
-import java.io.BufferedInputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.IOException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Set;
-import libcore.io.IoUtils;
 
 /**
  * {@link CertificateSource} based on the user-installed trusted CA store.
  * @hide
  */
-public class UserCertificateSource implements CertificateSource {
+public final class UserCertificateSource extends DirectoryCertificateSource {
     private static final UserCertificateSource INSTANCE = new UserCertificateSource();
-    private Set<X509Certificate> mUserCerts = null;
-    private final Object mLock = new Object();
 
     private UserCertificateSource() {
+        super(new File(
+                Environment.getUserConfigDirectory(UserHandle.myUserId()), "cacerts-added"));
     }
 
     public static UserCertificateSource getInstance() {
@@ -48,45 +37,7 @@
     }
 
     @Override
-    public Set<X509Certificate> getCertificates() {
-        // TODO: loading all of these is wasteful, we should instead use a keystore style API.
-        synchronized (mLock) {
-            if (mUserCerts != null) {
-                return mUserCerts;
-            }
-            CertificateFactory certFactory;
-            try {
-                certFactory = CertificateFactory.getInstance("X.509");
-            } catch (CertificateException e) {
-                throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
-            }
-            final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
-            final File userCaDir = new File(configDir, "cacerts-added");
-            Set<X509Certificate> userCerts = new ArraySet<X509Certificate>();
-            // If the user hasn't added any certificates the directory may not exist.
-            if (userCaDir.isDirectory()) {
-                for (String caFile : userCaDir.list()) {
-                    InputStream is = null;
-                    try {
-                        is = new BufferedInputStream(
-                                new FileInputStream(new File(userCaDir, caFile)));
-                        userCerts.add((X509Certificate) certFactory.generateCertificate(is));
-                    } catch (CertificateException | IOException e) {
-                        // Don't rethrow to be consistent with conscrypt's cert loading code.
-                        continue;
-                    } finally {
-                        IoUtils.closeQuietly(is);
-                    }
-                }
-            }
-            mUserCerts = userCerts;
-            return mUserCerts;
-        }
-    }
-
-    public void onCertificateStorageChange() {
-        synchronized (mLock) {
-            mUserCerts = null;
-        }
+    protected boolean isCertMarkedAsRemoved(String caFile) {
+        return false;
     }
 }
diff --git a/core/java/android/util/jar/StrictJarFile.java b/core/java/android/util/jar/StrictJarFile.java
new file mode 100644
index 0000000..fd57806
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarFile.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2013 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.util.jar;
+
+import dalvik.system.CloseGuard;
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ *        {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+    private final long nativeHandle;
+
+    // NOTE: It's possible to share a file descriptor with the native
+    // code, at the cost of some additional complexity.
+    private final RandomAccessFile raf;
+
+    private final StrictJarManifest manifest;
+    private final StrictJarVerifier verifier;
+
+    private final boolean isSigned;
+
+    private final CloseGuard guard = CloseGuard.get();
+    private boolean closed;
+
+    public StrictJarFile(String fileName) throws IOException, SecurityException {
+        this.nativeHandle = nativeOpenJarFile(fileName);
+        this.raf = new RandomAccessFile(fileName, "r");
+
+        try {
+            // Read the MANIFEST and signature files up front and try to
+            // parse them. We never want to accept a JAR File with broken signatures
+            // or manifests, so it's best to throw as early as possible.
+            HashMap<String, byte[]> metaEntries = getMetaEntries();
+            this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+            this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+            Set<String> files = manifest.getEntries().keySet();
+            for (String file : files) {
+                if (findEntry(file) == null) {
+                    throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+                }
+            }
+
+            isSigned = verifier.readCertificates() && verifier.isSignedJar();
+        } catch (IOException | SecurityException e) {
+            nativeClose(this.nativeHandle);
+            IoUtils.closeQuietly(this.raf);
+            throw e;
+        }
+
+        guard.open("close");
+    }
+
+    public StrictJarManifest getManifest() {
+        return manifest;
+    }
+
+    public Iterator<ZipEntry> iterator() throws IOException {
+        return new EntryIterator(nativeHandle, "");
+    }
+
+    public ZipEntry findEntry(String name) {
+        return nativeFindEntry(nativeHandle, name);
+    }
+
+    /**
+     * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
+     * This method MUST be called only after fully exhausting the InputStream belonging
+     * to this entry.
+     *
+     * Returns {@code null} if this jar file isn't signed or if this method is
+     * called before the stream is processed.
+     */
+    public Certificate[][] getCertificateChains(ZipEntry ze) {
+        if (isSigned) {
+            return verifier.getCertificateChains(ze.getName());
+        }
+
+        return null;
+    }
+
+    /**
+     * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+     * This method MUST be called only after fully exhausting the InputStream belonging
+     * to this entry.
+     *
+     * Returns {@code null} if this jar file isn't signed or if this method is
+     * called before the stream is processed.
+     *
+     * @deprecated Switch callers to use getCertificateChains instead
+     */
+    @Deprecated
+    public Certificate[] getCertificates(ZipEntry ze) {
+        if (isSigned) {
+            Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+            // Measure number of certs.
+            int count = 0;
+            for (Certificate[] chain : certChains) {
+                count += chain.length;
+            }
+
+            // Create new array and copy all the certs into it.
+            Certificate[] certs = new Certificate[count];
+            int i = 0;
+            for (Certificate[] chain : certChains) {
+                System.arraycopy(chain, 0, certs, i, chain.length);
+                i += chain.length;
+            }
+
+            return certs;
+        }
+
+        return null;
+    }
+
+    public InputStream getInputStream(ZipEntry ze) {
+        final InputStream is = getZipInputStream(ze);
+
+        if (isSigned) {
+            StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+            if (entry == null) {
+                return is;
+            }
+
+            return new JarFileInputStream(is, ze.getSize(), entry);
+        }
+
+        return is;
+    }
+
+    public void close() throws IOException {
+        if (!closed) {
+            guard.close();
+
+            nativeClose(nativeHandle);
+            IoUtils.closeQuietly(raf);
+            closed = true;
+        }
+    }
+
+    private InputStream getZipInputStream(ZipEntry ze) {
+        if (ze.getMethod() == ZipEntry.STORED) {
+            return new RAFStream(raf, ze.getDataOffset(),
+                    ze.getDataOffset() + ze.getSize());
+        } else {
+            final RAFStream wrapped = new RAFStream(
+                    raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+            int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+            return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+        }
+    }
+
+    static final class EntryIterator implements Iterator<ZipEntry> {
+        private final long iterationHandle;
+        private ZipEntry nextEntry;
+
+        EntryIterator(long nativeHandle, String prefix) throws IOException {
+            iterationHandle = nativeStartIteration(nativeHandle, prefix);
+        }
+
+        public ZipEntry next() {
+            if (nextEntry != null) {
+                final ZipEntry ze = nextEntry;
+                nextEntry = null;
+                return ze;
+            }
+
+            return nativeNextEntry(iterationHandle);
+        }
+
+        public boolean hasNext() {
+            if (nextEntry != null) {
+                return true;
+            }
+
+            final ZipEntry ze = nativeNextEntry(iterationHandle);
+            if (ze == null) {
+                return false;
+            }
+
+            nextEntry = ze;
+            return true;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private HashMap<String, byte[]> getMetaEntries() throws IOException {
+        HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+        Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+        while (entryIterator.hasNext()) {
+            final ZipEntry entry = entryIterator.next();
+            metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+        }
+
+        return metaEntries;
+    }
+
+    static final class JarFileInputStream extends FilterInputStream {
+        private final StrictJarVerifier.VerifierEntry entry;
+
+        private long count;
+        private boolean done = false;
+
+        JarFileInputStream(InputStream is, long size, StrictJarVerifier.VerifierEntry e) {
+            super(is);
+            entry = e;
+
+            count = size;
+        }
+
+        @Override
+        public int read() throws IOException {
+            if (done) {
+                return -1;
+            }
+            if (count > 0) {
+                int r = super.read();
+                if (r != -1) {
+                    entry.write(r);
+                    count--;
+                } else {
+                    count = 0;
+                }
+                if (count == 0) {
+                    done = true;
+                    entry.verify();
+                }
+                return r;
+            } else {
+                done = true;
+                entry.verify();
+                return -1;
+            }
+        }
+
+        @Override
+        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            if (done) {
+                return -1;
+            }
+            if (count > 0) {
+                int r = super.read(buffer, byteOffset, byteCount);
+                if (r != -1) {
+                    int size = r;
+                    if (count < size) {
+                        size = (int) count;
+                    }
+                    entry.write(buffer, byteOffset, size);
+                    count -= size;
+                } else {
+                    count = 0;
+                }
+                if (count == 0) {
+                    done = true;
+                    entry.verify();
+                }
+                return r;
+            } else {
+                done = true;
+                entry.verify();
+                return -1;
+            }
+        }
+
+        @Override
+        public int available() throws IOException {
+            if (done) {
+                return 0;
+            }
+            return super.available();
+        }
+
+        @Override
+        public long skip(long byteCount) throws IOException {
+            return Streams.skipByReading(this, byteCount);
+        }
+    }
+
+    /** @hide */
+    public static class ZipInflaterInputStream extends InflaterInputStream {
+        private final ZipEntry entry;
+        private long bytesRead = 0;
+
+        public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
+            super(is, inf, bsize);
+            this.entry = entry;
+        }
+
+        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            final int i;
+            try {
+                i = super.read(buffer, byteOffset, byteCount);
+            } catch (IOException e) {
+                throw new IOException("Error reading data for " + entry.getName() + " near offset "
+                        + bytesRead, e);
+            }
+            if (i == -1) {
+                if (entry.getSize() != bytesRead) {
+                    throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
+                            + entry.getSize());
+                }
+            } else {
+                bytesRead += i;
+            }
+            return i;
+        }
+
+        @Override public int available() throws IOException {
+            if (closed) {
+                // Our superclass will throw an exception, but there's a jtreg test that
+                // explicitly checks that the InputStream returned from ZipFile.getInputStream
+                // returns 0 even when closed.
+                return 0;
+            }
+            return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
+        }
+    }
+
+    /**
+     * Wrap a stream around a RandomAccessFile.  The RandomAccessFile is shared
+     * among all streams returned by getInputStream(), so we have to synchronize
+     * access to it.  (We can optimize this by adding buffering here to reduce
+     * collisions.)
+     *
+     * <p>We could support mark/reset, but we don't currently need them.
+     *
+     * @hide
+     */
+    public static class RAFStream extends InputStream {
+        private final RandomAccessFile sharedRaf;
+        private long endOffset;
+        private long offset;
+
+
+        public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
+            sharedRaf = raf;
+            offset = initialOffset;
+            this.endOffset = endOffset;
+        }
+
+        public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+            this(raf, initialOffset, raf.length());
+        }
+
+        @Override public int available() throws IOException {
+            return (offset < endOffset ? 1 : 0);
+        }
+
+        @Override public int read() throws IOException {
+            return Streams.readSingleByte(this);
+        }
+
+        @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            synchronized (sharedRaf) {
+                final long length = endOffset - offset;
+                if (byteCount > length) {
+                    byteCount = (int) length;
+                }
+                sharedRaf.seek(offset);
+                int count = sharedRaf.read(buffer, byteOffset, byteCount);
+                if (count > 0) {
+                    offset += count;
+                    return count;
+                } else {
+                    return -1;
+                }
+            }
+        }
+
+        @Override public long skip(long byteCount) throws IOException {
+            if (byteCount > endOffset - offset) {
+                byteCount = endOffset - offset;
+            }
+            offset += byteCount;
+            return byteCount;
+        }
+    }
+
+
+    private static native long nativeOpenJarFile(String fileName) throws IOException;
+    private static native long nativeStartIteration(long nativeHandle, String prefix);
+    private static native ZipEntry nativeNextEntry(long iterationHandle);
+    private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+    private static native void nativeClose(long nativeHandle);
+}
diff --git a/core/java/android/util/jar/StrictJarManifest.java b/core/java/android/util/jar/StrictJarManifest.java
new file mode 100644
index 0000000..dbb466c
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifest.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import libcore.io.Streams;
+
+/**
+ * The {@code StrictJarManifest} class is used to obtain attribute information for a
+ * {@code StrictJarFile} and its entries.
+ *
+ * @hide
+ */
+public class StrictJarManifest implements Cloneable {
+    static final int LINE_LENGTH_LIMIT = 72;
+
+    private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
+
+    private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
+
+    private final Attributes mainAttributes;
+    private final HashMap<String, Attributes> entries;
+
+    static final class Chunk {
+        final int start;
+        final int end;
+
+        Chunk(int start, int end) {
+            this.start = start;
+            this.end = end;
+        }
+    }
+
+    private HashMap<String, Chunk> chunks;
+
+    /**
+     * The end of the main attributes section in the manifest is needed in
+     * verification.
+     */
+    private int mainEnd;
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance.
+     */
+    public StrictJarManifest() {
+        entries = new HashMap<String, Attributes>();
+        mainAttributes = new Attributes();
+    }
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance using the attributes obtained
+     * from the input stream.
+     *
+     * @param is
+     *            {@code InputStream} to parse for attributes.
+     * @throws IOException
+     *             if an IO error occurs while creating this {@code StrictJarManifest}
+     */
+    public StrictJarManifest(InputStream is) throws IOException {
+        this();
+        read(Streams.readFully(is));
+    }
+
+    /**
+     * Creates a new {@code StrictJarManifest} instance. The new instance will have the
+     * same attributes as those found in the parameter {@code StrictJarManifest}.
+     *
+     * @param man
+     *            {@code StrictJarManifest} instance to obtain attributes from.
+     */
+    @SuppressWarnings("unchecked")
+    public StrictJarManifest(StrictJarManifest man) {
+        mainAttributes = (Attributes) man.mainAttributes.clone();
+        entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
+                .getEntries()).clone();
+    }
+
+    StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+        this();
+        if (readChunks) {
+            chunks = new HashMap<String, Chunk>();
+        }
+        read(manifestBytes);
+    }
+
+    /**
+     * Resets the both the main attributes as well as the entry attributes
+     * associated with this {@code StrictJarManifest}.
+     */
+    public void clear() {
+        entries.clear();
+        mainAttributes.clear();
+    }
+
+    /**
+     * Returns the {@code Attributes} associated with the parameter entry
+     * {@code name}.
+     *
+     * @param name
+     *            the name of the entry to obtain {@code Attributes} from.
+     * @return the Attributes for the entry or {@code null} if the entry does
+     *         not exist.
+     */
+    public Attributes getAttributes(String name) {
+        return getEntries().get(name);
+    }
+
+    /**
+     * Returns a map containing the {@code Attributes} for each entry in the
+     * {@code StrictJarManifest}.
+     *
+     * @return the map of entry attributes.
+     */
+    public Map<String, Attributes> getEntries() {
+        return entries;
+    }
+
+    /**
+     * Returns the main {@code Attributes} of the {@code JarFile}.
+     *
+     * @return main {@code Attributes} associated with the source {@code
+     *         JarFile}.
+     */
+    public Attributes getMainAttributes() {
+        return mainAttributes;
+    }
+
+    /**
+     * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
+     * will equal the {@code StrictJarManifest} from which it was cloned.
+     *
+     * @return a copy of this instance.
+     */
+    @Override
+    public Object clone() {
+        return new StrictJarManifest(this);
+    }
+
+    /**
+     * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
+     * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+     * calling this method, or no attributes will be written.
+     *
+     * @throws IOException
+     *             If an error occurs writing the {@code StrictJarManifest}.
+     */
+    public void write(OutputStream os) throws IOException {
+        write(this, os);
+    }
+
+    /**
+     * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
+     *
+     * @param is
+     *            The {@code InputStream} to read from.
+     * @throws IOException
+     *             If an error occurs reading the manifest.
+     */
+    public void read(InputStream is) throws IOException {
+        read(Streams.readFullyNoClose(is));
+    }
+
+    private void read(byte[] buf) throws IOException {
+        if (buf.length == 0) {
+            return;
+        }
+
+        StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
+        mainEnd = im.getEndOfMainSection();
+        im.readEntries(entries, chunks);
+    }
+
+    /**
+     * Returns the hash code for this instance.
+     *
+     * @return this {@code StrictJarManifest}'s hashCode.
+     */
+    @Override
+    public int hashCode() {
+        return mainAttributes.hashCode() ^ getEntries().hashCode();
+    }
+
+    /**
+     * Determines if the receiver is equal to the parameter object. Two {@code
+     * StrictJarManifest}s are equal if they have identical main attributes as well as
+     * identical entry attributes.
+     *
+     * @param o
+     *            the object to compare against.
+     * @return {@code true} if the manifests are equal, {@code false} otherwise
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+        if (o.getClass() != this.getClass()) {
+            return false;
+        }
+        if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
+            return false;
+        }
+        return getEntries().equals(((StrictJarManifest) o).getEntries());
+    }
+
+    Chunk getChunk(String name) {
+        return chunks.get(name);
+    }
+
+    void removeChunks() {
+        chunks = null;
+    }
+
+    int getMainAttributesEnd() {
+        return mainEnd;
+    }
+
+    /**
+     * Writes out the attribute information of the specified manifest to the
+     * specified {@code OutputStream}
+     *
+     * @param manifest
+     *            the manifest to write out.
+     * @param out
+     *            The {@code OutputStream} to write to.
+     * @throws IOException
+     *             If an error occurs writing the {@code StrictJarManifest}.
+     */
+    static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
+        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+        ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
+
+        Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+        String version = manifest.mainAttributes.getValue(versionName);
+        if (version == null) {
+            versionName = Attributes.Name.SIGNATURE_VERSION;
+            version = manifest.mainAttributes.getValue(versionName);
+        }
+        if (version != null) {
+            writeEntry(out, versionName, version, encoder, buffer);
+            Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
+            while (entries.hasNext()) {
+                Attributes.Name name = (Attributes.Name) entries.next();
+                if (!name.equals(versionName)) {
+                    writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
+                }
+            }
+        }
+        out.write(LINE_SEPARATOR);
+        Iterator<String> i = manifest.getEntries().keySet().iterator();
+        while (i.hasNext()) {
+            String key = i.next();
+            writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
+            Attributes attributes = manifest.entries.get(key);
+            Iterator<?> entries = attributes.keySet().iterator();
+            while (entries.hasNext()) {
+                Attributes.Name name = (Attributes.Name) entries.next();
+                writeEntry(out, name, attributes.getValue(name), encoder, buffer);
+            }
+            out.write(LINE_SEPARATOR);
+        }
+    }
+
+    private static void writeEntry(OutputStream os, Attributes.Name name,
+            String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
+        String nameString = name.toString();
+        os.write(nameString.getBytes(StandardCharsets.US_ASCII));
+        os.write(VALUE_SEPARATOR);
+
+        encoder.reset();
+        bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
+
+        CharBuffer cBuf = CharBuffer.wrap(value);
+
+        while (true) {
+            CoderResult r = encoder.encode(cBuf, bBuf, true);
+            if (CoderResult.UNDERFLOW == r) {
+                r = encoder.flush(bBuf);
+            }
+            os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
+            os.write(LINE_SEPARATOR);
+            if (CoderResult.UNDERFLOW == r) {
+                break;
+            }
+            os.write(' ');
+            bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
+        }
+    }
+}
diff --git a/core/java/android/util/jar/StrictJarManifestReader.java b/core/java/android/util/jar/StrictJarManifestReader.java
new file mode 100644
index 0000000..9881bb0
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarManifestReader.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+/**
+ * Reads a JAR file manifest. The specification is here:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
+ */
+class StrictJarManifestReader {
+    // There are relatively few unique attribute names,
+    // but a manifest might have thousands of entries.
+    private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
+
+    private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
+
+    private final byte[] buf;
+
+    private final int endOfMainSection;
+
+    private int pos;
+
+    private Attributes.Name name;
+
+    private String value;
+
+    private int consecutiveLineBreaks = 0;
+
+    public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
+        this.buf = buf;
+        while (readHeader()) {
+            main.put(name, value);
+        }
+        this.endOfMainSection = pos;
+    }
+
+    public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
+        int mark = pos;
+        while (readHeader()) {
+            if (!Attributes.Name.NAME.equals(name)) {
+                throw new IOException("Entry is not named");
+            }
+            String entryNameValue = value;
+
+            Attributes entry = entries.get(entryNameValue);
+            if (entry == null) {
+                entry = new Attributes(12);
+            }
+
+            while (readHeader()) {
+                entry.put(name, value);
+            }
+
+            if (chunks != null) {
+                if (chunks.get(entryNameValue) != null) {
+                    // TODO A bug: there might be several verification chunks for
+                    // the same name. I believe they should be used to update
+                    // signature in order of appearance; there are two ways to fix
+                    // this: either use a list of chunks, or decide on used
+                    // signature algorithm in advance and reread the chunks while
+                    // updating the signature; for now a defensive error is thrown
+                    throw new IOException("A jar verifier does not support more than one entry with the same name");
+                }
+                chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
+                mark = pos;
+            }
+
+            entries.put(entryNameValue, entry);
+        }
+    }
+
+    public int getEndOfMainSection() {
+        return endOfMainSection;
+    }
+
+    /**
+     * Read a single line from the manifest buffer.
+     */
+    private boolean readHeader() throws IOException {
+        if (consecutiveLineBreaks > 1) {
+            // break a section on an empty line
+            consecutiveLineBreaks = 0;
+            return false;
+        }
+        readName();
+        consecutiveLineBreaks = 0;
+        readValue();
+        // if the last line break is missed, the line
+        // is ignored by the reference implementation
+        return consecutiveLineBreaks > 0;
+    }
+
+    private void readName() throws IOException {
+        int mark = pos;
+
+        while (pos < buf.length) {
+            if (buf[pos++] != ':') {
+                continue;
+            }
+
+            String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
+
+            if (buf[pos++] != ' ') {
+                throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
+            }
+
+            try {
+                name = attributeNameCache.get(nameString);
+                if (name == null) {
+                    name = new Attributes.Name(nameString);
+                    attributeNameCache.put(nameString, name);
+                }
+            } catch (IllegalArgumentException e) {
+                // new Attributes.Name() throws IllegalArgumentException but we declare IOException
+                throw new IOException(e.getMessage());
+            }
+            return;
+        }
+    }
+
+    private void readValue() throws IOException {
+        boolean lastCr = false;
+        int mark = pos;
+        int last = pos;
+        valueBuffer.reset();
+        while (pos < buf.length) {
+            byte next = buf[pos++];
+            switch (next) {
+            case 0:
+                throw new IOException("NUL character in a manifest");
+            case '\n':
+                if (lastCr) {
+                    lastCr = false;
+                } else {
+                    consecutiveLineBreaks++;
+                }
+                continue;
+            case '\r':
+                lastCr = true;
+                consecutiveLineBreaks++;
+                continue;
+            case ' ':
+                if (consecutiveLineBreaks == 1) {
+                    valueBuffer.write(buf, mark, last - mark);
+                    mark = pos;
+                    consecutiveLineBreaks = 0;
+                    continue;
+                }
+            }
+
+            if (consecutiveLineBreaks >= 1) {
+                pos--;
+                break;
+            }
+            last = pos;
+        }
+
+        valueBuffer.write(buf, mark, last - mark);
+        // A bit frustrating that that Charset.forName will be called
+        // again.
+        value = valueBuffer.toString(StandardCharsets.UTF_8.name());
+    }
+}
diff --git a/core/java/android/util/jar/StrictJarVerifier.java b/core/java/android/util/jar/StrictJarVerifier.java
new file mode 100644
index 0000000..ca2aec1
--- /dev/null
+++ b/core/java/android/util/jar/StrictJarVerifier.java
@@ -0,0 +1,456 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.util.jar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import libcore.io.Base64;
+import sun.security.jca.Providers;
+import sun.security.pkcs.PKCS7;
+
+/**
+ * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
+ * the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
+ * objects are expected to have a {@code JarVerifier} instance member which
+ * can be used to carry out the tasks associated with verifying a signed JAR.
+ * These tasks would typically include:
+ * <ul>
+ * <li>verification of all signed signature files
+ * <li>confirmation that all signed data was signed only by the party or parties
+ * specified in the signature block data
+ * <li>verification that the contents of all signature files (i.e. {@code .SF}
+ * files) agree with the JAR entries information found in the JAR manifest.
+ * </ul>
+ */
+class StrictJarVerifier {
+    /**
+     * List of accepted digest algorithms. This list is in order from most
+     * preferred to least preferred.
+     */
+    private static final String[] DIGEST_ALGORITHMS = new String[] {
+        "SHA-512",
+        "SHA-384",
+        "SHA-256",
+        "SHA1",
+    };
+
+    private final String jarName;
+    private final StrictJarManifest manifest;
+    private final HashMap<String, byte[]> metaEntries;
+    private final int mainAttributesEnd;
+
+    private final Hashtable<String, HashMap<String, Attributes>> signatures =
+            new Hashtable<String, HashMap<String, Attributes>>(5);
+
+    private final Hashtable<String, Certificate[]> certificates =
+            new Hashtable<String, Certificate[]>(5);
+
+    private final Hashtable<String, Certificate[][]> verifiedEntries =
+            new Hashtable<String, Certificate[][]>();
+
+    /**
+     * Stores and a hash and a message digest and verifies that massage digest
+     * matches the hash.
+     */
+    static class VerifierEntry extends OutputStream {
+
+        private final String name;
+
+        private final MessageDigest digest;
+
+        private final byte[] hash;
+
+        private final Certificate[][] certChains;
+
+        private final Hashtable<String, Certificate[][]> verifiedEntries;
+
+        VerifierEntry(String name, MessageDigest digest, byte[] hash,
+                Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
+            this.name = name;
+            this.digest = digest;
+            this.hash = hash;
+            this.certChains = certChains;
+            this.verifiedEntries = verifedEntries;
+        }
+
+        /**
+         * Updates a digest with one byte.
+         */
+        @Override
+        public void write(int value) {
+            digest.update((byte) value);
+        }
+
+        /**
+         * Updates a digest with byte array.
+         */
+        @Override
+        public void write(byte[] buf, int off, int nbytes) {
+            digest.update(buf, off, nbytes);
+        }
+
+        /**
+         * Verifies that the digests stored in the manifest match the decrypted
+         * digests from the .SF file. This indicates the validity of the
+         * signing, not the integrity of the file, as its digest must be
+         * calculated and verified when its contents are read.
+         *
+         * @throws SecurityException
+         *             if the digest value stored in the manifest does <i>not</i>
+         *             agree with the decrypted digest as recovered from the
+         *             <code>.SF</code> file.
+         */
+        void verify() {
+            byte[] d = digest.digest();
+            if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
+                throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
+            }
+            verifiedEntries.put(name, certChains);
+        }
+    }
+
+    private static SecurityException invalidDigest(String signatureFile, String name,
+            String jarName) {
+        throw new SecurityException(signatureFile + " has invalid digest for " + name +
+                " in " + jarName);
+    }
+
+    private static SecurityException failedVerification(String jarName, String signatureFile) {
+        throw new SecurityException(jarName + " failed verification of " + signatureFile);
+    }
+
+    private static SecurityException failedVerification(String jarName, String signatureFile,
+                                                      Throwable e) {
+        throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
+    }
+
+
+    /**
+     * Constructs and returns a new instance of {@code JarVerifier}.
+     *
+     * @param name
+     *            the name of the JAR file being verified.
+     */
+    StrictJarVerifier(String name, StrictJarManifest manifest,
+        HashMap<String, byte[]> metaEntries) {
+        jarName = name;
+        this.manifest = manifest;
+        this.metaEntries = metaEntries;
+        this.mainAttributesEnd = manifest.getMainAttributesEnd();
+    }
+
+    /**
+     * Invoked for each new JAR entry read operation from the input
+     * stream. This method constructs and returns a new {@link VerifierEntry}
+     * which contains the certificates used to sign the entry and its hash value
+     * as specified in the JAR MANIFEST format.
+     *
+     * @param name
+     *            the name of an entry in a JAR file which is <b>not</b> in the
+     *            {@code META-INF} directory.
+     * @return a new instance of {@link VerifierEntry} which can be used by
+     *         callers as an {@link OutputStream}.
+     */
+    VerifierEntry initEntry(String name) {
+        // If no manifest is present by the time an entry is found,
+        // verification cannot occur. If no signature files have
+        // been found, do not verify.
+        if (manifest == null || signatures.isEmpty()) {
+            return null;
+        }
+
+        Attributes attributes = manifest.getAttributes(name);
+        // entry has no digest
+        if (attributes == null) {
+            return null;
+        }
+
+        ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
+        Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
+            HashMap<String, Attributes> hm = entry.getValue();
+            if (hm.get(name) != null) {
+                // Found an entry for entry name in .SF file
+                String signatureFile = entry.getKey();
+                Certificate[] certChain = certificates.get(signatureFile);
+                if (certChain != null) {
+                    certChains.add(certChain);
+                }
+            }
+        }
+
+        // entry is not signed
+        if (certChains.isEmpty()) {
+            return null;
+        }
+        Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
+
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            final String algorithm = DIGEST_ALGORITHMS[i];
+            final String hash = attributes.getValue(algorithm + "-Digest");
+            if (hash == null) {
+                continue;
+            }
+            byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+
+            try {
+                return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+                        certChainsArray, verifiedEntries);
+            } catch (NoSuchAlgorithmException ignored) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Add a new meta entry to the internal collection of data held on each JAR
+     * entry in the {@code META-INF} directory including the manifest
+     * file itself. Files associated with the signing of a JAR would also be
+     * added to this collection.
+     *
+     * @param name
+     *            the name of the file located in the {@code META-INF}
+     *            directory.
+     * @param buf
+     *            the file bytes for the file called {@code name}.
+     * @see #removeMetaEntries()
+     */
+    void addMetaEntry(String name, byte[] buf) {
+        metaEntries.put(name.toUpperCase(Locale.US), buf);
+    }
+
+    /**
+     * If the associated JAR file is signed, check on the validity of all of the
+     * known signatures.
+     *
+     * @return {@code true} if the associated JAR is signed and an internal
+     *         check verifies the validity of the signature(s). {@code false} if
+     *         the associated JAR file has no entries at all in its {@code
+     *         META-INF} directory. This situation is indicative of an invalid
+     *         JAR file.
+     *         <p>
+     *         Will also return {@code true} if the JAR file is <i>not</i>
+     *         signed.
+     * @throws SecurityException
+     *             if the JAR file is signed and it is determined that a
+     *             signature block file contains an invalid signature for the
+     *             corresponding signature file.
+     */
+    synchronized boolean readCertificates() {
+        if (metaEntries.isEmpty()) {
+            return false;
+        }
+
+        Iterator<String> it = metaEntries.keySet().iterator();
+        while (it.hasNext()) {
+            String key = it.next();
+            if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+                verifyCertificate(key);
+                it.remove();
+            }
+        }
+        return true;
+    }
+
+   /**
+     * Verifies that the signature computed from {@code sfBytes} matches
+     * that specified in {@code blockBytes} (which is a PKCS7 block). Returns
+     * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
+     * if something goes wrong during verification.
+     */
+    static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
+        throws GeneralSecurityException {
+
+        Object obj = null;
+        try {
+
+            obj = Providers.startJarVerification();
+            PKCS7 block = new PKCS7(blockBytes);
+            if (block.verify(sfBytes) == null) {
+                throw new GeneralSecurityException("Failed to verify signature");
+            }
+            X509Certificate[] blockCerts = block.getCertificates();
+            Certificate[] signerCertChain = null;
+            if (blockCerts != null) {
+                signerCertChain = new Certificate[blockCerts.length];
+                for (int i = 0; i < blockCerts.length; ++i) {
+                    signerCertChain[i] = blockCerts[i];
+                }
+            }
+            return signerCertChain;
+        } catch (IOException e) {
+            throw new GeneralSecurityException("IO exception verifying jar cert", e);
+        } finally {
+            Providers.stopJarVerification(obj);
+        }
+    }
+
+    /**
+     * @param certFile
+     */
+    private void verifyCertificate(String certFile) {
+        // Found Digital Sig, .SF should already have been read
+        String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
+        byte[] sfBytes = metaEntries.get(signatureFile);
+        if (sfBytes == null) {
+            return;
+        }
+
+        byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
+        // Manifest entry is required for any verifications.
+        if (manifestBytes == null) {
+            return;
+        }
+
+        byte[] sBlockBytes = metaEntries.get(certFile);
+        try {
+            Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
+            if (signerCertChain != null) {
+                certificates.put(signatureFile, signerCertChain);
+            }
+        } catch (GeneralSecurityException e) {
+          throw failedVerification(jarName, signatureFile, e);
+        }
+
+        // Verify manifest hash in .sf file
+        Attributes attributes = new Attributes();
+        HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
+        try {
+            StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
+            im.readEntries(entries, null);
+        } catch (IOException e) {
+            return;
+        }
+
+        // Do we actually have any signatures to look at?
+        if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+            return;
+        }
+
+        boolean createdBySigntool = false;
+        String createdBy = attributes.getValue("Created-By");
+        if (createdBy != null) {
+            createdBySigntool = createdBy.indexOf("signtool") != -1;
+        }
+
+        // Use .SF to verify the mainAttributes of the manifest
+        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
+        // file, such as those created before java 1.5, then we ignore
+        // such verification.
+        if (mainAttributesEnd > 0 && !createdBySigntool) {
+            String digestAttribute = "-Digest-Manifest-Main-Attributes";
+            if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
+                throw failedVerification(jarName, signatureFile);
+            }
+        }
+
+        // Use .SF to verify the whole manifest.
+        String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
+        if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
+            Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<String, Attributes> entry = it.next();
+                StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
+                if (chunk == null) {
+                    return;
+                }
+                if (!verify(entry.getValue(), "-Digest", manifestBytes,
+                        chunk.start, chunk.end, createdBySigntool, false)) {
+                    throw invalidDigest(signatureFile, entry.getKey(), jarName);
+                }
+            }
+        }
+        metaEntries.put(signatureFile, null);
+        signatures.put(signatureFile, entries);
+    }
+
+    /**
+     * Returns a <code>boolean</code> indication of whether or not the
+     * associated jar file is signed.
+     *
+     * @return {@code true} if the JAR is signed, {@code false}
+     *         otherwise.
+     */
+    boolean isSignedJar() {
+        return certificates.size() > 0;
+    }
+
+    private boolean verify(Attributes attributes, String entry, byte[] data,
+            int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
+        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+            String algorithm = DIGEST_ALGORITHMS[i];
+            String hash = attributes.getValue(algorithm + entry);
+            if (hash == null) {
+                continue;
+            }
+
+            MessageDigest md;
+            try {
+                md = MessageDigest.getInstance(algorithm);
+            } catch (NoSuchAlgorithmException e) {
+                continue;
+            }
+            if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
+                md.update(data, start, end - 1 - start);
+            } else {
+                md.update(data, start, end - start);
+            }
+            byte[] b = md.digest();
+            byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+            return MessageDigest.isEqual(b, Base64.decode(hashBytes));
+        }
+        return ignorable;
+    }
+
+    /**
+     * Returns all of the {@link java.security.cert.Certificate} chains that
+     * were used to verify the signature on the JAR entry called
+     * {@code name}. Callers must not modify the returned arrays.
+     *
+     * @param name
+     *            the name of a JAR entry.
+     * @return an array of {@link java.security.cert.Certificate} chains.
+     */
+    Certificate[][] getCertificateChains(String name) {
+        return verifiedEntries.get(name);
+    }
+
+    /**
+     * Remove all entries from the internal collection of data held about each
+     * JAR entry in the {@code META-INF} directory.
+     */
+    void removeMetaEntries() {
+        metaEntries.clear();
+    }
+}
diff --git a/core/java/android/view/IDockDividerVisibilityListener.aidl b/core/java/android/view/IDockDividerVisibilityListener.aidl
new file mode 100644
index 0000000..a7d5cda
--- /dev/null
+++ b/core/java/android/view/IDockDividerVisibilityListener.aidl
@@ -0,0 +1,27 @@
+/**
+ * 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.view;
+
+/**
+  * Listener for showing/hiding of the dock divider. Will fire when an app is shown in side by side
+  * mode and a divider should be shown.
+  *
+  * @hide
+  */
+oneway interface IDockDividerVisibilityListener {
+    void onDockDividerVisibilityChanged(boolean visible);
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 64a046e..bd65532 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
 import android.os.IRemoteCallback;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
 import android.view.IWindowSession;
@@ -290,10 +291,10 @@
     /**
      * Create a screenshot of the applications currently displayed.
      *
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and 
+     * @param frameScale the scale to apply to the frame, only used when width = -1 and
      *                   height = -1
      */
-    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight, 
+    Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
             float frameScale);
 
     /**
@@ -348,4 +349,9 @@
      * stack size.
      */
     void setDockedStackResizing(boolean resizing);
+
+    /**
+     * Registers a listener that will be called when the dock divider changes its visibility.
+     */
+    void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener);
 }
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
new file mode 100644
index 0000000..82f6c7f
--- /dev/null
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -0,0 +1,250 @@
+/*
+ * 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.view;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * A header of a notification view
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class NotificationHeaderView extends LinearLayout {
+    private final int mHeaderMinWidth;
+    private View mAppName;
+    private View mSubTextView;
+    private OnClickListener mExpandClickListener;
+    private HeaderTouchListener mTouchListener = new HeaderTouchListener();
+    private View mExpandButton;
+    private View mIcon;
+    private TextView mChildCount;
+
+    public NotificationHeaderView(Context context) {
+        this(context, null);
+    }
+
+    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public NotificationHeaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mHeaderMinWidth = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.notification_header_shrink_min_width);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mAppName = findViewById(com.android.internal.R.id.app_name_text);
+        mSubTextView = findViewById(com.android.internal.R.id.header_sub_text);
+        mExpandButton = findViewById(com.android.internal.R.id.expand_button);
+        mIcon = findViewById(com.android.internal.R.id.icon);
+        mChildCount = (TextView) findViewById(com.android.internal.R.id.number_of_children);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int givenWidth = MeasureSpec.getSize(widthMeasureSpec);
+        final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
+        int wrapContentWidthSpec = MeasureSpec.makeMeasureSpec(givenWidth,
+                MeasureSpec.AT_MOST);
+        int wrapContentHeightSpec = MeasureSpec.makeMeasureSpec(givenHeight,
+                MeasureSpec.AT_MOST);
+        int totalWidth = getPaddingStart() + getPaddingEnd();
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                // We'll give it the rest of the space in the end
+                continue;
+            }
+            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+            int childWidthSpec = getChildMeasureSpec(wrapContentWidthSpec,
+                    lp.leftMargin + lp.rightMargin, lp.width);
+            int childHeightSpec = getChildMeasureSpec(wrapContentHeightSpec,
+                    lp.topMargin + lp.bottomMargin, lp.height);
+            child.measure(childWidthSpec, childHeightSpec);
+            totalWidth += lp.leftMargin + lp.rightMargin + child.getMeasuredWidth();
+        }
+        if (totalWidth > givenWidth) {
+            int overFlow = totalWidth - givenWidth;
+            // We are overflowing, lets shrink
+            final int appWidth = mAppName.getMeasuredWidth();
+            if (appWidth > mHeaderMinWidth) {
+                int newSize = appWidth - Math.min(appWidth - mHeaderMinWidth, overFlow);
+                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+                mAppName.measure(childWidthSpec, wrapContentHeightSpec);
+                overFlow -= appWidth - newSize;
+            }
+            if (overFlow > 0 && mSubTextView.getVisibility() != GONE) {
+                // we're still too big
+                final int subTextWidth = mSubTextView.getMeasuredWidth();
+                int newSize = Math.max(0, subTextWidth - overFlow);
+                int childWidthSpec = MeasureSpec.makeMeasureSpec(newSize, MeasureSpec.AT_MOST);
+                mSubTextView.measure(childWidthSpec, wrapContentHeightSpec);
+            }
+            totalWidth = givenWidth;
+        }
+        setMeasuredDimension(totalWidth, givenHeight);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        updateTouchListener();
+    }
+
+    private void updateTouchListener() {
+        if (mExpandClickListener != null) {
+            mTouchListener.bindTouchRects();
+        }
+    }
+
+    @Override
+    public void setOnClickListener(@Nullable OnClickListener l) {
+        mExpandClickListener = l;
+        setOnTouchListener(mExpandClickListener != null ? mTouchListener : null);
+        updateTouchListener();
+    }
+
+    public void setChildCount(int childCount) {
+        if (childCount > 0) {
+            mChildCount.setText(getContext().getString(
+                    com.android.internal.R.string.notification_children_count_bracketed,
+                    childCount));
+            mChildCount.setVisibility(VISIBLE);
+        } else {
+            mChildCount.setVisibility(GONE);
+        }
+    }
+
+    public class HeaderTouchListener implements View.OnTouchListener {
+
+        private final ArrayList<Rect> mTouchRects = new ArrayList<>();
+        private int mTouchSlop;
+        private boolean mTrackGesture;
+        private float mDownX;
+        private float mDownY;
+
+        public HeaderTouchListener() {
+        }
+
+        public void bindTouchRects() {
+            mTouchRects.clear();
+            addRectAroundViewView(mIcon);
+            addRectAroundViewView(mExpandButton);
+            addInBetweenRect();
+            mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        }
+
+        private void addInBetweenRect() {
+            final Rect r = new Rect();
+            r.top = 0;
+            r.bottom = (int) (32 * getResources().getDisplayMetrics().density);
+            Rect leftRect = mTouchRects.get(0);
+            r.left = leftRect.right;
+            Rect rightRect = mTouchRects.get(1);
+            r.right = rightRect.left;
+            mTouchRects.add(r);
+        }
+
+        private void addRectAroundViewView(View view) {
+            final Rect r = getRectAroundView(view);
+            mTouchRects.add(r);
+        }
+
+        private Rect getRectAroundView(View view) {
+            float size = 48 * getResources().getDisplayMetrics().density;
+            final Rect r = new Rect();
+            if (view.getVisibility() == GONE) {
+                view = getFirstChildNotGone();
+                r.left = (int) (view.getLeft() - size / 2.0f);
+            } else {
+                r.left = (int) ((view.getLeft() + view.getRight()) / 2.0f - size / 2.0f);
+            }
+            r.top = (int) ((view.getTop() + view.getBottom()) / 2.0f - size / 2.0f);
+            r.bottom = (int) (r.top + size);
+            r.right = (int) (r.left + size);
+            return r;
+        }
+
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            float x = event.getX();
+            float y = event.getY();
+            switch (event.getActionMasked() & MotionEvent.ACTION_MASK) {
+                case MotionEvent.ACTION_DOWN:
+                    mTrackGesture = false;
+                    if (isInside(x, y)) {
+                        mTrackGesture = true;
+                        return true;
+                    }
+                    break;
+                case MotionEvent.ACTION_MOVE:
+                    if (mTrackGesture) {
+                        if (Math.abs(mDownX - x) > mTouchSlop
+                                || Math.abs(mDownY - y) > mTouchSlop) {
+                            mTrackGesture = false;
+                        }
+                    }
+                    break;
+                case MotionEvent.ACTION_UP:
+                    if (mTrackGesture) {
+                        mExpandClickListener.onClick(NotificationHeaderView.this);
+                    }
+                    break;
+            }
+            return mTrackGesture;
+        }
+
+        private boolean isInside(float x, float y) {
+            for (int i = 0; i < mTouchRects.size(); i++) {
+                Rect r = mTouchRects.get(i);
+                if (r.contains((int) x, (int) y)) {
+                    mDownX = x;
+                    mDownY = y;
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private View getFirstChildNotGone() {
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                return child;
+            }
+        }
+        return this;
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 692fa66..da66f97 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19429,6 +19429,7 @@
      *
      * @attr ref android.R.styleable#View_minHeight
      */
+    @RemotableViewMethod
     public void setMinimumHeight(int minHeight) {
         mMinHeight = minHeight;
         requestLayout();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ce1c108..0ed72e4d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1638,6 +1638,47 @@
     }
 
     /**
+     * Helper action to set layout margin on a View.
+     */
+    private class ViewMarginEndAction extends Action {
+        public ViewMarginEndAction(int viewId, int end) {
+            this.viewId = viewId;
+            this.end = end;
+        }
+
+        public ViewMarginEndAction(Parcel parcel) {
+            viewId = parcel.readInt();
+            end = parcel.readInt();
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(TAG);
+            dest.writeInt(viewId);
+            dest.writeInt(end);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+            final View target = root.findViewById(viewId);
+            if (target == null) {
+                return;
+            }
+            ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
+            if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
+                ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(end);
+            }
+        }
+
+        public String getActionName() {
+            return "ViewMarginEndAction";
+        }
+
+        int end;
+
+        public final static int TAG = 19;
+    }
+
+    /**
      * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
      */
@@ -1942,6 +1983,9 @@
                         case SetRemoteInputsAction.TAG:
                             mActions.add(new SetRemoteInputsAction(parcel));
                             break;
+                        case ViewMarginEndAction.TAG:
+                            mActions.add(new ViewMarginEndAction(parcel));
+                            break;
                         default:
                             throw new ActionException("Tag " + tag + " not found");
                     }
@@ -2549,6 +2593,19 @@
     }
 
     /**
+     * @hide
+     * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
+     * Only works if the {@link View#getLayoutParams()} supports margins.
+     * Hidden for now since we don't want to support this for all different layout margins yet.
+     *
+     * @param viewId The id of the view to change
+     * @param endMargin the left padding in pixels
+     */
+    public void setViewLayoutMarginEnd(int viewId, int endMargin) {
+        addAction(new ViewMarginEndAction(viewId, endMargin));
+    }
+
+    /**
      * Call a method taking one boolean on a view in the layout for this RemoteViews.
      *
      * @param viewId The id of the view on which to call the method.
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 476c6a2..d666939 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6828,7 +6828,10 @@
         if (mEditor != null) mEditor.prepareCursorControllers();
     }
 
-    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+    /**
+     * @hide
+     */
+    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
             Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
             boolean useSaved) {
         Layout result = null;
@@ -7225,6 +7228,7 @@
         // of the intended optimizations as part of requestLayoutForChild.
         nullLayouts();
         requestLayout();
+        invalidate();
     }
 
     @Override
@@ -9707,7 +9711,10 @@
         }
     }
 
-    TextDirectionHeuristic getTextDirectionHeuristic() {
+    /**
+     * @hide
+     */
+    protected TextDirectionHeuristic getTextDirectionHeuristic() {
         if (hasPasswordTransformationMethod()) {
             // passwords fields should be LTR
             return TextDirectionHeuristics.LTR;
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index c992c70..cc677cc 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@
     public static final int ACTION_ZEN_ALLOW_PEEK = 261;
     public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
     public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
+    public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
new file mode 100644
index 0000000..c4ed2e1
--- /dev/null
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -0,0 +1,89 @@
+/*
+ * 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.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+/**
+ * A TextView that can float around an image on the end.
+ *
+ * @hide
+ */
+@RemoteViews.RemoteView
+public class ImageFloatingTextView extends TextView {
+
+    private boolean mHasImage;
+
+    public ImageFloatingTextView(Context context) {
+        this(context, null);
+    }
+
+    public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
+            Layout.Alignment alignment, boolean shouldEllipsize,
+            TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
+        CharSequence text = getText() == null ? "" : getText();
+        StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
+                getPaint(), wantWidth)
+                .setAlignment(alignment)
+                .setTextDirection(getTextDirectionHeuristic())
+                .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
+                .setIncludePad(getIncludeFontPadding())
+                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+        // we set the endmargin on the first 2 lines. this works just in our case but that's
+        // sufficient for now.
+        int endMargin = (int) (getResources().getDisplayMetrics().density * 52);
+        int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null;
+        if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+            builder.setIndents(margins, null);
+        } else {
+            builder.setIndents(null, margins);
+        }
+
+        return builder.build();
+    }
+
+    @RemotableViewMethod
+    public void setHasImage(boolean hasImage) {
+        mHasImage = hasImage;
+        // The new layout will be automatically created when the text is
+        // set again by the notification.
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 4d648ce..0473016f 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -93,6 +93,7 @@
     android_util_Process.cpp \
     android_util_StringBlock.cpp \
     android_util_XmlBlock.cpp \
+    android_util_jar_StrictJarFile.cpp \
     android_graphics_Canvas.cpp \
     android_graphics_Picture.cpp \
     android/graphics/AutoDecodeCancel.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index bd41c5d..f6f45b5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -176,6 +176,7 @@
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
 extern int register_android_media_RemoteDisplay(JNIEnv *env);
+extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
 extern int register_android_view_InputChannel(JNIEnv* env);
 extern int register_android_view_InputDevice(JNIEnv* env);
 extern int register_android_view_InputEventReceiver(JNIEnv* env);
@@ -1359,6 +1360,7 @@
     REG_JNI(register_android_app_backup_FullBackup),
     REG_JNI(register_android_app_ActivityThread),
     REG_JNI(register_android_app_NativeActivity),
+    REG_JNI(register_android_util_jar_StrictJarFile),
     REG_JNI(register_android_view_InputChannel),
     REG_JNI(register_android_view_InputEventReceiver),
     REG_JNI(register_android_view_InputEventSender),
@@ -1374,6 +1376,8 @@
     REG_JNI(register_android_animation_PropertyValuesHolder),
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
     REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+
+
 };
 
 /*
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
new file mode 100644
index 0000000..7f8f708
--- /dev/null
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#define LOG_TAG "StrictJarFile"
+
+#include <memory>
+#include <string>
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "jni.h"
+#include "ziparchive/zip_archive.h"
+#include "cutils/log.h"
+
+namespace android {
+
+// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ)
+static jmethodID zipEntryCtor;
+
+static void throwIoException(JNIEnv* env, const int32_t errorCode) {
+  jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode));
+}
+
+static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName) {
+  return env->NewObject(JniConstants::zipEntryClass,
+                        zipEntryCtor,
+                        entryName,
+                        NULL,  // comment
+                        static_cast<jlong>(entry.crc32),
+                        static_cast<jlong>(entry.compressed_length),
+                        static_cast<jlong>(entry.uncompressed_length),
+                        static_cast<jint>(entry.method),
+                        static_cast<jint>(0),  // time
+                        NULL,  // byte[] extra
+                        static_cast<jlong>(entry.offset));
+}
+
+static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
+  ScopedUtfChars fileChars(env, fileName);
+  if (fileChars.c_str() == NULL) {
+    return static_cast<jlong>(-1);
+  }
+
+  ZipArchiveHandle handle;
+  int32_t error = OpenArchive(fileChars.c_str(), &handle);
+  if (error) {
+    CloseArchive(handle);
+    throwIoException(env, error);
+    return static_cast<jlong>(-1);
+  }
+
+  return reinterpret_cast<jlong>(handle);
+}
+
+class IterationHandle {
+ public:
+  IterationHandle() :
+    cookie_(NULL) {
+  }
+
+  void** CookieAddress() {
+    return &cookie_;
+  }
+
+  ~IterationHandle() {
+    EndIteration(cookie_);
+  }
+
+ private:
+  void* cookie_;
+};
+
+
+static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nativeHandle,
+                                                jstring prefix) {
+  ScopedUtfChars prefixChars(env, prefix);
+  if (prefixChars.c_str() == NULL) {
+    return static_cast<jlong>(-1);
+  }
+
+  IterationHandle* handle = new IterationHandle();
+  int32_t error = 0;
+  if (prefixChars.size() == 0) {
+    error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                           handle->CookieAddress(), NULL, NULL);
+  } else {
+    ZipString entry_name(prefixChars.c_str());
+    error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                           handle->CookieAddress(), &entry_name, NULL);
+  }
+
+  if (error) {
+    throwIoException(env, error);
+    return static_cast<jlong>(-1);
+  }
+
+  return reinterpret_cast<jlong>(handle);
+}
+
+static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
+  ZipEntry data;
+  ZipString entryName;
+
+  IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
+  const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
+  if (error) {
+    delete handle;
+    return NULL;
+  }
+
+  std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
+  memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
+  entryNameCString[entryName.name_length] = '\0';
+  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+
+  return newZipEntry(env, data, entryNameString.get());
+}
+
+static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle,
+                                             jstring entryName) {
+  ScopedUtfChars entryNameChars(env, entryName);
+  if (entryNameChars.c_str() == NULL) {
+    return NULL;
+  }
+
+  ZipEntry data;
+  const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                                  ZipString(entryNameChars.c_str()), &data);
+  if (error) {
+    return NULL;
+  }
+
+  return newZipEntry(env, data, entryName);
+}
+
+static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
+  CloseArchive(reinterpret_cast<ZipArchiveHandle>(nativeHandle));
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
+  NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
+  NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
+  NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
+  NATIVE_METHOD(StrictJarFile, nativeClose, "(J)V"),
+};
+
+void register_android_util_jar_StrictJarFile(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "android/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
+
+  zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>",
+      "(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V");
+  LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>");
+}
+
+}; // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 57338be..568b601 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1877,6 +1877,11 @@
     <permission android:name="android.permission.MANAGE_APP_TOKENS"
         android:protectionLevel="signature" />
 
+    <!-- Allows System UI to register listeners for events from Window Manager.
+         @hide -->
+    <permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows the application to temporarily freeze the screen for a
          full-screen transition. -->
     <permission android:name="android.permission.FREEZE_SCREEN"
diff --git a/core/res/res/drawable/ic_arrow_drop_down.xml b/core/res/res/drawable/ic_arrow_drop_down.xml
new file mode 100644
index 0000000..c8bb411
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_drop_down.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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="14.0dp"
+    android:height="14.0dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:pathData="M16.600000,8.600000l-4.600000,4.599999 -4.600000,-4.599999 -1.400000,1.400000 6.000000,6.000000 6.000000,-6.000000z"
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/drawable/ic_arrow_up_14dp.xml b/core/res/res/drawable/ic_arrow_up_14dp.xml
new file mode 100644
index 0000000..c4cc0d1
--- /dev/null
+++ b/core/res/res/drawable/ic_arrow_up_14dp.xml
@@ -0,0 +1,24 @@
+<!--
+  ~ 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="14.0dp"
+        android:height="14.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M12.000000,8.000000l-6.000000,6.000000 1.400000,1.400000 4.600000,-4.599999 4.600000,4.599999 1.400000,-1.400000z"
+        android:fillColor="#FF000000"/>
+</vector>
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index f4bc918..62602d8 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -21,7 +21,7 @@
     android:layout_width="wrap_content"
     android:layout_height="48dp"
     android:layout_gravity="center"
-    android:layout_marginStart="8dp"
+    android:layout_marginStart="4dp"
     android:textColor="@color/secondary_text_material_light"
     android:singleLine="true"
     android:ellipsize="end"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index edaf020..2a89faa 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -17,13 +17,14 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/actions_container"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
+        android:layout_height="wrap_content"
+        android:layout_marginStart="-16dp"
+        android:layout_marginEnd="-16dp">
     <LinearLayout
             android:id="@+id/actions"
             android:layout_width="match_parent"
             android:layout_height="56dp"
-            android:paddingEnd="8dp"
+            android:paddingEnd="4dp"
             android:orientation="horizontal"
             android:visibility="gone"
             android:background="#ffeeeeee"
diff --git a/core/res/res/layout/notification_material_media_action.xml b/core/res/res/layout/notification_material_media_action.xml
index 1d52e54..19a6f84 100644
--- a/core/res/res/layout/notification_material_media_action.xml
+++ b/core/res/res/layout/notification_material_media_action.xml
@@ -19,10 +19,12 @@
     style="@android:style/Widget.Material.Button.Borderless.Small"
     android:id="@+id/action0"
     android:layout_width="48dp"
-    android:layout_height="match_parent"
-    android:layout_marginLeft="2dp"
-    android:layout_marginRight="2dp"
-    android:layout_weight="1"
+    android:layout_height="48dp"
+    android:paddingBottom="8dp"
+    android:paddingTop="8dp"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp"
+    android:layout_marginEnd="2dp"
     android:gravity="center"
     android:background="@drawable/notification_material_media_action_background"
     />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
new file mode 100644
index 0000000..aceae9f
--- /dev/null
+++ b/core/res/res/layout/notification_template_header.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<NotificationHeaderView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/notification_header"
+    android:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="48dp"
+    android:clipChildren="false"
+    android:layout_gravity="start|top"
+    android:gravity="center_vertical"
+    android:paddingTop="5dp"
+    android:paddingBottom="16dp"
+    android:paddingStart="@dimen/notification_content_margin_start"
+    android:paddingEnd="16dp">
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="18dp"
+        android:layout_height="18dp"
+        android:layout_marginEnd="3dp"
+        />
+    <TextView
+        android:id="@+id/number_of_children"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification"
+        android:layout_marginEnd="3dp"
+        android:layout_marginStart="2dp"
+        android:visibility="gone"
+        android:singleLine="true"
+        />
+    <TextView
+        android:id="@+id/app_name_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="3dp"
+        android:layout_marginEnd="2dp"
+        android:singleLine="true"
+        />
+    <TextView
+        android:id="@+id/sub_text_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:text="@string/notification_header_divider_symbol"
+        android:visibility="gone"/>
+    <TextView
+        android:id="@+id/header_sub_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:visibility="gone"
+        android:singleLine="true"/>
+    <TextView
+        android:id="@+id/content_info_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"/>
+    <TextView
+        android:id="@+id/header_content_info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:visibility="gone"
+        android:maxWidth="72dp"
+        android:singleLine="true"/>
+    <TextView
+        android:id="@+id/time_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"/>
+    <ViewStub
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:layout="@layout/notification_template_part_time"
+        android:visibility="gone"
+        />
+    <ViewStub
+        android:id="@+id/chronometer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="2dp"
+        android:layout_marginEnd="2dp"
+        android:layout="@layout/notification_template_part_chronometer"
+        android:visibility="gone"
+        />
+    <ImageView
+        android:id="@+id/expand_button"
+        android:background="@null"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingTop="1dp"
+        android:src="@drawable/ic_arrow_drop_down"
+        android:visibility="gone"
+        />
+</NotificationHeaderView>
+
diff --git a/core/res/res/layout/notification_template_icon_group.xml b/core/res/res/layout/notification_template_icon_group.xml
deleted file mode 100644
index fa66163..0000000
--- a/core/res/res/layout/notification_template_icon_group.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:internal="http://schemas.android.com/apk/prv/res/android"
-    android:layout_width="@dimen/notification_large_icon_width"
-    android:layout_height="@dimen/notification_large_icon_height"
-    android:id="@+id/icon_group"
-    >
-    <ImageView android:id="@+id/icon"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginTop="12dp"
-        android:layout_marginBottom="12dp"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
-        android:scaleType="centerInside"
-        />
-    <ImageView android:id="@+id/right_icon"
-        android:layout_width="16dp"
-        android:layout_height="16dp"
-        android:padding="3dp"
-        android:layout_gravity="end|bottom"
-        android:scaleType="centerInside"
-        android:visibility="gone"
-        android:layout_marginEnd="8dp"
-        android:layout_marginBottom="8dp"
-        />
-</FrameLayout>
-
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 94bbec8..b69eb24 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -18,24 +18,32 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
-    android:layout_height="64dp"
+    android:layout_height="wrap_content"
     android:tag="base"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        />
+    <include layout="@layout/notification_template_header" />
     <LinearLayout
         android:id="@+id/notification_main_column"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_gravity="top"
-        android:layout_marginStart="@dimen/notification_large_icon_width"
-        android:minHeight="@dimen/notification_large_icon_height"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:layout_marginEnd="@dimen/notification_content_margin_end"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:minHeight="@dimen/notification_min_content_height"
         android:orientation="vertical"
         >
         <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
         <include layout="@layout/notification_template_part_line3" />
     </LinearLayout>
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:layout_marginBottom="11dp"
+        android:layout_marginEnd="@dimen/notification_content_margin_end">
+        <include layout="@layout/notification_template_progress" />
+    </FrameLayout>
+    <include layout="@layout/notification_template_right_icon" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 97df978e..8c78b8d 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -14,71 +14,56 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:orientation="vertical"
     android:tag="big"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        />
-    <LinearLayout
-        android:id="@+id/notification_main_column"
+    <FrameLayout
+        android:id="@+id/status_bar_latest_event_content"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/notification_min_height"
+        android:layout_gravity="top"
+        android:tag="base"
+        >
+        <include layout="@layout/notification_template_header" />
+        <LinearLayout
+            android:id="@+id/notification_main_column"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="top"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:minHeight="@dimen/notification_min_content_height"
+            android:orientation="vertical"
+            >
+            <include layout="@layout/notification_template_part_line1" />
+            <include layout="@layout/notification_template_part_line3" />
+        </LinearLayout>
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginBottom="11dp"
+            android:layout_marginEnd="@dimen/notification_content_margin_end">
+            <include layout="@layout/notification_template_progress" />
+        </FrameLayout>
+        <include layout="@layout/notification_template_right_icon" />
+    </FrameLayout>
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:id="@+id/action_divider"
+        android:visibility="gone"
+        android:background="@drawable/notification_template_divider" />
+    <include
+        layout="@layout/notification_material_action_list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_gravity="top"
-        android:layout_marginStart="@dimen/notification_large_icon_width"
-        android:minHeight="@dimen/notification_large_icon_height"
-        android:orientation="vertical"
-        >
-        <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:orientation="horizontal"
-            android:gravity="top"
-            >
-            <TextView android:id="@+id/big_text"
-                android:textAppearance="@style/TextAppearance.Material.Notification"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:singleLine="false"
-                android:visibility="gone"
-                />
-            <ImageView android:id="@+id/profile_badge_large_template"
-                android:layout_width="@dimen/notification_badge_size"
-                android:layout_height="@dimen/notification_badge_size"
-                android:layout_weight="0"
-                android:layout_marginStart="4dp"
-                android:scaleType="fitCenter"
-                android:visibility="gone"
-                android:contentDescription="@string/notification_work_profile_content_description"
-                />
-        </LinearLayout>
-        <include
-            layout="@layout/notification_template_part_line3"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            />
-        <ImageView
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:layout_marginTop="10dp"
-            android:id="@+id/action_divider"
-            android:visibility="gone"
-            android:background="@drawable/notification_template_divider" />
-        <include
-            layout="@layout/notification_material_action_list"
-            android:layout_marginStart="-8dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-    </LinearLayout>
-</FrameLayout>
+        />
+</LinearLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media.xml b/core/res/res/layout/notification_template_material_big_media.xml
index 7fd93de..0427c8a 100644
--- a/core/res/res/layout/notification_template_material_big_media.xml
+++ b/core/res/res/layout/notification_template_material_big_media.xml
@@ -15,44 +15,53 @@
   ~ limitations under the License
   -->
 
+<!-- Layout for the expanded media notification -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="128dp"
     android:background="#00000000"
-    android:tag="bigMedia"
+    android:tag="bigMediaNarrow"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        />
+    <include layout="@layout/notification_template_header"
+        android:layout_width="fill_parent"
+        android:layout_height="48dp"
+        android:layout_marginEnd="106dp"/>
     <LinearLayout
+        android:id="@+id/notification_main_column"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/notification_large_icon_width"
-        android:minHeight="@dimen/notification_large_icon_height"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:layout_marginEnd="24dp"
+        android:layout_toStartOf="@id/right_icon"
+        android:minHeight="@dimen/notification_min_content_height"
         android:orientation="vertical"
         >
         <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
         <include layout="@layout/notification_template_part_line3" />
     </LinearLayout>
     <LinearLayout
         android:id="@+id/media_actions"
-        android:layout_width="match_parent"
-        android:layout_height="48dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
         android:layout_alignParentBottom="true"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
+        android:layout_alignParentStart="true"
+        android:paddingStart="8dp"
+        android:paddingBottom="8dp"
         android:orientation="horizontal"
         android:layoutDirection="ltr"
         >
         <!-- media buttons will be added here -->
     </LinearLayout>
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_above="@id/media_actions"
-        android:id="@+id/action_divider"
-        android:background="@drawable/notification_template_divider_media" />
+
+    <ImageView android:id="@+id/right_icon"
+        android:layout_width="96dp"
+        android:layout_height="96dp"
+        android:layout_marginEnd="16dp"
+        android:layout_marginTop="16dp"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentTop="true"
+        android:scaleType="centerCrop"
+        />
 </RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_media_narrow.xml b/core/res/res/layout/notification_template_material_big_media_narrow.xml
deleted file mode 100644
index 807cfaf..0000000
--- a/core/res/res/layout/notification_template_material_big_media_narrow.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<!-- Layout to be used with only max 3 actions. It has a much larger picture at the left side-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/status_bar_latest_event_content"
-    android:layout_width="match_parent"
-    android:layout_height="128dp"
-    android:background="#00000000"
-    android:tag="bigMediaNarrow"
-    >
-    <ImageView android:id="@+id/icon"
-        android:layout_width="128dp"
-        android:layout_height="128dp"
-        android:scaleType="centerCrop"
-        />
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="12dp"
-        android:layout_toEndOf="@id/icon"
-        android:minHeight="@dimen/notification_large_icon_height"
-        android:orientation="vertical"
-        >
-        <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
-        <include layout="@layout/notification_template_part_line3" />
-    </LinearLayout>
-    <LinearLayout
-        android:id="@+id/media_actions"
-        android:layout_width="match_parent"
-        android:layout_height="48dp"
-        android:layout_toEndOf="@id/icon"
-        android:layout_alignParentBottom="true"
-        android:layout_marginStart="12dp"
-        android:layout_marginEnd="12dp"
-        android:orientation="horizontal"
-        android:layoutDirection="ltr"
-        >
-        <!-- media buttons will be added here -->
-    </LinearLayout>
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_toEndOf="@id/icon"
-        android:layout_above="@id/media_actions"
-        android:id="@+id/action_divider"
-        android:background="@drawable/notification_template_divider_media" />
-</RelativeLayout>
diff --git a/core/res/res/layout/notification_template_material_big_picture.xml b/core/res/res/layout/notification_template_material_big_picture.xml
index f3768b5..74e7775 100644
--- a/core/res/res/layout/notification_template_material_big_picture.xml
+++ b/core/res/res/layout/notification_template_material_big_picture.xml
@@ -21,39 +21,45 @@
     android:layout_height="match_parent"
     android:tag="bigPicture"
     >
-    <ImageView
-        android:id="@+id/big_picture"
-        android:layout_width="match_parent"
-        android:layout_height="192dp"
-        android:layout_marginTop="64dp"
-        android:layout_gravity="bottom"
-        android:scaleType="centerCrop"
-        />
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="6dp"
-        android:layout_marginTop="64dp"
-        android:scaleType="fitXY"
-        android:src="@drawable/title_bar_shadow"
-        />
-    <include layout="@layout/notification_template_material_base"
-        android:layout_width="match_parent"
-        android:layout_height="64dp"
-        />
-  <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="208dp"
-        android:paddingStart="@dimen/notification_large_icon_width"
-        android:layout_gravity="bottom"
-        android:background="#CCEEEEEE"
-        >
-        <include
-            layout="@layout/notification_material_action_list"
-            android:id="@+id/actions"
-            android:layout_gravity="bottom"
+    <include layout="@layout/notification_template_header" />
+    <include layout="@layout/notification_template_right_icon" />
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="top"
+            android:paddingStart="@dimen/notification_content_margin_start"
+            android:paddingEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin_top"
+            android:clipToPadding="false"
+            android:orientation="vertical"
+            >
+        <LinearLayout
+            android:id="@+id/notification_main_column"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            />
-    </FrameLayout>
+            android:orientation="vertical">
+            <include layout="@layout/notification_template_part_line1"/>
+            <include layout="@layout/notification_template_progress"/>
+            <include layout="@layout/notification_template_part_line3"/>
+        </LinearLayout>
+        <ImageView
+                android:id="@+id/big_picture"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:adjustViewBounds="true"
+                android:layout_weight="1"
+                android:layout_marginTop="13dp"
+                android:layout_marginBottom="16dp"
+                android:scaleType="centerCrop"
+                />
+        <ImageView
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:layout_marginStart="-16dp"
+                android:layout_marginEnd="-16dp"
+                android:id="@+id/action_divider"
+                android:visibility="gone"
+                android:background="@drawable/notification_template_divider" />
+        <include layout="@layout/notification_material_action_list" />
+    </LinearLayout>
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 7ae29fb..354c0fb 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -21,31 +21,30 @@
     android:layout_height="wrap_content"
     android:tag="bigText"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        />
+    <include layout="@layout/notification_template_header" />
     <LinearLayout
         android:id="@+id/notification_main_column"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="top"
-        android:layout_marginStart="@dimen/notification_large_icon_width"
-        android:minHeight="@dimen/notification_large_icon_height"
+        android:paddingStart="@dimen/notification_content_margin_start"
+        android:paddingEnd="@dimen/notification_content_margin_end"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:clipToPadding="false"
+        android:minHeight="@dimen/notification_min_content_height"
         android:orientation="vertical"
         >
         <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
+        <include layout="@layout/notification_template_progress" />
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
-            android:layout_marginBottom="10dp"
+            android:paddingBottom="13dp"
             android:orientation="horizontal"
             android:gravity="top"
             android:layout_weight="1"
             >
-            <TextView android:id="@+id/big_text"
+            <com.android.internal.widget.ImageFloatingTextView android:id="@+id/big_text"
                 android:textAppearance="@style/TextAppearance.Material.Notification"
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
@@ -66,27 +65,12 @@
         <ImageView
             android:layout_width="match_parent"
             android:layout_height="1dp"
+            android:layout_marginStart="-16dp"
+            android:layout_marginEnd="-16dp"
             android:id="@+id/action_divider"
             android:visibility="gone"
             android:background="@drawable/notification_template_divider" />
-        <include
-            layout="@layout/notification_material_action_list"
-            android:layout_marginStart="-8dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-        <ImageView
-            android:layout_width="match_parent"
-            android:layout_height="1dip"
-            android:id="@+id/overflow_divider"
-            android:visibility="visible"
-            android:background="@drawable/notification_template_divider" />
-        <include
-            layout="@layout/notification_template_part_line3"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginBottom="10dp" />
+        <include layout="@layout/notification_material_action_list" />
     </LinearLayout>
+    <include layout="@layout/notification_template_right_icon" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_inbox.xml b/core/res/res/layout/notification_template_material_inbox.xml
index 950ae40..4f12d76 100644
--- a/core/res/res/layout/notification_template_material_inbox.xml
+++ b/core/res/res/layout/notification_template_material_inbox.xml
@@ -21,21 +21,26 @@
     android:layout_height="wrap_content"
     android:tag="inbox"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        />
+    <include layout="@layout/notification_template_header" />
     <LinearLayout
         android:id="@+id/notification_main_column"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_gravity="top"
-        android:layout_marginStart="@dimen/notification_large_icon_width"
-        android:minHeight="@dimen/notification_large_icon_height"
+        android:paddingStart="@dimen/notification_content_margin_start"
+        android:paddingEnd="@dimen/notification_content_margin_end"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:minHeight="@dimen/notification_min_content_height"
+        android:clipToPadding="false"
         android:orientation="vertical"
         >
-        <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
+        <include layout="@layout/notification_template_part_line1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+        <include layout="@layout/notification_template_progress"
+            android:layout_width="match_parent"
+            android:layout_height="15dp"
+            android:layout_marginTop="4dp"/>
 
         <!-- We can't have another vertical linear layout here with weight != 0 so this forces us to
              put the badge on the first line. -->
@@ -43,7 +48,6 @@
             android:layout_width="match_parent"
             android:layout_weight="1"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:orientation="horizontal"
             >
             <TextView android:id="@+id/inbox_text0"
@@ -69,7 +73,6 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
@@ -79,7 +82,6 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
@@ -89,7 +91,6 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
@@ -99,7 +100,6 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
@@ -109,7 +109,6 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
@@ -119,27 +118,15 @@
             android:textAppearance="@style/TextAppearance.Material.Notification"
             android:layout_width="match_parent"
             android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
             android:singleLine="true"
             android:ellipsize="end"
             android:visibility="gone"
             android:layout_weight="1"
             />
-        <TextView android:id="@+id/inbox_more"
-            android:textAppearance="@style/TextAppearance.Material.Notification"
-            android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_marginEnd="8dp"
-            android:singleLine="true"
-            android:ellipsize="end"
-            android:visibility="gone"
-            android:layout_weight="1"
-            android:text="@android:string/notification_inbox_ellipsis"
-            />
         <FrameLayout
             android:id="@+id/inbox_end_pad"
             android:layout_width="match_parent"
-            android:layout_height="10dp"
+            android:layout_height="13dp"
             android:visibility="gone"
             android:layout_weight="0"
         />
@@ -148,27 +135,10 @@
             android:layout_height="1dip"
             android:id="@+id/action_divider"
             android:visibility="gone"
+            android:layout_marginStart="-16dp"
+            android:layout_marginEnd="-16dp"
             android:background="@drawable/notification_template_divider" />
-        <include
-            layout="@layout/notification_material_action_list"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="-8dp"
-            android:layout_marginRight="-8dp"
-            android:layout_weight="0"
-            />
-        <ImageView
-            android:layout_width="match_parent"
-            android:layout_height="1dip"
-            android:id="@+id/overflow_divider"
-            android:visibility="visible"
-            android:background="@drawable/notification_template_divider" />
-        <include
-            layout="@layout/notification_template_part_line3"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginBottom="10dp" />
+        <include layout="@layout/notification_material_action_list" />
     </LinearLayout>
+    <include layout="@layout/notification_template_right_icon" />
 </FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index f6c22c8..dc4afb8 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,40 +15,52 @@
   ~ limitations under the License
   -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout
     android:id="@+id/status_bar_latest_event_content"
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="64dp"
-    android:orientation="horizontal"
-    android:background="#00000000"
+    android:layout_height="wrap_content"
     android:tag="media"
     >
-    <include layout="@layout/notification_template_icon_group"
-        android:layout_width="@dimen/notification_large_icon_width"
-        android:layout_height="@dimen/notification_large_icon_height"
-        android:layout_weight="0"
-        />
+    <include layout="@layout/notification_template_header"
+        android:layout_width="fill_parent"
+        android:layout_height="48dp"
+        android:layout_marginEnd="106dp"/>
     <LinearLayout
+        android:id="@+id/notification_main_column"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:layout_gravity="fill_vertical"
-        android:minHeight="@dimen/notification_large_icon_height"
-        android:orientation="vertical"
-        >
-        <include layout="@layout/notification_template_part_line1" />
-        <include layout="@layout/notification_template_part_line2" />
-        <include layout="@layout/notification_template_part_line3" />
-    </LinearLayout>
-    <LinearLayout
-        android:id="@+id/media_actions"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="6dp"
-        android:layout_gravity="center_vertical|end"
+        android:layout_height="@dimen/notification_min_content_height"
+        android:background="#00000000"
         android:orientation="horizontal"
-        android:layoutDirection="ltr"
+        android:layout_marginStart="@dimen/notification_content_margin_start"
+        android:layout_marginTop="@dimen/notification_content_margin_top"
+        android:layout_marginEnd="72dp"
+        android:tag="media"
         >
-        <!-- media buttons will be added here -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="1"
+            android:minHeight="@dimen/notification_min_content_height"
+            android:orientation="vertical"
+            >
+            <include layout="@layout/notification_template_part_line1"/>
+            <include layout="@layout/notification_template_progress"/>
+            <include layout="@layout/notification_template_part_line3"/>
+        </LinearLayout>
+        <LinearLayout
+            android:id="@+id/media_actions"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="bottom|end"
+            android:layout_marginStart="10dp"
+            android:layout_marginBottom="8dp"
+            android:layoutDirection="ltr"
+            android:orientation="horizontal"
+            >
+            <!-- media buttons will be added here -->
+        </LinearLayout>
     </LinearLayout>
-</LinearLayout>
+    <include layout="@layout/notification_template_right_icon" />
+</FrameLayout>
diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml
index 1f0430e..c5ffbea 100644
--- a/core/res/res/layout/notification_template_part_chronometer.xml
+++ b/core/res/res/layout/notification_template_part_chronometer.xml
@@ -18,9 +18,7 @@
     android:textAppearance="@style/TextAppearance.Material.Notification.Time"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:layout_gravity="center"
+    android:layout_marginEnd="4dp"
     android:layout_weight="0"
     android:singleLine="true"
-    android:gravity="center"
-    android:paddingStart="8dp"
     />
diff --git a/core/res/res/layout/notification_template_part_line1.xml b/core/res/res/layout/notification_template_part_line1.xml
index 78bc1ed..e7ac408 100644
--- a/core/res/res/layout/notification_template_part_line1.xml
+++ b/core/res/res/layout/notification_template_part_line1.xml
@@ -19,30 +19,25 @@
     android:id="@+id/line1"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginEnd="8dp"
     android:orientation="horizontal"
+    android:layout_marginBottom="1dp"
     >
     <TextView android:id="@+id/title"
         android:textAppearance="@style/TextAppearance.Material.Notification.Title"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:singleLine="true"
         android:ellipsize="marquee"
         android:fadingEdge="horizontal"
-        android:layout_weight="1"
         />
-    <ViewStub android:id="@+id/time"
-        android:layout_width="wrap_content"
+    <TextView android:id="@+id/text_line_1"
+        android:textAppearance="@style/TextAppearance.Material.Notification"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:visibility="gone"
-        android:layout="@layout/notification_template_part_time"
-        />
-    <ViewStub android:id="@+id/chronometer"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="0"
-        android:visibility="gone"
-        android:layout="@layout/notification_template_part_chronometer"
+        android:gravity="end|bottom"
+        android:layout_marginStart="16dp"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:fadingEdge="horizontal"
         />
 </LinearLayout>
diff --git a/core/res/res/layout/notification_template_part_line2.xml b/core/res/res/layout/notification_template_part_line2.xml
deleted file mode 100644
index db43271..0000000
--- a/core/res/res/layout/notification_template_part_line2.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:orientation="horizontal"
-        android:gravity="center_vertical"
-        >
-        <TextView
-            android:id="@+id/text2"
-            android:textAppearance="@style/TextAppearance.Material.Notification.Line2"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="-1dp"
-            android:layout_marginBottom="-1dp"
-            android:singleLine="true"
-            android:fadingEdge="horizontal"
-            android:ellipsize="marquee"
-            android:visibility="gone"
-            android:layout_weight="1"
-        />
-        <ImageView android:id="@+id/profile_badge_line2"
-            android:layout_width="@dimen/notification_badge_size"
-            android:layout_height="@dimen/notification_badge_size"
-            android:layout_weight="0"
-            android:layout_marginStart="4dp"
-            android:scaleType="fitCenter"
-            android:visibility="gone"
-            android:contentDescription="@string/notification_work_profile_content_description"
-            />
-    </LinearLayout>
-    <ViewStub
-        android:id="@android:id/progress"
-        android:layout="@layout/notification_template_progressbar"
-        android:layout_width="match_parent"
-        android:layout_height="15dp"
-        android:layout_marginEnd="8dp"
-        android:visibility="gone"
-        android:layout_weight="0"
-        />
-</merge>
diff --git a/core/res/res/layout/notification_template_part_line3.xml b/core/res/res/layout/notification_template_part_line3.xml
index da3c5c5..76337ac 100644
--- a/core/res/res/layout/notification_template_part_line3.xml
+++ b/core/res/res/layout/notification_template_part_line3.xml
@@ -19,7 +19,6 @@
     android:id="@+id/line3"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginEnd="8dp"
     android:orientation="horizontal"
     android:gravity="center_vertical"
     >
@@ -33,16 +32,6 @@
         android:ellipsize="marquee"
         android:fadingEdge="horizontal"
         />
-    <TextView android:id="@+id/info"
-        android:textAppearance="@style/TextAppearance.Material.Notification.Info"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_weight="0"
-        android:singleLine="true"
-        android:gravity="center"
-        android:paddingStart="8dp"
-        />
     <ImageView android:id="@+id/profile_badge_line3"
         android:layout_width="@dimen/notification_badge_size"
         android:layout_height="@dimen/notification_badge_size"
diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml
index 37c7ebe..442ff8c 100644
--- a/core/res/res/layout/notification_template_part_time.xml
+++ b/core/res/res/layout/notification_template_part_time.xml
@@ -19,8 +19,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center"
+    android:layout_marginEnd="4dp"
     android:layout_weight="0"
     android:singleLine="true"
-    android:gravity="center"
-    android:paddingStart="8dp"
     />
diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/layout/notification_template_progress.xml
similarity index 64%
rename from core/res/res/drawable/notification_icon_legacy_bg.xml
rename to core/res/res/layout/notification_template_progress.xml
index cc5755d..85532ad 100644
--- a/core/res/res/drawable/notification_icon_legacy_bg.xml
+++ b/core/res/res/layout/notification_template_progress.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2014 The Android Open Source Project
+  ~ 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.
@@ -14,9 +14,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="oval">
-    <solid
-            android:color="@color/notification_icon_bg_color"/>
-</shape>
+<ViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/progress"
+    android:layout="@layout/notification_template_progressbar"
+    android:layout_width="match_parent"
+    android:layout_height="15dp"
+    android:visibility="gone"
+    />
diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/layout/notification_template_right_icon.xml
similarity index 62%
copy from core/res/res/drawable/notification_icon_legacy_bg.xml
copy to core/res/res/layout/notification_template_right_icon.xml
index cc5755d..3b358ab 100644
--- a/core/res/res/drawable/notification_icon_legacy_bg.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2014 The Android Open Source Project
+  ~ 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.
@@ -15,8 +15,13 @@
   ~ limitations under the License
   -->
 
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="oval">
-    <solid
-            android:color="@color/notification_icon_bg_color"/>
-</shape>
+<ImageView android:id="@+id/right_icon" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="40dp"
+    android:layout_height="40dp"
+    android:layout_marginEnd="16dp"
+    android:layout_marginTop="32dp"
+    android:layout_gravity="top|end"
+    android:scaleType="centerCrop"
+    />
+
+
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 7f8c460..dad68ba 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -130,12 +130,9 @@
     <drawable name="notification_template_divider">#29000000</drawable>
     <drawable name="notification_template_divider_media">#29ffffff</drawable>
 
-    <color name="notification_icon_bg_color">#ff9e9e9e</color>
+    <color name="notification_icon_default_color">#ff616161</color>
     <color name="notification_action_color_filter">@color/secondary_text_material_light</color>
 
-    <color name="notification_media_primary_color">@color/primary_text_material_dark</color>
-    <color name="notification_media_secondary_color">@color/secondary_text_material_dark</color>
-
     <color name="notification_progress_background_color">@color/secondary_text_material_light</color>
 
     <!-- Keyguard colors -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 01daf26..7b4becc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -130,11 +130,38 @@
     <!-- Default padding for dialogs. -->
     <dimen name="dialog_padding">16dp</dimen>
 
+    <!-- The margin on the start of the content view -->
+    <dimen name="notification_content_margin_start">16dp</dimen>
+
+    <!-- The margin on the end of the content view -->
+    <dimen name="notification_content_margin_end">16dp</dimen>
+
+    <!-- The margin on the end of the content view with a picture.-->
+    <dimen name="notification_content_picture_margin">56dp</dimen>
+
+    <!-- height of the content margin to accomodate for the header -->
+    <dimen name="notification_content_margin_top">30dp</dimen>
+
+    <!-- height of notification header view if present -->
+    <dimen name="notification_header_height">32dp</dimen>
+
+    <!-- Height of a small notification in the status bar -->
+    <dimen name="notification_min_height">84dp</dimen>
+
     <!-- The width of the big icons in notifications. -->
     <dimen name="notification_large_icon_width">64dp</dimen>
     <!-- The width of the big icons in notifications. -->
     <dimen name="notification_large_icon_height">64dp</dimen>
 
+    <!--  Min height of the notification content. -->
+    <dimen name="notification_min_content_height">54dp</dimen>
+
+    <!-- The minimum width of the app name in the header if it shrinks -->
+    <dimen name="notification_header_shrink_min_width">72dp</dimen>
+
+    <!-- The minimum height of the content if there is a picture present with big picture -->
+    <dimen name="notification_big_picture_content_min_height_with_picture">41dp</dimen>
+
     <!-- Minimum width of the search view text entry area. -->
     <dimen name="search_view_text_min_width">160dip</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8a00294..d6dd842 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -531,6 +531,12 @@
          [CHAR LIMIT=4] -->
     <string name="status_bar_notification_info_overflow">999+</string>
 
+    <!-- The number of notifications in the notification header. An example would be (2) or (12) -->
+    <string name="notification_children_count_bracketed">(<xliff:g id="notificationCount" example="1">%d</xliff:g>)</string>
+
+    <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
+    <string name="notification_header_divider_symbol" translatable="false">•</string>
+
     <!-- Displayed to the user to tell them that they have started up the phone in "safe mode" -->
     <string name="safeMode">Safe mode</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8cf1134..9dbdaaa 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -197,11 +197,8 @@
   <java-symbol type="id" name="inbox_text4" />
   <java-symbol type="id" name="inbox_text5" />
   <java-symbol type="id" name="inbox_text6" />
-  <java-symbol type="id" name="inbox_more" />
-  <java-symbol type="id" name="inbox_end_pad" />
   <java-symbol type="id" name="status_bar_latest_event_content" />
   <java-symbol type="id" name="action_divider" />
-  <java-symbol type="id" name="overflow_divider" />
   <java-symbol type="id" name="notification_main_column" />
   <java-symbol type="id" name="sms_short_code_confirm_message" />
   <java-symbol type="id" name="sms_short_code_detail_layout" />
@@ -218,7 +215,6 @@
   <java-symbol type="id" name="pin_error_message" />
   <java-symbol type="id" name="timePickerLayout" />
   <java-symbol type="id" name="profile_badge_large_template" />
-  <java-symbol type="id" name="profile_badge_line2" />
   <java-symbol type="id" name="profile_badge_line3" />
   <java-symbol type="id" name="transitionPosition" />
   <java-symbol type="id" name="selection_start_handle" />
@@ -1877,15 +1873,11 @@
   <java-symbol type="layout" name="notification_template_material_inbox" />
   <java-symbol type="layout" name="notification_template_material_media" />
   <java-symbol type="layout" name="notification_template_material_big_media" />
-  <java-symbol type="layout" name="notification_template_material_big_media_narrow" />
   <java-symbol type="layout" name="notification_template_material_big_text" />
-  <java-symbol type="layout" name="notification_template_icon_group" />
+  <java-symbol type="layout" name="notification_template_header" />
   <java-symbol type="layout" name="notification_material_media_action" />
   <java-symbol type="color" name="notification_action_color_filter" />
-  <java-symbol type="color" name="notification_icon_bg_color" />
-  <java-symbol type="drawable" name="notification_icon_legacy_bg" />
-  <java-symbol type="color" name="notification_media_primary_color" />
-  <java-symbol type="color" name="notification_media_secondary_color" />
+  <java-symbol type="color" name="notification_icon_default_color" />
   <java-symbol type="color" name="notification_progress_background_color" />
   <java-symbol type="id" name="media_actions" />
 
@@ -2352,4 +2344,24 @@
   <java-symbol type="id" name="suggestionContainer" />
   <java-symbol type="id" name="addToDictionaryButton" />
   <java-symbol type="id" name="deleteButton" />
+
+  <java-symbol type="string" name="notification_children_count_bracketed" />
+  <java-symbol type="id" name="app_name_text" />
+  <java-symbol type="id" name="number_of_children" />
+  <java-symbol type="id" name="header_sub_text" />
+  <java-symbol type="id" name="expand_button" />
+  <java-symbol type="id" name="notification_header" />
+  <java-symbol type="id" name="header_content_info" />
+  <java-symbol type="id" name="time_divider" />
+  <java-symbol type="id" name="sub_text_divider" />
+  <java-symbol type="id" name="content_info_divider" />
+  <java-symbol type="id" name="text_line_1" />
+  <java-symbol type="drawable" name="ic_arrow_up_14dp" />
+  <java-symbol type="dimen" name="notification_header_height" />
+  <java-symbol type="dimen" name="notification_big_picture_content_min_height_with_picture" />
+  <java-symbol type="dimen" name="notification_header_shrink_min_width" />
+  <java-symbol type="dimen" name="notification_content_margin_start" />
+  <java-symbol type="dimen" name="notification_content_margin_end" />
+  <java-symbol type="dimen" name="notification_content_picture_margin" />
+  <java-symbol type="dimen" name="notification_content_margin_top" />
 </resources>
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index c5e2ae6..ddbdc87 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -16,7 +16,11 @@
 
 package android.widget;
 
+import static android.widget.espresso.TextViewActions.mouseDoubleClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseLongClickOnTextAtIndex;
+import static android.widget.espresso.TextViewActions.mouseDoubleClickAndDragOnText;
 import static android.widget.espresso.TextViewActions.mouseDragOnText;
+import static android.widget.espresso.TextViewActions.mouseLongClickAndDragOnText;
 import static android.widget.espresso.TextViewAssertions.hasSelection;
 import static android.support.test.espresso.Espresso.onView;
 import static android.support.test.espresso.action.ViewActions.click;
@@ -62,4 +66,104 @@
 
         onView(withId(R.id.textview)).check(hasSelection("llo wor"));
     }
+
+    @SmallTest
+    public void testSelectTextByLongClick() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+        onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(0));
+        onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+        onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+                helloWorld.indexOf("world")));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+                helloWorld.indexOf("llo")));
+        onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+        onView(withId(R.id.textview)).perform(mouseLongClickOnTextAtIndex(
+                helloWorld.indexOf("rld")));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+    }
+
+    @SmallTest
+    public void testSelectTextByDoubleClick() throws Exception {
+        getActivity();
+
+        final String helloWorld = "Hello world!";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(helloWorld));
+
+        onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(0));
+        onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+        onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+                helloWorld.indexOf("world")));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+
+        onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+                helloWorld.indexOf("llo")));
+        onView(withId(R.id.textview)).check(hasSelection("Hello"));
+
+        onView(withId(R.id.textview)).perform(mouseDoubleClickOnTextAtIndex(
+                helloWorld.indexOf("rld")));
+        onView(withId(R.id.textview)).check(hasSelection("world"));
+    }
+
+    @SmallTest
+    public void testSelectTextByDoubleClickAndDrag() throws Exception {
+        getActivity();
+
+        final String text = "abcd efg hijk lmn";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseDoubleClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+    }
+
+    @SmallTest
+    public void testSelectTextByDoubleClickAndDrag_reverse() throws Exception {
+        getActivity();
+
+        final String text = "abcd efg hijk lmn";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseDoubleClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+    }
+
+    @SmallTest
+    public void testSelectTextByLongPressAndDrag() throws Exception {
+        getActivity();
+
+        final String text = "abcd efg hijk lmn";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseLongClickAndDragOnText(text.indexOf("f"), text.indexOf("j")));
+        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+    }
+
+    @SmallTest
+    public void testSelectTextByLongPressAndDrag_reverse() throws Exception {
+        getActivity();
+
+        final String text = "abcd efg hijk lmn";
+        onView(withId(R.id.textview)).perform(click());
+        onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        onView(withId(R.id.textview)).perform(
+                mouseLongClickAndDragOnText(text.indexOf("j"), text.indexOf("f")));
+        onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 78a0a59..4614505 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -34,12 +34,15 @@
 import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
 import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
 import static org.hamcrest.Matchers.allOf;
 
 import com.android.frameworks.coretests.R;
 
+import android.support.test.espresso.NoMatchingRootException;
+import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewInteraction;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -165,18 +168,21 @@
         assertFloatingToolbarIsDisplayed(getActivity());
     }
 
-    private static ViewInteraction onHandleView(int id) {
-        return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
-                .inRoot(withDecorView(hasDescendant(withId(id))));
-    }
-
     @SmallTest
     public void testSelectionHandles() throws Exception {
         final String text = "abcd efg hijk lmn";
         onView(withId(R.id.textview)).perform(click());
         onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text));
+
+        assertNoSelectionHandles();
+
         onView(withId(R.id.textview)).perform(doubleClickOnTextAtIndex(text.indexOf('f')));
 
+        onHandleView(com.android.internal.R.id.selection_start_handle)
+                .check(matches(isDisplayed()));
+        onHandleView(com.android.internal.R.id.selection_end_handle)
+                .check(matches(isDisplayed()));
+
         final TextView textView = (TextView)getActivity().findViewById(R.id.textview);
         onHandleView(com.android.internal.R.id.selection_start_handle)
                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
@@ -336,4 +342,25 @@
                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
         onView(withId(R.id.textview)).check(hasSelection("hijk"));
     }
+
+    private static void assertNoSelectionHandles() {
+        try {
+            onHandleView(com.android.internal.R.id.selection_start_handle)
+                    .check(matches(isDisplayed()));
+        } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+            try {
+                onHandleView(com.android.internal.R.id.selection_end_handle)
+                        .check(matches(isDisplayed()));
+            } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e1) {
+                return;
+            }
+        }
+        throw new AssertionError("Selection handle found");
+    }
+
+    private static ViewInteraction onHandleView(int id)
+            throws NoMatchingRootException, NoMatchingViewException, AssertionError {
+        return onView(allOf(withId(id), isAssignableFrom(Editor.HandleView.class)))
+                .inRoot(withDecorView(hasDescendant(withId(id))));
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/espresso/DragAction.java b/core/tests/coretests/src/android/widget/espresso/DragAction.java
index 1132ce0..ce97568 100644
--- a/core/tests/coretests/src/android/widget/espresso/DragAction.java
+++ b/core/tests/coretests/src/android/widget/espresso/DragAction.java
@@ -91,6 +91,72 @@
         },
 
         /**
+         * Starts a drag with a mouse double click.
+         */
+        MOUSE_DOUBLE_CLICK {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                @Nullable
+                public MotionEvent perform(
+                        UiController uiController,  float[] coordinates, float[] precision) {
+                    return performDoubleTap(uiController, coordinates, precision);
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "mouse double click and drag to select";
+            }
+
+            @Override
+            public UiController wrapUiController(UiController uiController) {
+                return new MouseUiController(uiController);
+            }
+        },
+
+        /**
+         * Starts a drag with a mouse long click.
+         */
+        MOUSE_LONG_CLICK {
+            private DownMotionPerformer downMotion = new DownMotionPerformer() {
+                @Override
+                public MotionEvent perform(
+                        UiController uiController, float[] coordinates, float[] precision) {
+                    MotionEvent downEvent = MotionEvents.sendDown(
+                            uiController, coordinates, precision)
+                            .down;
+                    return performLongPress(uiController, coordinates, precision);
+                }
+            };
+
+            @Override
+            public Status sendSwipe(
+                    UiController uiController,
+                    float[] startCoordinates, float[] endCoordinates, float[] precision) {
+                return sendLinearDrag(
+                        uiController, downMotion, startCoordinates, endCoordinates, precision);
+            }
+
+            @Override
+            public String toString() {
+                return "mouse long click and drag to select";
+            }
+
+            @Override
+            public UiController wrapUiController(UiController uiController) {
+                return new MouseUiController(uiController);
+            }
+        },
+
+        /**
          * Starts a drag with a tap.
          */
         TAP {
@@ -127,15 +193,7 @@
                 @Override
                 public MotionEvent perform(
                         UiController uiController, float[] coordinates, float[] precision) {
-                    MotionEvent downEvent = MotionEvents.sendDown(
-                            uiController, coordinates, precision)
-                            .down;
-                    // Duration before a press turns into a long press.
-                    // Factor 1.5 is needed, otherwise a long press is not safely detected.
-                    // See android.test.TouchUtils longClickView
-                    long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
-                    uiController.loopMainThreadForAtLeast(longPressTimeout);
-                    return downEvent;
+                    return performLongPress(uiController, coordinates, precision);
                 }
             };
 
@@ -162,25 +220,7 @@
                 @Nullable
                 public MotionEvent perform(
                         UiController uiController,  float[] coordinates, float[] precision) {
-                    MotionEvent downEvent = MotionEvents.sendDown(
-                            uiController, coordinates, precision)
-                            .down;
-                    try {
-                        if (!MotionEvents.sendUp(uiController, downEvent)) {
-                            String logMessage = "Injection of up event as part of the double tap " +
-                                    "failed. Sending cancel event.";
-                            Log.d(TAG, logMessage);
-                            MotionEvents.sendCancel(uiController, downEvent);
-                            return null;
-                        }
-
-                        long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
-                        uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
-
-                        return MotionEvents.sendDown(uiController, coordinates, precision).down;
-                    } finally {
-                        downEvent.recycle();
-                    }
+                    return performDoubleTap(uiController, coordinates, precision);
                 }
             };
 
@@ -267,6 +307,43 @@
             return res;
         }
 
+        private static MotionEvent performLongPress(
+                UiController uiController, float[] coordinates, float[] precision) {
+            MotionEvent downEvent = MotionEvents.sendDown(
+                    uiController, coordinates, precision)
+                    .down;
+            // Duration before a press turns into a long press.
+            // Factor 1.5 is needed, otherwise a long press is not safely detected.
+            // See android.test.TouchUtils longClickView
+            long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
+            uiController.loopMainThreadForAtLeast(longPressTimeout);
+            return downEvent;
+        }
+
+        @Nullable
+        private static MotionEvent performDoubleTap(
+                UiController uiController,  float[] coordinates, float[] precision) {
+            MotionEvent downEvent = MotionEvents.sendDown(
+                    uiController, coordinates, precision)
+                    .down;
+            try {
+                if (!MotionEvents.sendUp(uiController, downEvent)) {
+                    String logMessage = "Injection of up event as part of the double tap " +
+                            "failed. Sending cancel event.";
+                    Log.d(TAG, logMessage);
+                    MotionEvents.sendCancel(uiController, downEvent);
+                    return null;
+                }
+
+                long doubleTapMinimumTimeout = ViewConfiguration.getDoubleTapMinTime();
+                uiController.loopMainThreadForAtLeast(doubleTapMinimumTimeout);
+
+                return MotionEvents.sendDown(uiController, coordinates, precision).down;
+            } finally {
+                downEvent.recycle();
+            }
+        }
+
         @Override
         public UiController wrapUiController(UiController uiController) {
             return uiController;
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
new file mode 100644
index 0000000..de640ca
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/espresso/MouseClickAction.java
@@ -0,0 +1,55 @@
+/*
+ * 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.widget.espresso;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Tapper;
+import android.view.View;
+
+/**
+ * ViewAction for performing an click on View by a mouse.
+ */
+public final class MouseClickAction implements ViewAction {
+    private final GeneralClickAction mGeneralClickAction;
+
+    public MouseClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
+            PrecisionDescriber precisionDescriber) {
+        mGeneralClickAction = new GeneralClickAction(tapper, coordinatesProvider,
+                precisionDescriber);
+    }
+
+    @Override
+    public Matcher<View> getConstraints() {
+        return mGeneralClickAction.getConstraints();
+    }
+
+    @Override
+    public String getDescription() {
+        return mGeneralClickAction.getDescription();
+    }
+
+    @Override
+    public void perform(UiController uiController, View view) {
+        mGeneralClickAction.perform(new MouseUiController(uiController), view);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
index 32abc86..63e310e 100644
--- a/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
+++ b/core/tests/coretests/src/android/widget/espresso/TextViewActions.java
@@ -53,6 +53,21 @@
     }
 
     /**
+     * Returns an action that clicks by mouse on text at an index on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param index The index of the TextView's text to click on.
+     */
+    public static ViewAction mouseClickOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), Press.PINPOINT));
+    }
+
+    /**
      * Returns an action that double-clicks on text at an index on the TextView.<br>
      * <br>
      * View constraints:
@@ -68,6 +83,21 @@
     }
 
     /**
+     * Returns an action that double-clicks by mouse on text at an index on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param index The index of the TextView's text to double-click on.
+     */
+    public static ViewAction mouseDoubleClickOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.PINPOINT));
+    }
+
+    /**
      * Returns an action that long presses on text at an index on the TextView.<br>
      * <br>
      * View constraints:
@@ -83,6 +113,21 @@
     }
 
     /**
+     * Returns an action that long click by mouse on text at an index on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param index The index of the TextView's text to long click on.
+     */
+    public static ViewAction mouseLongClickOnTextAtIndex(int index) {
+        return actionWithAssertions(
+                new MouseClickAction(Tap.LONG, new TextCoordinates(index), Press.PINPOINT));
+    }
+
+    /**
      * Returns an action that long presses then drags on text from startIndex to endIndex on the
      * TextView.<br>
      * <br>
@@ -148,6 +193,50 @@
                         TextView.class));
     }
 
+    /**
+     * Returns an action that double click then drags by mouse on text from startIndex to endIndex
+     * on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param startIndex The index of the TextView's text to start a drag from
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) {
+        return actionWithAssertions(
+                new DragAction(
+                        DragAction.Drag.MOUSE_DOUBLE_CLICK,
+                        new TextCoordinates(startIndex),
+                        new TextCoordinates(endIndex),
+                        Press.PINPOINT,
+                        TextView.class));
+    }
+
+    /**
+     * Returns an action that long click then drags by mouse on text from startIndex to endIndex
+     * on the TextView.<br>
+     * <br>
+     * View constraints:
+     * <ul>
+     * <li>must be a TextView displayed on screen
+     * <ul>
+     *
+     * @param startIndex The index of the TextView's text to start a drag from
+     * @param endIndex The index of the TextView's text to end the drag at
+     */
+    public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) {
+        return actionWithAssertions(
+                new DragAction(
+                        DragAction.Drag.MOUSE_LONG_CLICK,
+                        new TextCoordinates(startIndex),
+                        new TextCoordinates(endIndex),
+                        Press.PINPOINT,
+                        TextView.class));
+    }
+
     public enum Handle {
         SELECTION_START,
         SELECTION_END,
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index f6d8ee9..edfd380 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -228,6 +228,7 @@
     $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \
     $(LOCAL_PATH)/effects/ogg/Trusted_48k.ogg:system/media/audio/ui/Trusted.ogg \
     $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+    $(LOCAL_PATH)/effects/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
     $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \
     $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
     $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk
index 5a5eea6..c5222af 100644
--- a/data/sounds/AudioPackage10.mk
+++ b/data/sounds/AudioPackage10.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 10 - Mako
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk
index 0f85b33..43c83b9 100644
--- a/data/sounds/AudioPackage11.mk
+++ b/data/sounds/AudioPackage11.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 11 - Razor
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop_48k.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery_48k.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage12.mk b/data/sounds/AudioPackage12.mk
index 4251332..cd4d35b 100644
--- a/data/sounds/AudioPackage12.mk
+++ b/data/sounds/AudioPackage12.mk
@@ -13,7 +13,7 @@
 RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage12_48.mk b/data/sounds/AudioPackage12_48.mk
index 70e68d3..80758f4 100644
--- a/data/sounds/AudioPackage12_48.mk
+++ b/data/sounds/AudioPackage12_48.mk
@@ -13,7 +13,7 @@
 RINGTONE_FILES := Callisto Dione Ganymede Luna Oberon Phobos Sedna Titania Triton Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted
+MATERIAL_EFFECT_FILES := camera_click VideoRecord LowBattery WirelessChargingStarted VideoStop
 
 # Alarms not yet available in 48 kHz
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
diff --git a/data/sounds/AudioPackage13.mk b/data/sounds/AudioPackage13.mk
index cec7280..d33a4af 100644
--- a/data/sounds/AudioPackage13.mk
+++ b/data/sounds/AudioPackage13.mk
@@ -14,7 +14,7 @@
 	Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	camera_focus Dock Undock Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage13_48.mk b/data/sounds/AudioPackage13_48.mk
index d1b17c8..9c320ae 100644
--- a/data/sounds/AudioPackage13_48.mk
+++ b/data/sounds/AudioPackage13_48.mk
@@ -14,7 +14,7 @@
 	Umbriel
 EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
 	Lock Unlock Trusted
-MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
 
 PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
 	$(LOCAL_PATH)/alarms/material/ogg/$(fn)_48k.ogg:system/media/audio/alarms/$(fn).ogg)
diff --git a/data/sounds/AudioPackage2.mk b/data/sounds/AudioPackage2.mk
index ba9d7e2..40319c4 100644
--- a/data/sounds/AudioPackage2.mk
+++ b/data/sounds/AudioPackage2.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 2
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -34,6 +34,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage3.mk b/data/sounds/AudioPackage3.mk
index 5bfeb42..a05de72 100644
--- a/data/sounds/AudioPackage3.mk
+++ b/data/sounds/AudioPackage3.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 3
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -34,6 +34,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage4.mk b/data/sounds/AudioPackage4.mk
index 43dbe20..d376a2d 100644
--- a/data/sounds/AudioPackage4.mk
+++ b/data/sounds/AudioPackage4.mk
@@ -1,11 +1,11 @@
 #
 # Audio Package 4
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
 # This is a larger package of sounds than the 1.0 release for devices
 # that have larger internal flash.
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -39,6 +39,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage5.mk b/data/sounds/AudioPackage5.mk
index fbbb16a..72384c8 100644
--- a/data/sounds/AudioPackage5.mk
+++ b/data/sounds/AudioPackage5.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 5 - Crespo/Soju
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -20,6 +20,7 @@
 	$(LOCAL_PATH)/effects/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage6.mk b/data/sounds/AudioPackage6.mk
index c843fdc..5413704 100644
--- a/data/sounds/AudioPackage6.mk
+++ b/data/sounds/AudioPackage6.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 6 - Trygon/Stingray
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -19,6 +19,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7.mk b/data/sounds/AudioPackage7.mk
index ce82651..e4763be 100644
--- a/data/sounds/AudioPackage7.mk
+++ b/data/sounds/AudioPackage7.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 7 - Tuna
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -21,6 +21,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage7alt.mk b/data/sounds/AudioPackage7alt.mk
index db468f3..30e6173 100644
--- a/data/sounds/AudioPackage7alt.mk
+++ b/data/sounds/AudioPackage7alt.mk
@@ -21,6 +21,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
 	$(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \
diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk
index 4112c18..b38e62d 100644
--- a/data/sounds/AudioPackage8.mk
+++ b/data/sounds/AudioPackage8.mk
@@ -1,9 +1,9 @@
 #
 # Audio Package 7 - Tuna
-# 
+#
 # Include this file in a product makefile to include these audio files
 #
-# 
+#
 
 LOCAL_PATH:= frameworks/base/data/sounds
 
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk
index 1b430c0..dbe1350 100644
--- a/data/sounds/AudioPackage9.mk
+++ b/data/sounds/AudioPackage9.mk
@@ -23,6 +23,7 @@
 	$(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \
 	$(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/material/ogg/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
 	$(LOCAL_PATH)/effects/material/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \
diff --git a/data/sounds/OriginalAudio.mk b/data/sounds/OriginalAudio.mk
index e1ca24b..f683752 100644
--- a/data/sounds/OriginalAudio.mk
+++ b/data/sounds/OriginalAudio.mk
@@ -1,8 +1,8 @@
 #
 # Original audio package that shipped on G1
-# 
+#
 # This file is included from core.mk so that all devices will have these sounds
-# 
+#
 # TODO: Clean up for future releases
 #
 
@@ -32,6 +32,7 @@
 	$(LOCAL_PATH)/effects/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \
 	$(LOCAL_PATH)/effects/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \
 	$(LOCAL_PATH)/effects/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \
+	$(LOCAL_PATH)/effects/VideoStop.ogg:system/media/audio/ui/VideoStop.ogg \
 	$(LOCAL_PATH)/effects/camera_click.ogg:system/media/audio/ui/camera_click.ogg \
 	$(LOCAL_PATH)/newwavelabs/BeatPlucker.ogg:system/media/audio/ringtones/BeatPlucker.ogg \
 	$(LOCAL_PATH)/newwavelabs/CaffeineSnake.ogg:system/media/audio/notifications/CaffeineSnake.ogg
diff --git a/data/sounds/effects/VideoStop.ogg b/data/sounds/effects/VideoStop.ogg
new file mode 100644
index 0000000..1450522
--- /dev/null
+++ b/data/sounds/effects/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/VideoStop.wav b/data/sounds/effects/VideoStop.wav
new file mode 100644
index 0000000..5809d93
--- /dev/null
+++ b/data/sounds/effects/VideoStop.wav
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop.ogg b/data/sounds/effects/material/ogg/VideoStop.ogg
new file mode 100644
index 0000000..e98fabc
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop.ogg
Binary files differ
diff --git a/data/sounds/effects/material/ogg/VideoStop_48k.ogg b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
new file mode 100644
index 0000000..b1eb780
--- /dev/null
+++ b/data/sounds/effects/material/ogg/VideoStop_48k.ogg
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index f9474ef..d313aa5 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -139,6 +139,7 @@
         exit.setInterpolator(LINEAR_INTERPOLATOR);
         exit.setDuration(OPACITY_EXIT_DURATION);
         exit.setStartDelay(fastEnterDuration);
+        exit.setStartValue(targetAlpha);
         set.add(exit);
 
         // Linear "fast" enter based on current opacity.
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1c2c940..f5b7a2e 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -36,6 +36,7 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.system.ErrnoException;
 import android.system.OsConstants;
@@ -1260,6 +1261,13 @@
      */
     public void setWakeMode(Context context, int mode) {
         boolean washeld = false;
+
+        /* Disable persistant wakelocks in media player based on property */
+        if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) {
+            Log.w(TAG, "IGNORING setWakeMode " + mode);
+            return;
+        }
+
         if (mWakeLock != null) {
             if (mWakeLock.isHeld()) {
                 washeld = true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55e2f44..b99c806 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -39,13 +39,15 @@
 import android.os.SystemClock;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
 import android.support.design.widget.Snackbar;
 import android.text.format.DateUtils;
 import android.util.Log;
-import android.widget.Toast;
 
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.model.RootInfo;
 
 import libcore.io.IoUtils;
 
@@ -72,6 +74,12 @@
     // TODO: Move it to a shared file when more operations are implemented.
     public static final int FAILURE_COPY = 1;
 
+    // Parameters of the copy job. Requests to an IntentService are serialized so this code only
+    // needs to deal with one job at a time.
+    // NOTE: This must be declared by concrete type as the concrete type
+    // is required by putParcelableArrayListExtra.
+    private final ArrayList<DocumentInfo> mFailedFiles = new ArrayList<>();
+
     private PowerManager mPowerManager;
 
     private NotificationManager mNotificationManager;
@@ -80,9 +88,6 @@
     // Jobs are serialized but a job ID is used, to avoid mixing up cancellation requests.
     private String mJobId;
     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 ArrayList<DocumentInfo> mFailedFiles;
     private long mBatchSize;
     private long mBytesCopied;
     private long mStartTime;
@@ -97,10 +102,11 @@
     private ContentProviderClient mSrcClient;
     private ContentProviderClient mDstClient;
 
+    // For testing only.
+    @Nullable private TestOnlyListener mJobFinishedListener;
+
     public CopyService() {
         super("CopyService");
-
-        mFailedFiles = new ArrayList<DocumentInfo>();
     }
 
     /**
@@ -115,7 +121,11 @@
         final Resources res = activity.getResources();
         final Intent copyIntent = new Intent(activity, CopyService.class);
         copyIntent.putParcelableArrayListExtra(
-                EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
+                EXTRA_SRC_LIST,
+                // Don't create a copy unless absolutely necessary :)
+                srcDocs instanceof ArrayList
+                    ? (ArrayList<DocumentInfo>) srcDocs
+                    : new ArrayList<DocumentInfo>(srcDocs));
         copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
         copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
 
@@ -198,6 +208,11 @@
                         .setAutoCancel(true);
                 mNotificationManager.notify(mJobId, 0, errorBuilder.build());
             }
+
+            if (mJobFinishedListener != null) {
+                mJobFinishedListener.onFinished(mFailedFiles);
+            }
+
             if (DEBUG) Log.d(TAG, "Done cleaning up");
         }
     }
@@ -269,6 +284,26 @@
     }
 
     /**
+     * Sets a callback to be run when the next run job is finished.
+     * This is test ONLY instrumentation. The alternative is for us to add
+     * broadcast intents SOLELY for the purpose of testing.
+     * @param listener
+     */
+    @VisibleForTesting
+    void addFinishedListener(TestOnlyListener listener) {
+        this.mJobFinishedListener = listener;
+
+    }
+
+    /**
+     * Only used for testing. Is that obvious enough?
+     */
+    @VisibleForTesting
+    interface TestOnlyListener {
+        void onFinished(List<DocumentInfo> failed);
+    }
+
+    /**
      * Calculates the cumulative size of all the documents in the list. Directories are recursed
      * into and totaled up.
      *
@@ -279,7 +314,7 @@
     private long calculateFileSizes(List<DocumentInfo> srcs) throws RemoteException {
         long result = 0;
         for (DocumentInfo src : srcs) {
-            if (Document.MIME_TYPE_DIR.equals(src.mimeType)) {
+            if (src.isDirectory()) {
                 // Directories need to be recursed into.
                 result += calculateFileSizesHelper(src.derivedUri);
             } else {
@@ -412,8 +447,21 @@
      */
     private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
             throws RemoteException {
-        if (DEBUG) Log.d(TAG, "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")" +
-            " to " + dstDirInfo.displayName + " (" + dstDirInfo.derivedUri + ")");
+
+        String opDesc = mode == TRANSFER_MODE_COPY ? "copy" : "move";
+
+        // Guard unsupported recursive operation.
+        if (dstDirInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstDirInfo)) {
+            if (DEBUG) Log.d(TAG,
+                    "Skipping recursive " + opDesc + " of directory " + dstDirInfo.derivedUri);
+            mFailedFiles.add(srcInfo);
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG,
+                "Performing " + opDesc + " of " + srcInfo.displayName
+                + " (" + srcInfo.derivedUri + ")" + " to " + dstDirInfo.displayName
+                + " (" + dstDirInfo.derivedUri + ")");
 
         // When copying within the same provider, try to use optimized copying and moving.
         // If not supported, then fallback to byte-by-byte copy/move.
@@ -450,7 +498,7 @@
             return;
         }
 
-        if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+        if (srcInfo.isDirectory()) {
             copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
         } else {
             copyFileHelper(srcInfo.derivedUri, dstUri, mode);
@@ -458,6 +506,17 @@
     }
 
     /**
+     * Returns true if {@code doc} is a descendant of {@code parentDoc}.
+     */
+    boolean isDescendentOf(DocumentInfo doc, DocumentInfo parentDoc) throws RemoteException {
+        if (parentDoc.isDirectory() && doc.authority.equals(parentDoc.authority)) {
+            return DocumentsContract.isChildDocument(
+                    mDstClient, doc.derivedUri, parentDoc.derivedUri);
+        }
+        return false;
+    }
+
+    /**
      * 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".
      *
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index c6425a6..f3c3f2f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -83,8 +84,10 @@
         editText.setOnEditorActionListener(
                 new OnEditorActionListener() {
                     @Override
-                    public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
-                        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
+                    public boolean onEditorAction(
+                            TextView view, int actionId, @Nullable KeyEvent event) {
+                        if (event != null
+                                && event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                                 && event.hasNoModifiers()) {
                             createDirectory(editText.getText().toString());
                             dialog.dismiss();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
index 120f610..23074f0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -37,7 +37,6 @@
         implements DialogInterface.OnClickListener {
     private static final String TAG = "FailureDialogFragment";
 
-    private int mFailure;
     private int mTransferMode;
     private ArrayList<DocumentInfo> mFailedSrcList;
 
@@ -75,7 +74,6 @@
     public Dialog onCreateDialog(Bundle inState) {
         super.onCreate(inState);
 
-        mFailure = getArguments().getInt(CopyService.EXTRA_FAILURE);
         mTransferMode = getArguments().getInt(CopyService.EXTRA_TRANSFER_MODE);
         mFailedSrcList = getArguments().getParcelableArrayList(CopyService.EXTRA_SRC_LIST);
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index cc981e1e..dfdc705 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -25,6 +25,7 @@
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsProvider;
+import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 
 import com.android.documentsui.DocumentsApplication;
@@ -204,13 +205,18 @@
         }
     }
 
-    private void deriveFields() {
+    @VisibleForTesting
+    void deriveFields() {
         derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
     }
 
     @Override
     public String toString() {
-        return "Document{docId=" + documentId + ", name=" + displayName + "}";
+        return "Document{"
+                + "docId=" + documentId
+                + ", name=" + displayName
+                + ", isDirectory=" + isDirectory()
+                + "}";
     }
 
     public boolean isCreateSupported() {
@@ -237,6 +243,22 @@
         return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
     }
 
+    public int hashCode() {
+        return derivedUri.hashCode() + mimeType.hashCode();
+    }
+
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (!(other instanceof DocumentInfo)) {
+            return false;
+        }
+
+        DocumentInfo that = (DocumentInfo) other;
+        // Uri + mime type should be totally unique.
+        return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
+    }
+
     public static String getCursorString(Cursor cursor, String columnName) {
         final int index = cursor.getColumnIndex(columnName);
         return (index != -1) ? cursor.getString(index) : null;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
similarity index 85%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
index 369ab7d..079d599 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
@@ -28,12 +28,10 @@
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsContract.Document;
 import android.test.MoreAsserts;
 import android.test.ServiceTestCase;
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -48,6 +46,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -55,9 +54,9 @@
 import java.util.concurrent.TimeoutException;
 
 @MediumTest
-public class CopyTest extends ServiceTestCase<CopyService> {
+public class CopyServiceTest extends ServiceTestCase<CopyService> {
 
-    public CopyTest() {
+    public CopyServiceTest() {
         super(CopyService.class);
     }
 
@@ -72,11 +71,13 @@
     private DocumentsProviderHelper mDocHelper;
     private StubProvider mStorage;
     private Context mSystemContext;
+    private CopyJobListener mListener;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
+        mListener = new CopyJobListener();
         setupTestContext();
         mClient = mResolver.acquireContentProviderClient(AUTHORITY);
 
@@ -84,6 +85,8 @@
         mStorage.clearCacheAndBuildRoots();
 
         mDocHelper = new DocumentsProviderHelper(AUTHORITY, mClient);
+
+        assertDestFileCount(0);
     }
 
     @Override
@@ -97,15 +100,13 @@
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
-        assertDstFileCountEquals(0);
-
         startService(createCopyIntent(Lists.newArrayList(testFile)));
 
         // 2 operations: file creation, then writing data.
         mResolver.waitForChanges(2);
 
         // Verify that one file was copied; check file contents.
-        assertDstFileCountEquals(1);
+        assertDestFileCount(1);
         assertCopied(srcPath);
     }
 
@@ -114,8 +115,6 @@
         String testContent = "The five boxing wizards jump quickly";
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
 
-        assertDstFileCountEquals(0);
-
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
         moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
         startService(moveIntent);
@@ -124,7 +123,7 @@
         mResolver.waitForChanges(3);
 
         // Verify that one file was moved; check file contents.
-        assertDstFileCountEquals(1);
+        assertDestFileCount(1);
         assertDoesNotExist(SRC_ROOT, srcPath);
 
         byte[] dstContent = readFile(DST_ROOT, srcPath);
@@ -147,15 +146,13 @@
                 mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
                 mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
 
-        assertDstFileCountEquals(0);
-
         // Copy all the test files.
         startService(createCopyIntent(testFiles));
 
         // 3 file creations, 3 file writes.
         mResolver.waitForChanges(6);
 
-        assertDstFileCountEquals(3);
+        assertDestFileCount(3);
         for (String path : srcPaths) {
             assertCopied(path);
         }
@@ -163,29 +160,54 @@
 
     public void testCopyEmptyDir() throws Exception {
         String srcPath = "/emptyDir";
-        Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-
-        assertDstFileCountEquals(0);
+        Uri testDir = createTestDirectory(srcPath);
 
         startService(createCopyIntent(Lists.newArrayList(testDir)));
 
         // Just 1 operation: Directory creation.
         mResolver.waitForChanges(1);
 
-        assertDstFileCountEquals(1);
+        assertDestFileCount(1);
 
         // Verify that the dst exists and is a directory.
         File dst = mStorage.getFile(DST_ROOT, srcPath);
         assertTrue(dst.isDirectory());
     }
 
+    public void testNoCopyDirToSelf() throws Exception {
+        Uri testDir = createTestDirectory("/someDir");
+
+        Intent intent = createCopyIntent(Lists.newArrayList(testDir), testDir);
+        startService(intent);
+
+        getService().addFinishedListener(mListener);
+
+        mListener.waitForFinished();
+        mListener.assertFailedCount(1);
+        mListener.assertFileFailed("someDir");
+
+        assertDestFileCount(0);
+    }
+
+    public void testNoCopyDirToDescendent() throws Exception {
+        Uri testDir = createTestDirectory("/someDir");
+        Uri descDir = createTestDirectory("/someDir/theDescendent");
+
+        Intent intent = createCopyIntent(Lists.newArrayList(testDir), descDir);
+        startService(intent);
+
+        getService().addFinishedListener(mListener);
+
+        mListener.waitForFinished();
+        mListener.assertFailedCount(1);
+        mListener.assertFileFailed("someDir");
+
+        assertDestFileCount(0);
+    }
+
     public void testMoveEmptyDir() throws Exception {
         String srcPath = "/emptyDir";
-        Uri testDir = mStorage.createFile(SRC_ROOT, srcPath, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
-
-        assertDstFileCountEquals(0);
+        Uri testDir = createTestDirectory(srcPath);
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
         moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -194,7 +216,7 @@
         // 2 operations: Directory creation, and removal of the original.
         mResolver.waitForChanges(2);
 
-        assertDstFileCountEquals(1);
+        assertDestFileCount(1);
 
         // Verify that the dst exists and is a directory.
         File dst = mStorage.getFile(DST_ROOT, srcPath);
@@ -217,8 +239,7 @@
                 srcDir + "/test2.txt"
         };
         // Create test dir; put some files in it.
-        Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
+        Uri testDir = createTestDirectory(srcDir);
         mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
         mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
         mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
@@ -252,8 +273,6 @@
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
-        assertDstFileCountEquals(0);
-
         mStorage.simulateReadErrorsForFile(testFile);
 
         startService(createCopyIntent(Lists.newArrayList(testFile)));
@@ -262,7 +281,7 @@
         mResolver.waitForChanges(3);
 
         // Verify that the failed copy was cleaned up.
-        assertDstFileCountEquals(0);
+        assertDestFileCount(0);
     }
 
     public void testMoveFileWithReadErrors() throws Exception {
@@ -270,8 +289,6 @@
         Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
-        assertDstFileCountEquals(0);
-
         mStorage.simulateReadErrorsForFile(testFile);
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
@@ -288,7 +305,7 @@
             return;
         } finally {
             // Verify that the failed copy was cleaned up, and the src file wasn't removed.
-            assertDstFileCountEquals(0);
+            assertDestFileCount(0);
             assertExists(SRC_ROOT, srcPath);
         }
         // The asserts above didn't fail, but the CopyService did something unexpected.
@@ -308,8 +325,7 @@
                 srcDir + "/test2.txt"
         };
         // Create test dir; put some files in it.
-        Uri testDir = mStorage.createFile(SRC_ROOT, srcDir, DocumentsContract.Document.MIME_TYPE_DIR,
-                null);
+        Uri testDir = createTestDirectory(srcDir);
         mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
         Uri errFile = mStorage
                 .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
@@ -346,33 +362,37 @@
         assertExists(SRC_ROOT, srcFiles[1]);
     }
 
-    /**
-     * Copies the given files to a pre-determined destination.
-     *
-     * @throws FileNotFoundException
-     */
+    private Uri createTestDirectory(String dir) throws IOException {
+        return mStorage.createFile(
+                SRC_ROOT, dir, DocumentsContract.Document.MIME_TYPE_DIR, null);
+    }
+
     private Intent createCopyIntent(List<Uri> srcs) throws Exception {
+        RootInfo root = mDocHelper.getRoot(DST_ROOT);
+        final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
+
+        return createCopyIntent(srcs, dst);
+    }
+
+    private Intent createCopyIntent(List<Uri> srcs, Uri dst) throws Exception {
         final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList();
         for (Uri src : srcs) {
             srcDocs.add(DocumentInfo.fromUri(mResolver, src));
         }
 
-        RootInfo root = mDocHelper.getRoot(DST_ROOT);
-        final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, root.documentId);
         DocumentStack stack = new DocumentStack();
         stack.push(DocumentInfo.fromUri(mResolver, dst));
         final Intent copyIntent = new Intent(mContext, CopyService.class);
         copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
         copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
 
-        // startService(copyIntent);
         return copyIntent;
     }
 
     /**
      * Returns a count of the files in the given directory.
      */
-    private void assertDstFileCountEquals(int expected) throws RemoteException {
+    private void assertDestFileCount(int expected) throws RemoteException {
         RootInfo dest = mDocHelper.getRoot(DST_ROOT);
         final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY,
                 dest.documentId);
@@ -449,6 +469,34 @@
         mResolver.addProvider(AUTHORITY, mStorage);
     }
 
+    private final class CopyJobListener implements CopyService.TestOnlyListener {
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        final List<DocumentInfo> failedDocs = new ArrayList<>();
+        @Override
+        public void onFinished(List<DocumentInfo> failed) {
+            failedDocs.addAll(failed);
+            latch.countDown();
+        }
+
+        public void assertFileFailed(String expectedName) {
+            for (DocumentInfo failed : failedDocs) {
+                if (expectedName.equals(failed.displayName)) {
+                    return;
+                }
+            }
+            fail("Couldn't find failed file: " + expectedName);
+        }
+
+        public void waitForFinished() throws InterruptedException {
+            latch.await(500, TimeUnit.MILLISECONDS);
+        }
+
+        public void assertFailedCount(int expected) {
+            assertEquals(expected, failedDocs.size());
+        }
+    }
+
     /**
      * A test resolver that enables this test suite to listen for notifications that mark when copy
      * operations are done.
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 2d42ddc..d23cdeb 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -531,6 +531,16 @@
             this.rootInfo = rootInfo;
             mStorage.put(this.documentId, this);
         }
+        @Override
+        public String toString() {
+            return "StubDocument{"
+                    + "path:" + file.getPath()
+                    + ", mimeType:" + mimeType
+                    + ", rootInfo:" + rootInfo
+                    + ", documentId:" + documentId
+                    + ", parentId:" + parentId
+                    + "}";
+        }
     }
 
     private static String getDocumentIdForFile(File file) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
new file mode 100644
index 0000000..a6aba7b
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/model/DocumentInfoTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.documentsui.model;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class DocumentInfoTest extends AndroidTestCase {
+
+    public void testEquals() throws Exception {
+        DocumentInfo doc = createDocInfo("authority.a", "doc.1", "text/plain");
+        assertEquals(doc, doc);
+    }
+
+    public void testNotEquals_differentAuthority() throws Exception {
+        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+        DocumentInfo docB = createDocInfo("authority.b", "doc.1", "text/plain");
+        assertFalse(docA.equals(docB));
+    }
+
+    public void testNotEquals_differentDocId() throws Exception {
+        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+        DocumentInfo docB = createDocInfo("authority.a", "doc.2", "text/plain");
+        assertFalse(docA.equals(docB));
+    }
+
+    public void testNotEquals_differentMimetype() throws Exception {
+        DocumentInfo docA = createDocInfo("authority.a", "doc.1", "text/plain");
+        DocumentInfo docB = createDocInfo("authority.a", "doc.1", "image/png");
+        assertFalse(docA.equals(docB));
+    }
+
+    private DocumentInfo createDocInfo(String authority, String docId, String mimeType) {
+        DocumentInfo doc = new DocumentInfo();
+        doc.authority = authority;
+        doc.documentId = docId;
+        doc.mimeType = mimeType;
+        doc.deriveFields();
+        return doc;
+    }
+}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
index 5547771..ed6ee7e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpManagerTest.java
@@ -45,10 +45,20 @@
     @Override
     public void setUp() throws Exception {
         mUsbManager = getContext().getSystemService(UsbManager.class);
-        mUsbDevice = findDevice();
-        mManager = new MtpManager(getContext());
-        mManager.openDevice(mUsbDevice.getDeviceId());
-        waitForStorages(mManager, mUsbDevice.getDeviceId());
+        for (int i = 0; i < 2; i++) {
+            mUsbDevice = findDevice();
+            mManager = new MtpManager(getContext());
+            mManager.openDevice(mUsbDevice.getDeviceId());
+            try {
+                waitForStorages(mManager, mUsbDevice.getDeviceId());
+                return;
+            } catch (IOException exp) {
+                // When the MTP device is Android, and it changes the USB device type from
+                // "Charging" to "MTP", the device ID will be updated. We need to find a device
+                // again.
+                continue;
+            }
+        }
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 6102bef..00dadb8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -143,6 +143,9 @@
 
     public boolean openTile(DashboardTile tile) {
         closeDrawer();
+        if (tile == null) {
+            return false;
+        }
         int numUserHandles = tile.userHandle.size();
         if (numUserHandles > 1) {
             ProfileSelectDialog.show(getFragmentManager(), tile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index e28b2e5..2b1c66d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -19,8 +19,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkRequest;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
@@ -62,6 +66,9 @@
     private final Context mContext;
     private final WifiManager mWifiManager;
     private final IntentFilter mFilter;
+    private final ConnectivityManager mConnectivityManager;
+    private final NetworkRequest mNetworkRequest;
+    private WifiTrackerNetworkCallback mNetworkCallback;
 
     private final AtomicBoolean mConnected = new AtomicBoolean(false);
     private final WifiListener mListener;
@@ -104,13 +111,15 @@
     public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
             boolean includeSaved, boolean includeScans, boolean includePasspoints) {
         this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
-                (WifiManager) context.getSystemService(Context.WIFI_SERVICE), Looper.myLooper());
+                context.getSystemService(WifiManager.class),
+                context.getSystemService(ConnectivityManager.class), Looper.myLooper());
     }
 
     @VisibleForTesting
     WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
             boolean includeSaved, boolean includeScans, boolean includePasspoints,
-            WifiManager wifiManager, Looper currentLooper) {
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            Looper currentLooper) {
         if (!includeSaved && !includeScans) {
             throw new IllegalArgumentException("Must include either saved or scans");
         }
@@ -127,6 +136,7 @@
         mIncludeScans = includeScans;
         mIncludePasspoints = includePasspoints;
         mListener = wifiListener;
+        mConnectivityManager = connectivityManager;
 
         // check if verbose logging has been turned on or off
         sVerboseLogging = mWifiManager.getVerboseLoggingLevel();
@@ -139,7 +149,11 @@
         mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
         mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
         mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+
+        mNetworkRequest = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build();
     }
 
     /**
@@ -192,6 +206,9 @@
         resumeScanning();
         if (!mRegistered) {
             mContext.registerReceiver(mReceiver, mFilter);
+            // NetworkCallback objects cannot be reused. http://b/20701525 .
+            mNetworkCallback = new WifiTrackerNetworkCallback();
+            mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
             mRegistered = true;
         }
     }
@@ -207,6 +224,7 @@
             mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
             mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_NETWORK_INFO);
             mContext.unregisterReceiver(mReceiver);
+            mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
             mRegistered = false;
         }
         pauseScanning();
@@ -461,12 +479,12 @@
             mMainHandler.sendEmptyMessage(MainHandler.MSG_RESUME_SCANNING);
         }
 
-        mLastInfo = mWifiManager.getConnectionInfo();
         if (networkInfo != null) {
             mLastNetworkInfo = networkInfo;
         }
 
         WifiConfiguration connectionConfig = null;
+        mLastInfo = mWifiManager.getConnectionInfo();
         if (mLastInfo != null) {
             connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId());
         }
@@ -532,12 +550,21 @@
                 mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_ACCESS_POINTS);
                 mWorkHandler.obtainMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO, info)
                         .sendToTarget();
-            } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
-                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
             }
         }
     };
 
+    private final class WifiTrackerNetworkCallback extends ConnectivityManager.NetworkCallback {
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            if (network.equals(mWifiManager.getCurrentNetwork())) {
+                // We don't send a NetworkInfo object along with this message, because even if we
+                // fetch one from ConnectivityManager, it might be older than the most recent
+                // NetworkInfo message we got via a WIFI_STATE_CHANGED broadcast.
+                mWorkHandler.sendEmptyMessage(WorkHandler.MSG_UPDATE_NETWORK_INFO);
+            }
+        }
+    }
+
     private final class MainHandler extends Handler {
         private static final int MSG_CONNECTED_CHANGED = 0;
         private static final int MSG_WIFI_STATE_CHANGED = 1;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bf3982d..25346ac 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -144,6 +144,7 @@
             android:name=".BugreportReceiver"
             android:permission="android.permission.DUMP">
             <intent-filter>
+                <action android:name="android.intent.action.BUGREPORT_STARTED" />
                 <action android:name="android.intent.action.BUGREPORT_FINISHED" />
             </intent-filter>
         </receiver>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 4469d38..cff36f7 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -17,6 +17,8 @@
 <resources>
     <string name="app_label">Shell</string>
 
+    <!-- Title of notification indicating a bugreport process is in progress. [CHAR LIMIT=50] -->
+    <string name="bugreport_in_progress_title">Bug report in progress</string>
     <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] -->
     <string name="bugreport_finished_title">Bug report captured</string>
 
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a2030ef..d0e91d2 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -21,11 +21,16 @@
 
 import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.NumberFormat;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
@@ -45,26 +50,102 @@
 import android.content.res.Configuration;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Process;
 import android.os.SystemProperties;
 import android.support.v4.content.FileProvider;
+import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Patterns;
+import android.util.SparseArray;
 import android.widget.Toast;
 
+/**
+ * Service used to keep progress of bug reports processes ({@code dumpstate}).
+ * <p>
+ * The workflow is:
+ * <ol>
+ * <li>When {@code dumpstate} starts, it sends a {@code BUGREPORT_STARTED} with its pid and the
+ * estimated total effort.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service.
+ * <li>Upon start, this service:
+ * <ol>
+ * <li>Issues a system notification so user can watch the progresss (which is 0% initially).
+ * <li>Polls the {@link SystemProperties} for updates on the {@code dumpstate} progress.
+ * <li>If the progress changed, it updates the system notification.
+ * </ol>
+ * <li>As {@code dumpstate} progresses, it updates the system property.
+ * <li>When {@code dumpstate} finishes, it sends a {@code BUGREPORT_FINISHED} intent.
+ * <li>{@link BugreportReceiver} receives the intent and delegates it to this service, which in
+ * turn:
+ * <ol>
+ * <li>Updates the system notification so user can share the bug report.
+ * <li>Stops monitoring that {@code dumpstate} process.
+ * <li>Stops itself if it doesn't have any process left to monitor.
+ * </ol>
+ * </ol>
+ */
 public class BugreportProgressService extends Service {
     private static final String TAG = "Shell";
+    private static final boolean DEBUG = false;
 
     private static final String AUTHORITY = "com.android.shell";
 
+    static final String INTENT_BUGREPORT_STARTED = "android.intent.action.BUGREPORT_STARTED";
+    static final String INTENT_BUGREPORT_FINISHED = "android.intent.action.BUGREPORT_FINISHED";
+
     static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT";
     static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT";
+    static final String EXTRA_PID = "android.intent.extra.PID";
+    static final String EXTRA_MAX = "android.intent.extra.MAX";
+    static final String EXTRA_NAME = "android.intent.extra.NAME";
+    static final String EXTRA_ORIGINAL_INTENT = "android.intent.extra.ORIGINAL_INTENT";
+
+    private static final int MSG_SERVICE_COMMAND = 1;
+    private static final int MSG_POLL = 2;
+
+    /** Polling frequency, in milliseconds. */
+    private static final long POLLING_FREQUENCY = 500;
+
+    /** How long (in ms) a dumpstate process will be monitored if it didn't show progress. */
+    private static final long INACTIVITY_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
+
+    /** System property used for monitoring progress. */
+    private static final String PROGRESS_PROPERTY_TEMPLATE = "dumpstate.%d.progress";
+
+    /** Managed dumpstate processes (keyed by pid) */
+    private final SparseArray<BugreportInfo> mProcesses = new SparseArray<>();
+
+    private Looper mServiceLooper;
+    private ServiceHandler mServiceHandler;
+
+    @Override
+    public void onCreate() {
+        HandlerThread thread = new HandlerThread("BugreportProgressServiceThread",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+
+        mServiceLooper = thread.getLooper();
+        mServiceHandler = new ServiceHandler(mServiceLooper);
+    }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (intent != null) {
-            onBugreportFinished(intent);
+            // Handle it in a separate thread.
+            Message msg = mServiceHandler.obtainMessage();
+            msg.what = MSG_SERVICE_COMMAND;
+            msg.obj = intent;
+            mServiceHandler.sendMessage(msg);
         }
+
+        // If service is killed it cannot be recreated because it would not know which
+        // dumpstate PIDs it would have to watch.
         return START_NOT_STICKY;
     }
 
@@ -73,26 +154,249 @@
         return null;
     }
 
-    private void onBugreportFinished(Intent intent) {
-        final Context context = getApplicationContext();
-        final Configuration conf = context.getResources().getConfiguration();
-        final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
-        final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+    @Override
+    public void onDestroy() {
+        mServiceLooper.quit();
+        super.onDestroy();
+    }
 
-        if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
-            triggerLocalNotification(context, bugreportFile, screenshotFile);
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        writer.printf("Monitored dumpstate processes: \n");
+        synchronized (mProcesses) {
+            for (int i = 0; i < mProcesses.size(); i++) {
+              writer.printf("\t%s\n", mProcesses.valueAt(i));
+            }
         }
-        stopSelf();
+    }
+
+    private final class ServiceHandler extends Handler {
+        public ServiceHandler(Looper looper) {
+            super(looper);
+            pollProgress();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_POLL) {
+                pollProgress();
+                return;
+            }
+
+            if (msg.what != MSG_SERVICE_COMMAND) {
+                // Sanity check.
+                Log.e(TAG, "Invalid message type: " + msg.what);
+                return;
+            }
+
+            // At this point it's handling onStartCommand(), whose intent contains the extras
+            // originally received by BugreportReceiver.
+            if (!(msg.obj instanceof Intent)) {
+                // Sanity check.
+                Log.e(TAG, "Internal error: invalid msg.obj: " + msg.obj);
+                return;
+            }
+            final Parcelable parcel = ((Intent) msg.obj).getParcelableExtra(EXTRA_ORIGINAL_INTENT);
+            if (!(parcel instanceof Intent)) {
+                // Sanity check.
+                Log.e(TAG, "Internal error: msg.obj is missing extra " + EXTRA_ORIGINAL_INTENT);
+                return;
+            }
+
+            final Intent intent = (Intent) parcel;
+            final String action = intent.getAction();
+            int pid = intent.getIntExtra(EXTRA_PID, 0);
+            int max = intent.getIntExtra(EXTRA_MAX, -1);
+            String name = intent.getStringExtra(EXTRA_NAME);
+
+            if (DEBUG) Log.v(TAG, "action: " + action + ", name: " + name + ", pid: " + pid
+                    + ", max: "+ max);
+            switch (action) {
+                case INTENT_BUGREPORT_STARTED:
+                    if (!startProgress(name, pid, max)) {
+                        stopSelfWhenDone();
+                        return;
+                    }
+                    break;
+                case INTENT_BUGREPORT_FINISHED:
+                    if (pid == -1) {
+                        // Shouldn't happen, unless BUGREPORT_FINISHED is received from a legacy,
+                        // out-of-sync dumpstate process.
+                        Log.w(TAG, "Missing " + EXTRA_PID + " on intent " + intent);
+                    }
+                    stopProgress(pid, intent);
+                    break;
+                default:
+                    Log.w(TAG, "Unsupported intent: " + action);
+            }
+            return;
+
+        }
+
+        /**
+         * Creates the {@link BugreportInfo} for a process and issue a system notification to
+         * indicate its progress.
+         *
+         * @return whether it succeeded or not.
+         */
+        private boolean startProgress(String name, int pid, int max) {
+            if (name == null) {
+                Log.w(TAG, "Missing " + EXTRA_NAME + " on start intent");
+                name = "N/A";
+            }
+            if (pid == -1) {
+                Log.e(TAG, "Missing " + EXTRA_PID + " on start intent");
+                return false;
+            }
+            if (max <= 0) {
+                Log.e(TAG, "Invalid value for extra " + EXTRA_MAX + ": " + max);
+                return false;
+            }
+
+            final BugreportInfo info = new BugreportInfo(pid, name, max);
+            synchronized (mProcesses) {
+                if (mProcesses.indexOfKey(pid) >= 0) {
+                    Log.w(TAG, "PID " + pid + " already watched");
+                } else {
+                    mProcesses.put(info.pid, info);
+                }
+            }
+            updateProgress(info);
+            return true;
+        }
+
+        /**
+         * Updates the system notification for a given bug report.
+         */
+        private void updateProgress(BugreportInfo info) {
+            if (info.max <= 0 || info.progress < 0 || info.name == null) {
+                Log.e(TAG, "Invalid progress values for " + info);
+                return;
+            }
+
+            final Context context = getApplicationContext();
+            final NumberFormat nf = NumberFormat.getPercentInstance();
+            nf.setMinimumFractionDigits(2);
+            nf.setMaximumFractionDigits(2);
+            final String percentText = nf.format((double) info.progress / info.max);
+
+            final String title = context.getString(R.string.bugreport_in_progress_title);
+            final Notification notification = new Notification.Builder(context)
+                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+                    .setContentTitle(title)
+                    .setTicker(title)
+                    .setContentText(info.name)
+                    .setContentInfo(percentText)
+                    .setProgress(info.max, info.progress, false)
+                    // TODO: .setOngoing(true) once it has a CANCEL action
+                    .setLocalOnly(true)
+                    .setColor(context.getColor(
+                            com.android.internal.R.color.system_notification_accent_color))
+                    .build();
+
+            NotificationManager.from(context).notify(TAG, info.pid, notification);
+        }
+
+        /**
+         * Finalizes the progress on a given process and sends the finished intent.
+         */
+        private void stopProgress(int pid, Intent intent) {
+            synchronized (mProcesses) {
+                if (mProcesses.indexOfKey(pid) < 0) {
+                    Log.w(TAG, "PID not watched: " + pid);
+                } else {
+                    mProcesses.remove(pid);
+                }
+                stopSelfWhenDone();
+            }
+            if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): cancel notification");
+            NotificationManager.from(getApplicationContext()).cancel(TAG, pid);
+            if (intent != null) {
+                // Bug report finished fine: send a new, different notification.
+                if (DEBUG) Log.v(TAG, "stopProgress(" + pid + "): finish bug report");
+                onBugreportFinished(pid, intent);
+            }
+        }
+
+        /**
+         * Poll {@link SystemProperties} to get the progress on each monitored process.
+         */
+        private void pollProgress() {
+            synchronized (mProcesses) {
+                if (mProcesses.size() == 0) {
+                    Log.d(TAG, "No process to poll progress.");
+                }
+                for (int i = 0; i < mProcesses.size(); i++) {
+                    int pid = mProcesses.keyAt(i);
+                    String property = String.format(PROGRESS_PROPERTY_TEMPLATE, pid);
+                    int progress;
+                    try {
+                        progress = SystemProperties.getInt(property, 0);
+                    } catch (IllegalArgumentException e) {
+                        Log.v(TAG, "Could not read system property " + property, e);
+                        continue;
+                    }
+                    if (progress == 0) {
+                        Log.v(TAG, "System property " + property + " is not set yet");
+                        continue;
+                    }
+
+                    BugreportInfo info = mProcesses.valueAt(i);
+
+                    if (progress != info.progress) {
+                        if (DEBUG) Log.v(TAG, "Updating progress for PID " + pid + " from "
+                                + info.progress + " to " + progress);
+                        info.progress = progress;
+                        info.lastUpdate = System.currentTimeMillis();
+                        updateProgress(info);
+                    } else {
+                        long inactiveTime = System.currentTimeMillis() - info.lastUpdate;
+                        if (inactiveTime >= INACTIVITY_TIMEOUT) {
+                            Log.w(TAG, "No progress update for process " + pid + " since "
+                                    + info.getFormattedLastUpdate());
+                            stopProgress(info.pid, null);
+                        }
+                    }
+                }
+                // Keep polling...
+                sendEmptyMessageDelayed(MSG_POLL, POLLING_FREQUENCY);
+            }
+        }
+
+        /**
+         * Finishes the service when it's not monitoring any more processes.
+         */
+        private void stopSelfWhenDone() {
+            synchronized (mProcesses) {
+                if (mProcesses.size() > 0) {
+                    if (DEBUG) Log.v(TAG, "Staying alive, waiting for pids " + mProcesses);
+                    return;
+                }
+                Log.v(TAG, "No more pids to handle, shutting down");
+                stopSelf();
+            }
+        }
+
+        private void onBugreportFinished(int pid, Intent intent) {
+            final Context context = getApplicationContext();
+            final Configuration conf = context.getResources().getConfiguration();
+            final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
+            final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT);
+
+            if ((conf.uiMode & Configuration.UI_MODE_TYPE_MASK) != Configuration.UI_MODE_TYPE_WATCH) {
+                triggerLocalNotification(context, pid, bugreportFile, screenshotFile);
+            }
+        }
     }
 
     /**
-     * Responsible for triggering a notification that allows the user to start a
-     * "share" intent with the bug report. On watches we have other methods to allow the user to
-     * start this intent (usually by triggering it on another connected device); we don't need to
-     * display the notification in this case.
+     * Responsible for triggering a notification that allows the user to start a "share" intent with
+     * the bug report. On watches we have other methods to allow the user to start this intent
+     * (usually by triggering it on another connected device); we don't need to display the
+     * notification in this case.
      */
-    private static void triggerLocalNotification(final Context context, final File bugreportFile,
-            final File screenshotFile) {
+    private static void triggerLocalNotification(final Context context, final int pid,
+            final File bugreportFile, final File screenshotFile) {
         if (!bugreportFile.exists() || !bugreportFile.canRead()) {
             Log.e(TAG, "Could not read bugreport file " + bugreportFile);
             Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
@@ -103,10 +407,10 @@
         boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
         if (!isPlainText) {
             // Already zipped, send it right away.
-            sendBugreportNotification(context, bugreportFile, screenshotFile);
+            sendBugreportNotification(context, pid, bugreportFile, screenshotFile);
         } else {
             // Asynchronously zip the file first, then send it.
-            sendZippedBugreportNotification(context, bugreportFile, screenshotFile);
+            sendZippedBugreportNotification(context, pid, bugreportFile, screenshotFile);
         }
     }
 
@@ -155,7 +459,7 @@
     /**
      * Sends a bugreport notitication.
      */
-    private static void sendBugreportNotification(Context context, File bugreportFile,
+    private static void sendBugreportNotification(Context context, int pid, File bugreportFile,
             File screenshotFile) {
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
@@ -173,10 +477,11 @@
         }
         notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
+        final String title = context.getString(R.string.bugreport_finished_title);
         final Notification.Builder builder = new Notification.Builder(context)
                 .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
-                .setContentTitle(context.getString(R.string.bugreport_finished_title))
-                .setTicker(context.getString(R.string.bugreport_finished_title))
+                .setContentTitle(title)
+                .setTicker(title)
                 .setContentText(context.getString(R.string.bugreport_finished_text))
                 .setContentIntent(PendingIntent.getActivity(
                         context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT))
@@ -185,19 +490,19 @@
                 .setColor(context.getColor(
                         com.android.internal.R.color.system_notification_accent_color));
 
-        NotificationManager.from(context).notify(TAG, 0, builder.build());
+        NotificationManager.from(context).notify(TAG, pid, builder.build());
     }
 
     /**
      * Sends a zipped bugreport notification.
      */
     private static void sendZippedBugreportNotification(final Context context,
-            final File bugreportFile, final File screenshotFile) {
+            final int pid, final File bugreportFile, final File screenshotFile) {
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... params) {
                 File zippedFile = zipBugreport(bugreportFile);
-                sendBugreportNotification(context, zippedFile, screenshotFile);
+                sendBugreportNotification(context, pid, zippedFile, screenshotFile);
                 return null;
             }
         }.execute();
@@ -213,8 +518,8 @@
         Log.v(TAG, "zipping " + bugreportPath + " as " + zippedPath);
         File bugreportZippedFile = new File(zippedPath);
         try (InputStream is = new FileInputStream(bugreportFile);
-            ZipOutputStream zos = new ZipOutputStream(
-                new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
+                ZipOutputStream zos = new ZipOutputStream(
+                        new BufferedOutputStream(new FileOutputStream(bugreportZippedFile)))) {
             ZipEntry entry = new ZipEntry(bugreportFile.getName());
             entry.setTime(bugreportFile.lastModified());
             zos.putNextEntry(entry);
@@ -230,8 +535,8 @@
             }
             return bugreportZippedFile;
         } catch (IOException e) {
-          Log.e(TAG, "exception zipping file " + zippedPath, e);
-          return bugreportFile;  // Return original.
+            Log.e(TAG, "exception zipping file " + zippedPath, e);
+            return bugreportFile; // Return original.
         }
     }
 
@@ -281,4 +586,55 @@
             return null;
         }
     }
+
+    /**
+     * Information about a bug report process while its in progress.
+     */
+    private static final class BugreportInfo {
+        /**
+         * {@code pid} of the {@code dumpstate} process generating the bug report.
+         */
+        final int pid;
+
+        /**
+         * Name of the bug report, will be used to rename the final files.
+         * <p>
+         * Initial value is the bug report filename reported by {@code dumpstate}, but user can
+         * change it later to a more meaningful name.
+         */
+        final String name;
+
+        /**
+         * Maximum progress of the bug report generation.
+         */
+        final int max;
+
+        /**
+         * Current progress of the bug report generation.
+         */
+        int progress;
+
+        /**
+         * Time of the last progress update.
+         */
+        long lastUpdate = System.currentTimeMillis();
+
+        BugreportInfo(int pid, String name, int max) {
+            this.pid = pid;
+            this.name = name;
+            this.max = max;
+        }
+
+        String getFormattedLastUpdate() {
+            return SimpleDateFormat.getDateTimeInstance().format(new Date(lastUpdate));
+        }
+
+        @Override
+        public String toString() {
+            final float percent = ((float) progress * 100 / max);
+            return String.format("Progress for %s (pid=%d): %d/%d (%2.2f%%) Last update: %s", name,
+                    pid, progress, max, percent,
+                    getFormattedLastUpdate());
+        }
+    }
 }
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index f1da14d..5133162 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -17,6 +17,8 @@
 package com.android.shell;
 
 import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_ORIGINAL_INTENT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
 import static com.android.shell.BugreportProgressService.getFileExtra;
 
 import java.io.File;
@@ -50,13 +52,16 @@
         // Clean up older bugreports in background
         cleanupOldFiles(intent);
 
-        // Delegate to service.
+        // Delegate intent handling to service.
         Intent serviceIntent = new Intent(context, BugreportProgressService.class);
-        serviceIntent.putExtras(intent.getExtras());
+        serviceIntent.putExtra(EXTRA_ORIGINAL_INTENT, intent);
         context.startService(serviceIntent);
     }
 
     private void cleanupOldFiles(Intent intent) {
+        if (!INTENT_BUGREPORT_FINISHED.equals(intent.getAction())) {
+            return;
+        }
         final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT);
         final PendingResult result = goAsync();
         new AsyncTask<Void, Void, Void>() {
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 1bdd9dd..33c4ef1 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -19,7 +19,12 @@
 import static android.test.MoreAsserts.assertContainsRegex;
 import static com.android.shell.ActionSendMultipleConsumerActivity.UI_NAME;
 import static com.android.shell.BugreportProgressService.EXTRA_BUGREPORT;
+import static com.android.shell.BugreportProgressService.EXTRA_MAX;
+import static com.android.shell.BugreportProgressService.EXTRA_NAME;
+import static com.android.shell.BugreportProgressService.EXTRA_PID;
 import static com.android.shell.BugreportProgressService.EXTRA_SCREENSHOT;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_FINISHED;
+import static com.android.shell.BugreportProgressService.INTENT_BUGREPORT_STARTED;
 
 import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;
@@ -96,6 +101,33 @@
         cancelExistingNotifications();
     }
 
+    public void testFullWorkflow() throws Exception {
+        final String name = "BUG, Y U NO REPORT?";
+        // TODO: call method to remove property instead
+        SystemProperties.set("dumpstate.42.progress", "-1");
+
+        Intent intent = new Intent(INTENT_BUGREPORT_STARTED);
+        intent.putExtra(EXTRA_PID, 42);
+        intent.putExtra(EXTRA_NAME, name);
+        intent.putExtra(EXTRA_MAX, 1000);
+        mContext.sendBroadcast(intent);
+
+        assertProgressNotification(name, "0.00%");
+
+        SystemProperties.set("dumpstate.42.progress", "108");
+        assertProgressNotification(name, "10.80%");
+
+        SystemProperties.set("dumpstate.42.progress", "500");
+        assertProgressNotification(name, "50.00%");
+
+        createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
+        createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
+        Bundle extras = sendBugreportFinishedIntent(42, PLAIN_TEXT_PATH, SCREENSHOT_PATH);
+        assertActionSendMultiple(extras, BUGREPORT_CONTENT, SCREENSHOT_CONTENT);
+
+        // TODO: assert service is down
+    }
+
     public void testBugreportFinished_plainBugreportAndScreenshot() throws Exception {
         createTextFile(PLAIN_TEXT_PATH, BUGREPORT_CONTENT);
         createTextFile(SCREENSHOT_PATH, SCREENSHOT_CONTENT);
@@ -131,13 +163,32 @@
         }
     }
 
+    private void assertProgressNotification(String name, String percent) {
+        // TODO: it current looks for 3 distinct objects, without taking advantage of their
+        // relationship.
+        String title = mContext.getString(R.string.bugreport_in_progress_title);
+        Log.v(TAG, "Looking for progress notification title: '" + title+ "'");
+        mUiBot.getNotification(title);
+        Log.v(TAG, "Looking for progress notification details: '" + name + "-" + percent + "'");
+        mUiBot.getObject(name);
+        mUiBot.getObject(percent);
+    }
+
     /**
      * Sends a "bugreport finished" intent and waits for the result.
      *
      * @return extras sent to the bugreport finished consumer.
      */
     private Bundle sendBugreportFinishedIntent(String bugreportPath, String screenshotPath) {
-        Intent intent = new Intent("android.intent.action.BUGREPORT_FINISHED");
+        return sendBugreportFinishedIntent(null, bugreportPath, screenshotPath);
+    }
+
+    private Bundle sendBugreportFinishedIntent(Integer pid, String bugreportPath,
+            String screenshotPath) {
+        Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
+        if (pid != null) {
+            intent.putExtra(EXTRA_PID, pid);
+        }
         if (bugreportPath != null) {
             intent.putExtra(EXTRA_BUGREPORT, bugreportPath);
         }
diff --git a/packages/Shell/tests/src/com/android/shell/UiBot.java b/packages/Shell/tests/src/com/android/shell/UiBot.java
index f5dd31c..fa1714e 100644
--- a/packages/Shell/tests/src/com/android/shell/UiBot.java
+++ b/packages/Shell/tests/src/com/android/shell/UiBot.java
@@ -42,32 +42,49 @@
     }
 
     /**
-     * Opens the system notification and clicks a given notification.
+     * Opens the system notification and gets a given notification.
      *
      * @param text Notificaton's text as displayed by the UI.
+     * @return notification object.
      */
-    public void clickOnNotification(String text) {
+    public UiObject getNotification(String text) {
         boolean opened = mDevice.openNotification();
         Log.v(TAG, "openNotification(): " + opened);
         boolean gotIt = mDevice.wait(Until.hasObject(By.pkg(SYSTEMUI_PACKAGED)), mTimeout);
         assertTrue("could not get system ui (" + SYSTEMUI_PACKAGED + ")", gotIt);
 
-        gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
-        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+        return getObject(text);
+    }
 
-        UiObject notification = getVisibleObject(text);
-
+    /**
+     * Opens the system notification and clicks a given notification.
+     *
+     * @param text Notificaton's text as displayed by the UI.
+     */
+    public void clickOnNotification(String text) {
+        UiObject notification = getNotification(text);
         click(notification, "bug report notification");
     }
 
     /**
-     * Gets an object which is guaranteed to be present in the current UI.\
+     * Gets an object that might not yet be available in current UI.
+     *
+     * @param text Object's text as displayed by the UI.
+     */
+    public UiObject getObject(String text) {
+        boolean gotIt = mDevice.wait(Until.hasObject(By.text(text)), mTimeout);
+        assertTrue("object with text '(" + text + "') not visible yet", gotIt);
+        return getVisibleObject(text);
+    }
+
+    /**
+     * Gets an object which is guaranteed to be present in the current UI.
      *
      * @param text Object's text as displayed by the UI.
      */
     public UiObject getVisibleObject(String text) {
         UiObject uiObject = mDevice.findObject(new UiSelector().text(text));
-        assertTrue("could not find object with text '(" + text + "')", uiObject.exists());
+        assertTrue("could not find object with text '" + text + "'", uiObject.exists());
         return uiObject;
     }
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6fda2c6..2f79adf 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -87,6 +87,7 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
     <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
 
diff --git a/packages/SystemUI/res/drawable/notification_expand_more.xml b/packages/SystemUI/res/drawable/notification_expand_more.xml
index 5aa7937..430fb0d 100644
--- a/packages/SystemUI/res/drawable/notification_expand_more.xml
+++ b/packages/SystemUI/res/drawable/notification_expand_more.xml
@@ -12,7 +12,7 @@
     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.
+    limitations under the License._more
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:width="22.0dp"
diff --git a/packages/SystemUI/res/layout/notification_header.xml b/packages/SystemUI/res/layout/notification_header.xml
deleted file mode 100644
index 3475d00..0000000
--- a/packages/SystemUI/res/layout/notification_header.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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
-  -->
-
-<com.android.systemui.statusbar.notification.NotificationHeaderView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/notification_header_height"
-    android:clipChildren="false">
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="wrap_content"
-        android:layout_height="@dimen/notification_header_height"
-        android:layout_gravity="start"
-        android:gravity="center_vertical"
-        android:paddingStart="@dimen/notification_content_margin_start">
-        <ImageView
-            android:id="@+id/header_notification_icon"
-            android:layout_width="24dp"
-            android:layout_height="24dp"
-            android:layout_marginEnd="4dp"
-            />
-        <TextView
-            android:id="@+id/number_of_children"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.Material.Notification"
-            android:layout_marginEnd="2dp"
-            android:layout_marginStart="1dp"
-            />
-        <TextView
-            android:id="@+id/app_name_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
-            android:layout_marginStart="4dp"
-            android:layout_marginEnd="8dp"
-            />
-        <TextView
-            android:id="@+id/app_title_sub_text_divider"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
-            android:layout_marginEnd="8dp"
-            android:text="@string/notification_header_divider_symbol"/>
-        <TextView
-            android:id="@+id/title_sub_text"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.Material.Notification.HeaderTitle"
-            android:layout_marginEnd="8dp" />
-        <TextView
-            android:id="@+id/post_time"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="end|center_vertical"
-            android:textAppearance="@*android:style/TextAppearance.Material.Notification.Time"
-            android:paddingEnd="8dp"
-            />
-    </LinearLayout>
-    <ImageButton
-        android:id="@+id/notification_expand_button"
-        android:background="@null"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:layout_gravity="end|center_vertical"
-        android:src="@drawable/notification_expand_more"
-        android:layout_marginEnd="8dp"
-        />
-</com.android.systemui.statusbar.notification.NotificationHeaderView>
diff --git a/packages/SystemUI/res/layout/recents_history.xml b/packages/SystemUI/res/layout/recents_history.xml
index de70d30..b65a5c5 100644
--- a/packages/SystemUI/res/layout/recents_history.xml
+++ b/packages/SystemUI/res/layout/recents_history.xml
@@ -19,16 +19,6 @@
     android:layout_height="match_parent"
     android:background="#99000000"
     android:orientation="vertical">
-    <TextView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:padding="14dp"
-        android:gravity="start"
-        android:text="@string/recents_history_label"
-        android:textSize="24sp"
-        android:textColor="#FFFFFF"
-        android:fontFamily="sans-serif-medium" />
     <android.support.v7.widget.RecyclerView
         android:id="@+id/list"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/recents_history_button.xml b/packages/SystemUI/res/layout/recents_history_button.xml
index 471f518..601c5ed 100644
--- a/packages/SystemUI/res/layout/recents_history_button.xml
+++ b/packages/SystemUI/res/layout/recents_history_button.xml
@@ -22,5 +22,9 @@
     android:textSize="14sp"
     android:textColor="#FFFFFF"
     android:textAllCaps="true"
+    android:shadowColor="#99000000"
+    android:shadowDx="0"
+    android:shadowDy="2"
+    android:shadowRadius="5"
     android:fontFamily="sans-serif-medium"
     android:visibility="invisible" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml
index b9088ec..0cea7ae 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_row.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml
@@ -61,14 +61,6 @@
         />
 
     <ViewStub
-        android:layout="@layout/notification_header"
-        android:id="@+id/notification_header_stub"
-        android:inflatedId="@+id/notification_header"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/notification_header_height"
-        />
-
-    <ViewStub
         android:layout="@layout/notification_guts"
         android:id="@+id/notification_guts_stub"
         android:inflatedId="@+id/notification_guts"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 26a0577..bfd8af9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -109,7 +109,6 @@
     <color name="assist_orb_color">#ffffff</color>
 
     <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
-    <color name="doze_small_icon_background_color">#ff434343</color>
 
     <!-- The color of the navigation bar icons. Need to be in sync with ic_sysbar_* -->
     <color name="navigation_bar_icon_color">#E5FFFFFF</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c85ada8..086e9f4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -48,11 +48,14 @@
     <!-- Height of a single line notification in the status bar -->
     <dimen name="notification_single_line_height">32sp</dimen>
 
-    <!-- Height of a small notification in the status bar -->
-    <dimen name="notification_min_height">64dp</dimen>
+    <!-- Height of a small notification in the status bar-->
+    <dimen name="notification_min_height">84dp</dimen>
+
+    <!-- Height of a small notification in the status bar which was used before android N -->
+    <dimen name="notification_min_height_legacy">64dp</dimen>
 
     <!-- Height of a large notification in the status bar -->
-    <dimen name="notification_max_height">256dp</dimen>
+    <dimen name="notification_max_height">276dp</dimen>
 
     <!-- Height of a medium notification in the status bar -->
     <dimen name="notification_mid_height">128dp</dimen>
@@ -214,9 +217,6 @@
     <!-- The amount to offset when animating into an affiliate group. -->
     <dimen name="recents_task_view_affiliate_group_enter_offset">64dp</dimen>
 
-    <!-- The alpha to apply to a task thumbnail. -->
-    <item name="recents_task_view_thumbnail_alpha" format="float" type="dimen">0.9</item>
-
     <!-- The height of a task view bar. -->
     <dimen name="recents_task_bar_height">56dp</dimen>
 
@@ -261,7 +261,7 @@
 
     <!-- bottom_stack_peek_amount + notification_min_height
          + notification_collapse_second_card_padding -->
-    <dimen name="min_stack_height">84dp</dimen>
+    <dimen name="min_stack_height">104dp</dimen>
 
     <!-- The height of the area before the bottom stack in which the notifications slow down -->
     <dimen name="bottom_stack_slow_down_length">12dp</dimen>
@@ -279,7 +279,7 @@
     <dimen name="notification_padding_dimmed">0dp</dimen>
 
     <!-- The padding between the individual notification cards. -->
-    <dimen name="notification_padding">4dp</dimen>
+    <dimen name="notification_padding">2dp</dimen>
 
     <!-- The minimum amount of top overscroll to go to the quick settings. -->
     <dimen name="min_top_overscroll_to_qs">36dp</dimen>
@@ -299,7 +299,7 @@
     <!-- Falsing threshold used when dismissing notifications from the lockscreen. -->
     <dimen name="swipe_helper_falsing_threshold">70dp</dimen>
 
-    <dimen name="notifications_top_padding">8dp</dimen>
+    <dimen name="notifications_top_padding">4dp</dimen>
     
     <!-- Minimum distance the user has to drag down to go to the full shade. -->
     <dimen name="keyguard_drag_down_min_distance">100dp</dimen>
@@ -351,9 +351,6 @@
     <!-- radius of the corners of the material rounded rect background but negative-->
     <dimen name="notification_material_rounded_rect_radius_negative">-2dp</dimen>
 
-    <!-- height of notification header view if present -->
-    <dimen name="notification_header_height">32dp</dimen>
-
     <!-- The padding between notification children -->
     <dimen name="notification_children_padding">2dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 04233ba..d6a361c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -753,9 +753,6 @@
     <!-- Text for overflow card on Keyguard when there is not enough space for all notifications on Keyguard. [CHAR LIMIT=1] -->
     <string name="keyguard_more_overflow_text">+<xliff:g id="number_of_notifications" example="5">%d</xliff:g></string>
 
-    <!-- The divider symbol between different parts of the notification header. not translatable [CHAR LIMIT=1] -->
-    <string name="notification_header_divider_symbol" translatable="false">•</string>
-
     <!-- An explanation for the visual speed bump in the notifications, which will appear when you click on it. [CHAR LIMIT=50] -->
     <string name="speed_bump_explanation">Less urgent notifications below</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index d9f7a46..051921a 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -509,7 +509,7 @@
         if (canBeExpanded) {
             if (DEBUG) Log.d(TAG, "working on an expandable child");
             mNaturalHeight = mScaler.getNaturalHeight();
-            mSmallSize = v.getMinHeight();
+            mSmallSize = v.getMinExpandHeight();
         } else {
             if (DEBUG) Log.d(TAG, "working on a non-expandable child");
             mNaturalHeight = mOldHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
index eddf2b1..5b8d3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ViewInvertHelper.java
@@ -26,6 +26,8 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import java.util.ArrayList;
+
 /**
  * Helper to invert the colors of views and fade between the states.
  */
@@ -33,14 +35,24 @@
 
     private final Paint mDarkPaint = new Paint();
     private final Interpolator mLinearOutSlowInInterpolator;
-    private final View mTarget;
+    private final ArrayList<View> mTargets;
     private final ColorMatrix mMatrix = new ColorMatrix();
     private final ColorMatrix mGrayscaleMatrix = new ColorMatrix();
     private final long mFadeDuration;
 
     public ViewInvertHelper(View target, long fadeDuration) {
-        mTarget = target;
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTarget.getContext(),
+        this(constructArray(target), fadeDuration);
+    }
+
+    private static ArrayList<View> constructArray(View target) {
+        final ArrayList<View> views = new ArrayList<>();
+        views.add(target);
+        return views;
+    }
+
+    public ViewInvertHelper(ArrayList<View> targets, long fadeDuration) {
+        mTargets = targets;
+        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(mTargets.get(0).getContext(),
                 android.R.interpolator.linear_out_slow_in);
         mFadeDuration = fadeDuration;
     }
@@ -53,14 +65,18 @@
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 updateInvertPaint((Float) animation.getAnimatedValue());
-                mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+                for (int i = 0; i < mTargets.size(); i++) {
+                    mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+                }
             }
         });
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (!invert) {
-                    mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+                    for (int i = 0; i < mTargets.size(); i++) {
+                        mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+                    }
                 }
             }
         });
@@ -73,16 +89,16 @@
     public void update(boolean invert) {
         if (invert) {
             updateInvertPaint(1f);
-            mTarget.setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+            for (int i = 0; i < mTargets.size(); i++) {
+                mTargets.get(i).setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+            }
         } else {
-            mTarget.setLayerType(View.LAYER_TYPE_NONE, null);
+            for (int i = 0; i < mTargets.size(); i++) {
+                mTargets.get(i).setLayerType(View.LAYER_TYPE_NONE, null);
+            }
         }
     }
 
-    public View getTarget() {
-        return mTarget;
-    }
-
     private void updateInvertPaint(float intensity) {
         float components = 1 - 2 * intensity;
         final float[] invert = {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a12a3f1..19b65f7 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -36,7 +36,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
-import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -162,9 +161,6 @@
                 .setColor(mContext.getColor(
                         com.android.internal.R.color.system_notification_accent_color));
         final Notification n = nb.build();
-        if (n.headsUpContentView != null) {
-            n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
-        }
         mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
     }
 
@@ -200,9 +196,6 @@
             mPlaySound = false;
         }
         final Notification n = nb.build();
-        if (n.headsUpContentView != null) {
-            n.headsUpContentView.setViewVisibility(com.android.internal.R.id.right_icon, View.GONE);
-        }
         mNoMan.notifyAsUser(TAG_NOTIFICATION, R.id.notification_power, n, UserHandle.ALL);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 94c45a4..a0c481a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -724,18 +724,6 @@
         getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
     }
 
-    public final void onBusEvent(DragStartEvent event) {
-        // Lock the orientation while dragging
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
-
-        // TODO: docking requires custom accessibility actions
-    }
-
-    public final void onBusEvent(DragEndEvent event) {
-        // Unlock the orientation when dragging completes
-        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
-    }
-
     public final void onBusEvent(LaunchTaskSucceededEvent event) {
         MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 8dd9e47..43db666 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -82,7 +82,11 @@
             if (launchedFromHome) {
                 return numTasks - 1;
             } else {
-                return numTasks - 2;
+                if (flags.isFastToggleRecentsEnabled()) {
+                    return numTasks - 1;
+                } else {
+                    return numTasks - 2;
+                }
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 440ed6b1..cdfad18 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -69,7 +69,6 @@
     public final int smallestWidth;
 
     /** Misc **/
-    public boolean hasDockedTasks;
     public boolean useHardwareLayers;
     public boolean fakeShadows;
     public int svelteLevel;
@@ -112,7 +111,6 @@
         // settings or via multi window
         lockToAppEnabled = !ssp.hasFreeformWorkspaceSupport() &&
                 ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
-        hasDockedTasks = ssp.hasDockedTask();
 
         // Recompute some values based on the given state, since we can not rely on the resource
         // system to get certain values.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
index 957da94..3deeb47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -19,7 +19,6 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
 import com.android.systemui.recents.views.DropTarget;
 import com.android.systemui.recents.views.TaskView;
 
@@ -30,15 +29,13 @@
 
     public final Task task;
     public final TaskView taskView;
-    public final DragView dragView;
     public final DropTarget dropTarget;
     public final ReferenceCountedTrigger postAnimationTrigger;
 
-    public DragEndEvent(Task task, TaskView taskView, DragView dragView, DropTarget dropTarget,
+    public DragEndEvent(Task task, TaskView taskView, DropTarget dropTarget,
             ReferenceCountedTrigger postAnimationTrigger) {
         this.task = task;
         this.taskView = taskView;
-        this.dragView = dragView;
         this.dropTarget = dropTarget;
         this.postAnimationTrigger = postAnimationTrigger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
index 2d42a0e..b81c10c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.recents.events.ui.dragndrop;
 
+import android.graphics.Point;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.views.DragView;
 import com.android.systemui.recents.views.TaskView;
 
 /**
@@ -28,11 +28,11 @@
 
     public final Task task;
     public final TaskView taskView;
-    public final DragView dragView;
+    public final Point tlOffset;
 
-    public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+    public DragStartEvent(Task task, TaskView taskView, Point tlOffset) {
         this.task = task;
         this.taskView = taskView;
-        this.dragView = dragView;
+        this.tlOffset = tlOffset;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
index f48883f..9d3a99f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryView.java
@@ -29,6 +29,8 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -43,6 +45,7 @@
     private RecyclerView mRecyclerView;
     private RecentsHistoryAdapter mAdapter;
     private boolean mIsVisible;
+    private Rect mSystemInsets = new Rect();
 
     private Interpolator mFastOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
@@ -123,7 +126,8 @@
      * Updates the system insets of this history view to the provided values.
      */
     public void setSystemInsets(Rect systemInsets) {
-        setPadding(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+        mSystemInsets.set(systemInsets.left, systemInsets.top, systemInsets.right, systemInsets.bottom);
+        requestLayout();
     }
 
     /**
@@ -142,6 +146,26 @@
     }
 
     @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        RecentsConfiguration config = Recents.getConfiguration();
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
+
+        // Pad the view to align the history with the stack layout
+        Rect taskStackBounds = new Rect();
+        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
+                mSystemInsets.right, new Rect() /* searchBarSpaceBounds */, taskStackBounds);
+        int stackWidthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
+        int stackHeightPadding = mContext.getResources().getDimensionPixelSize(
+                R.dimen.recents_stack_top_padding);
+        mRecyclerView.setPadding(stackWidthPadding + mSystemInsets.left,
+                stackHeightPadding + mSystemInsets.top,
+                stackWidthPadding + mSystemInsets.right, mSystemInsets.bottom);
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         setSystemInsets(insets.getSystemWindowInsets());
         return insets;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index de40a37..2b20c07 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -56,6 +56,7 @@
 import android.util.MutableBoolean;
 import android.util.Pair;
 import android.view.Display;
+import android.view.IDockDividerVisibilityListener;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
@@ -409,7 +410,7 @@
             return thumbnail;
         }
 
-        Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
+        Bitmap thumbnail = getThumbnail(taskId);
         if (thumbnail != null) {
             thumbnail.setHasAlpha(false);
             // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
@@ -429,8 +430,12 @@
     /**
      * Returns a task thumbnail from the activity manager
      */
-    public static Bitmap getThumbnail(ActivityManager activityManager, int taskId) {
-        ActivityManager.TaskThumbnail taskThumbnail = activityManager.getTaskThumbnail(taskId);
+    public Bitmap getThumbnail(int taskId) {
+        if (mAm == null) {
+            return null;
+        }
+
+        ActivityManager.TaskThumbnail taskThumbnail = mAm.getTaskThumbnail(taskId);
         if (taskThumbnail == null) return null;
 
         Bitmap thumbnail = taskThumbnail.mainThumbnail;
@@ -847,4 +852,15 @@
             e.printStackTrace();
         }
     }
+
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (mWm == null) return;
+
+        try {
+            WindowManagerGlobal.getWindowManagerService().registerDockDividerVisibilityListener(
+                    listener);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 5921d13..3bb89a3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -128,7 +128,7 @@
             boolean isStackTask = true;
             if (debugFlags.isHistoryEnabled()) {
                 boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
-                isStackTask = !isFreeformTask && (!isHistoricalTask(t) ||
+                isStackTask = isFreeformTask || (!isHistoricalTask(t) ||
                         (t.lastActiveTime >= lastStackActiveTime &&
                                 i >= (taskCount - MIN_NUM_TASKS)));
                 if (isStackTask && newLastStackActiveTime < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 945fdc1..271a2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.ObjectAnimator;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.util.IntProperty;
@@ -65,6 +64,15 @@
         mCornerRadius = cornerRadius;
     }
 
+    /**
+     * Resets the right and bottom clip for this view.
+     */
+    public void reset() {
+        mClipRect.setEmpty();
+        mSourceView.invalidateOutline();
+        updateClipBounds();
+    }
+
     @Override
     public void getOutline(View view, Outline outline) {
         outline.setAlpha(mMinAlpha + mAlpha / (1f - mMinAlpha));
@@ -82,15 +90,6 @@
         }
     }
 
-    /**
-     * Animates the bottom clip.
-     */
-    public void animateClipBottom(int bottom) {
-        ObjectAnimator animator = ObjectAnimator.ofInt(this, CLIP_BOTTOM, getClipBottom(), bottom);
-        animator.setDuration(150);
-        animator.start();
-    }
-
     /** Sets the bottom clip. */
     public void setClipBottom(int bottom, boolean force) {
         if (bottom != mClipRect.bottom || force) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
deleted file mode 100644
index 96dfaac..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
+++ /dev/null
@@ -1,39 +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.systemui.recents.views;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.widget.ImageView;
-
-public class DragView extends ImageView {
-
-    // The offset from the top-left of the dragBitmap
-    Point mTopLeftOffset;
-
-    public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
-        super(context);
-
-        mTopLeftOffset = tlOffset;
-        setImageBitmap(dragBitmap);
-    }
-
-    public Point getTopLeftOffset() {
-        return mTopLeftOffset;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
index 90d62c1..a70b66d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents.views;
 
+import android.graphics.Rect;
 import android.util.Log;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -52,8 +53,8 @@
         int numFreeformTasks = stackLayout.mNumFreeformTasks;
         if (!freeformTasks.isEmpty()) {
             // Calculate the cell width/height depending on the number of freeform tasks
-            mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
-            mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks /
+            mFreeformCellXCount = Math.max(1, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+            mFreeformCellYCount = Math.max(1, (int) Math.ceil((float) numFreeformTasks /
                     mFreeformCellXCount));
             // For now, make the cells square
             mFreeformCellWidth = Math.min(stackLayout.mFreeformRect.width() / mFreeformCellXCount,
@@ -94,15 +95,21 @@
     public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
             TaskStackLayoutAlgorithm stackLayout) {
         if (mTaskIndexMap.containsKey(task.key)) {
-            // This is a freeform task, so lay it out in the freeform workspace
+            Rect taskRect = stackLayout.mTaskRect;
             int taskIndex = mTaskIndexMap.get(task.key);
-            int topOffset = (stackLayout.mFreeformRect.top - stackLayout.mTaskRect.top);
+            int topOffset = (stackLayout.mFreeformRect.top - taskRect.top);
             int x = taskIndex % mFreeformCellXCount;
             int y = taskIndex / mFreeformCellXCount;
-            float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
-            int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
-            int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
-            transformOut.scale = scale * 0.9f;
+
+            int bitmapWidth = task.thumbnail.getWidth();
+            int bitmapHeight = task.thumbnail.getHeight();
+            float thumbnailScale = Math.min((float) mFreeformCellWidth / bitmapWidth,
+                    (float) mFreeformCellHeight / bitmapHeight);
+            float thumbnailWidth = bitmapWidth * thumbnailScale;
+            float thumbnailHeight = bitmapHeight * thumbnailScale;
+            int scaleXOffset = (int) (((1f - thumbnailScale) * thumbnailWidth) / 2);
+            int scaleYOffset = (int) (((1f - thumbnailScale) * thumbnailHeight) / 2);
+            transformOut.scale = thumbnailScale * 0.9f;
             transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
             transformOut.translationY = topOffset + y * mFreeformCellHeight - scaleYOffset;
             transformOut.translationZ = stackLayout.mMaxTranslationZ;
@@ -115,7 +122,6 @@
             if (DEBUG) {
                 Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
             }
-
             return transformOut;
         }
         return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 0557f65..551f067 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -81,7 +81,6 @@
 
     RecentsTransitionHelper mTransitionHelper;
     RecentsViewTouchHandler mTouchHandler;
-    DragView mDragView;
     TaskStack.DockState[] mVisibleDockStates = {
             TaskStack.DockState.LEFT,
             TaskStack.DockState.TOP,
@@ -341,13 +340,6 @@
             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
-        if (mDragView != null) {
-            Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
-            mDragView.measure(
-                    MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
-                    MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
-        }
-
         // Measure the history button with the full space above the stack, but width-constrained
         // to the stack
         Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -379,11 +371,6 @@
             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
 
-        if (mDragView != null) {
-            mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
-                    top + mDragView.getMeasuredHeight());
-        }
-
         // Layout the history button left-aligned with the stack, but offset from the top of the
         // view
         Rect historyButtonRect = mTaskStackView.mLayoutAlgorithm.mHistoryButtonRect;
@@ -461,10 +448,6 @@
     /**** EventBus Events ****/
 
     public final void onBusEvent(DragStartEvent event) {
-        // Add the drag view
-        mDragView = event.dragView;
-        addView(mDragView);
-
         updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
                 TaskStack.DockState.NONE.viewState.dockAreaAlpha);
     }
@@ -480,65 +463,36 @@
     }
 
     public final void onBusEvent(final DragEndEvent event) {
-        final Runnable cleanUpRunnable = new Runnable() {
-            @Override
-            public void run() {
-                // Remove the drag view
-                removeView(mDragView);
-                mDragView = null;
-            }
-        };
-
         // Animate the overlay alpha back to 0
         updateVisibleDockRegions(null, -1);
 
-        if (event.dropTarget == null) {
-            // No drop targets for hit, so just animate the task back to its place
-            event.postAnimationTrigger.increment();
-            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    cleanUpRunnable.run();
-                }
-            });
-            // Animate the task back to where it was before then clean up afterwards
-            TaskViewTransform taskTransform = new TaskViewTransform();
-            TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
-            layoutAlgorithm.getStackTransform(event.task,
-                    mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
-            event.dragView.animate()
-                    .scaleX(taskTransform.scale)
-                    .scaleY(taskTransform.scale)
-                    .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
-                            + taskTransform.translationX)
-                    .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
-                            + taskTransform.translationY)
-                    .setDuration(175)
-                    .setInterpolator(mFastOutSlowInInterpolator)
-                    .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
-                    .start();
-
-        } else if (event.dropTarget instanceof TaskStack.DockState) {
+        // Handle the case where we drop onto a dock region
+        if (event.dropTarget instanceof TaskStack.DockState) {
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
 
-            // For now, just remove the drag view and the original task
-            // TODO: Animate the task to the drop target rect before launching it above
-            cleanUpRunnable.run();
+            // Remove the task after it is docked
+            if (event.taskView.isFocusedTask()) {
+                mTaskStackView.resetFocusedTask();
+            }
+            event.taskView.animate()
+                    .alpha(0f)
+                    .setDuration(150)
+                    .setInterpolator(mFastOutLinearInInterpolator)
+                    .setUpdateListener(null)
+                    .setListener(null)
+                    .withEndAction(new Runnable() {
+                        @Override
+                        public void run() {
+                            mTaskStackView.getStack().removeTask(event.task);
+                        }
+                    })
+                    .withLayer()
+                    .start();
 
             // Dock the task and launch it
             SystemServicesProxy ssp = Recents.getSystemServices();
             ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
             launchTask(event.task, null, INVALID_STACK_ID);
-
-        } else {
-            // We dropped on another drop target, so just add the cleanup to the post animation
-            // trigger
-            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-                @Override
-                public void run() {
-                    cleanUpRunnable.run();
-                }
-            });
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index c7edc92..2920295 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -27,6 +27,7 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
@@ -64,8 +65,8 @@
 
     private Task mDragTask;
     private TaskView mTaskView;
-    private DragView mDragView;
 
+    private Point mTaskViewOffset = new Point();
     private Point mDownPos = new Point();
     private boolean mDragging;
 
@@ -111,20 +112,24 @@
     /**** Events ****/
 
     public final void onBusEvent(DragStartEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
         mRv.getParent().requestDisallowInterceptTouchEvent(true);
         mDragging = true;
         mDragTask = event.task;
         mTaskView = event.taskView;
-        mDragView = event.dragView;
         mDropTargets.clear();
 
-        float x = mDownPos.x - mDragView.getTopLeftOffset().x;
-        float y = mDownPos.y - mDragView.getTopLeftOffset().y;
-        mDragView.setTranslationX(x);
-        mDragView.setTranslationY(y);
+        int[] recentsViewLocation = new int[2];
+        mRv.getLocationInWindow(recentsViewLocation);
+        mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
+                mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
+        float x = mDownPos.x - mTaskViewOffset.x;
+        float y = mDownPos.y - mTaskViewOffset.y;
+        mTaskView.setTranslationX(x);
+        mTaskView.setTranslationY(y);
 
         RecentsConfiguration config = Recents.getConfiguration();
-        if (!config.hasDockedTasks) {
+        if (!ssp.hasDockedTask()) {
             // Add the dock state drop targets (these take priority)
             TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
             for (TaskStack.DockState dockState : dockStates) {
@@ -140,7 +145,6 @@
         mDragging = false;
         mDragTask = null;
         mTaskView = null;
-        mDragView = null;
         mLastDropTarget = null;
     }
 
@@ -160,8 +164,8 @@
                     int height = mRv.getMeasuredHeight();
                     float evX = ev.getX();
                     float evY = ev.getY();
-                    float x = evX - mDragView.getTopLeftOffset().x;
-                    float y = evY - mDragView.getTopLeftOffset().y;
+                    float x = evX - mTaskViewOffset.x;
+                    float y = evY - mTaskViewOffset.y;
 
                     DropTarget currentDropTarget = null;
                     for (DropTarget target : mDropTargets) {
@@ -176,8 +180,8 @@
                                 currentDropTarget));
                     }
 
-                    mDragView.setTranslationX(x);
-                    mDragView.setTranslationY(y);
+                    mTaskView.setTranslationX(x);
+                    mTaskView.setTranslationY(y);
                 }
                 break;
             }
@@ -187,7 +191,7 @@
                     ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
                             mRv.getContext(), null, null, null);
                     postAnimationTrigger.increment();
-                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+                    EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
                             mLastDropTarget, postAnimationTrigger));
                     postAnimationTrigger.decrement();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 30efd5f..0395e99 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.recents.views;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.Context;
@@ -1420,7 +1424,12 @@
         }
     }
 
+    private AnimatorSet mDropAnimation;
+
     public final void onBusEvent(DragStartEvent event) {
+        // Cancel the existing drop animation
+        Utilities.cancelAnimationWithoutCallbacks(mDropAnimation);
+
         if (event.task.isFreeformTask()) {
             // Animate to the front of the stack
             mStackScroller.animateScroll(mStackScroller.getStackScroll(),
@@ -1441,56 +1450,84 @@
     }
 
     public final void onBusEvent(final DragEndEvent event) {
-        if (event.dropTarget != mFreeformWorkspaceDropTarget &&
-                event.dropTarget != mStackDropTarget) {
-            return;
-        }
-        if (event.task.isFreeformTask() && event.dropTarget == mFreeformWorkspaceDropTarget) {
-            // TODO: Animate back into view
-            return;
-        }
-        if (!event.task.isFreeformTask() && event.dropTarget == mStackDropTarget) {
-            // TODO: Animate back into view
+        // We don't handle drops on the dock regions
+        if (event.dropTarget instanceof TaskStack.DockState) {
             return;
         }
 
-        // Move the task to the right position in the stack (ie. the front of the stack if freeform
-        // or the front of the stack if fullscreen).  Note, we MUST move the tasks before we update
-        // their stack ids, otherwise, the keys will have changed.
-        if (event.dropTarget == mFreeformWorkspaceDropTarget) {
-            mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
-            updateLayout(true);
-        } else if (event.dropTarget == mStackDropTarget) {
-            mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
-            updateLayout(true);
+        boolean isFreeformTask = event.task.isFreeformTask();
+        boolean hasChangedStacks =
+                (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
+                        (isFreeformTask && event.dropTarget == mStackDropTarget);
+        if (hasChangedStacks) {
+            ArrayList<Animator> animations = new ArrayList<>();
+
+            // Move the task to the right position in the stack (ie. the front of the stack if
+            // freeform or the front of the stack if fullscreen).  Note, we MUST move the tasks
+            // before we update their stack ids, otherwise, the keys will have changed.
+            if (event.dropTarget == mFreeformWorkspaceDropTarget) {
+                mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+                updateLayout(true);
+
+                // Update the clipping to match the scaled bitmap rect
+                TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+                float thumbnailScale = thumbnailView.computeThumbnailScale(true);
+                RectF bitmapRect = thumbnailView.getScaledBitmapRect(thumbnailScale);
+                AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+                int clipRight = (int) (thumbnailView.getMeasuredWidth() - bitmapRect.width());
+                int clipBottom = (int) (thumbnailView.getMeasuredHeight() - bitmapRect.height());
+                animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+                        thumbnailView.getBitmapScale(), thumbnailScale));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+                        viewBounds.getClipBottom(), clipBottom));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+                        viewBounds.getClipRight(), clipRight));
+            } else if (event.dropTarget == mStackDropTarget) {
+                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+                updateLayout(true);
+
+                // Reset the clipping when animating to the stack
+                TaskViewThumbnail thumbnailView = event.taskView.mThumbnailView;
+                float thumbnailScale = thumbnailView.computeThumbnailScale(false);
+                AnimateableViewBounds viewBounds = event.taskView.getViewBounds();
+                animations.add(ObjectAnimator.ofFloat(thumbnailView, TaskViewThumbnail.BITMAP_SCALE,
+                        thumbnailView.getBitmapScale(), thumbnailScale));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_BOTTOM,
+                        viewBounds.getClipBottom(), 0));
+                animations.add(ObjectAnimator.ofInt(viewBounds, AnimateableViewBounds.CLIP_RIGHT,
+                        viewBounds.getClipRight(), 0));
+            }
+
+            // Move the task to the new stack in the system after the animation completes
+            event.postAnimationTrigger.increment();
+            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+                @Override
+                public void run() {
+                    SystemServicesProxy ssp = Recents.getSystemServices();
+                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+                }
+            });
+
+            // Animate the normal properties of the view
+            mDropAnimation = new AnimatorSet();
+            mDropAnimation.playTogether(animations);
+            mDropAnimation.setDuration(250);
+            mDropAnimation.setInterpolator(mFastOutSlowInInterpolator);
+            mDropAnimation.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    event.postAnimationTrigger.decrement();
+                }
+            });
+            mDropAnimation.start();
         }
 
         event.postAnimationTrigger.increment();
-        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
-            @Override
-            public void run() {
-                SystemServicesProxy ssp = Recents.getSystemServices();
-                ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
-            }
-        });
+        event.taskView.animate()
+                .withEndAction(event.postAnimationTrigger.decrementAsRunnable());
 
-        // Animate the drag view to the new position
-        mLayoutAlgorithm.getStackTransform(event.task, mStackScroller.getStackScroll(),
-                mTmpTransform, null);
-        event.dragView.animate()
-                .scaleX(mTmpTransform.scale)
-                .scaleY(mTmpTransform.scale)
-                .translationX((mLayoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
-                        + mTmpTransform.translationX)
-                .translationY((mLayoutAlgorithm.mTaskRect.top - event.dragView.getTop())
-                        + mTmpTransform.translationY)
-                .setDuration(175)
-                .setInterpolator(mFastOutSlowInInterpolator)
-                .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
-                .start();
-
-        // Animate the other views into place
-        requestSynchronizeStackViewsWithModel(175);
+        // Animate the tack view back into position
+        requestSynchronizeStackViewsWithModel(250);
     }
 
     public final void onBusEvent(StackViewScrolledEvent event) {
@@ -1522,7 +1559,13 @@
         int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
-            tv.animate().alpha(0f).setDuration(200).start();
+            tv.animate()
+                    .alpha(0f)
+                    .setDuration(200)
+                    .setUpdateListener(null)
+                    .setListener(null)
+                    .withLayer()
+                    .start();
         }
     }
 
@@ -1531,7 +1574,13 @@
         int taskViewCount = taskViews.size();
         for (int i = taskViewCount - 1; i >= 0; i--) {
             TaskView tv = taskViews.get(i);
-            tv.animate().alpha(1f).setDuration(200).start();
+            tv.animate()
+                    .alpha(1f)
+                    .setDuration(200)
+                    .setUpdateListener(null)
+                    .setListener(null)
+                    .withLayer()
+                    .start();
         }
     }
 
@@ -1543,7 +1592,9 @@
         Task task = tv.getTask();
 
         // Reset the previously focused task before it is removed from the stack
-        resetFocusedTask();
+        if (tv.isFocusedTask()) {
+            resetFocusedTask();
+        }
 
         // Announce for accessibility
         tv.announceForAccessibility(getContext().getString(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 6db2eb9..f2c89e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -22,8 +22,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
@@ -244,6 +242,8 @@
     void resetViewProperties() {
         setDim(0);
         setLayerType(View.LAYER_TYPE_NONE, null);
+        setVisibility(View.VISIBLE);
+        getViewBounds().reset();
         TaskViewTransform.reset(this);
         if (mActionButtonView != null) {
             mActionButtonView.setScaleX(1f);
@@ -251,18 +251,6 @@
             mActionButtonView.setAlpha(1f);
             mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
         }
-        setVisibility(View.VISIBLE);
-    }
-
-    /**
-     * When we are un/filtering, this method will set up the transform that we are animating to,
-     * in order to hide the task.
-     */
-    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
-        // Fade the view out and slide it away
-        toTransform.alpha = 0f;
-        toTransform.translationY += 200;
-        toTransform.translationZ = 0;
     }
 
     /** Prepares this task view for the enter-recents animations.  This is called earlier in the
@@ -296,8 +284,6 @@
         }
         // Apply the current dim
         setDim(initialDim);
-        // Prepare the thumbnail view alpha
-        mThumbnailView.prepareEnterRecentsAnimation(isTaskViewLaunchTargetTask);
     }
 
     /** Animates this task view as it enters recents */
@@ -409,6 +395,7 @@
                 .translationY(ctx.offscreenTranslationY)
                 .setStartDelay(0)
                 .setUpdateListener(null)
+                .setListener(null)
                 .setInterpolator(mFastOutLinearInInterpolator)
                 .setDuration(taskViewExitToHomeDuration)
                 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
@@ -425,9 +412,6 @@
                 R.dimen.recents_task_view_affiliate_group_enter_offset);
 
         if (isLaunchingTask) {
-            // Animate the thumbnail alpha back into full opacity for the window animation out
-            mThumbnailView.startLaunchTaskAnimation(postAnimRunnable);
-
             // Animate the dim
             if (mDimAlpha > 0) {
                 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
@@ -448,10 +432,11 @@
                     .setStartDelay(0)
                     .setDuration(taskViewExitToAppDuration)
                     .setInterpolator(mFastOutLinearInInterpolator)
+                    .withEndAction(postAnimRunnable)
                     .start();
         } else {
             // Hide the dismiss button
-            mHeaderView.startLaunchTaskDismissAnimation();
+            mHeaderView.startLaunchTaskDismissAnimation(postAnimRunnable);
             // If this is another view in the task grouping and is in front of the launch task,
             // animate it away first
             if (occludesLaunchTarget) {
@@ -459,6 +444,7 @@
                     .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
                     .setStartDelay(0)
                     .setUpdateListener(null)
+                    .setListener(null)
                     .setInterpolator(mFastOutLinearInInterpolator)
                     .setDuration(taskViewExitToAppDuration)
                     .start();
@@ -480,6 +466,7 @@
             .alpha(0f)
             .setStartDelay(delay)
             .setUpdateListener(null)
+            .setListener(null)
             .setInterpolator(mFastOutSlowInInterpolator)
             .setDuration(taskViewRemoveAnimDuration)
             .withEndAction(new Runnable() {
@@ -637,7 +624,6 @@
         mIsFocused = isFocused;
         mIsFocusAnimated = animated;
         mHeaderView.onTaskViewFocusChanged(isFocused, animated);
-        mThumbnailView.onFocusChanged(isFocused);
         if (isFocused) {
             if (requestViewFocus && !isFocused()) {
                 requestFocus();
@@ -688,21 +674,13 @@
     @Override
     public void onTaskDataLoaded() {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        RecentsConfiguration config = Recents.getConfiguration();
         if (mThumbnailView != null && mHeaderView != null) {
             // Bind each of the views to the new task data
             mThumbnailView.rebindToTask(mTask);
             mHeaderView.rebindToTask(mTask);
             // Rebind any listeners
             mActionButtonView.setOnClickListener(this);
-
-            // Only enable long-click if we have a freeform workspace to drag to/from, or if we
-            // aren't already docked
-            if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) {
-                setOnLongClickListener(this);
-            } else {
-                setOnLongClickListener(null);
-            }
+            setOnLongClickListener(this);
         }
         mTaskDataLoaded = true;
     }
@@ -742,58 +720,27 @@
 
     @Override
     public boolean onLongClick(View v) {
-        if (v == this) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        if (v == this && !ssp.hasDockedTask()) {
             // Start listening for drag events
             setClipViewInStack(false);
 
+            // Enlarge the view slightly
             final float finalScale = getScaleX() * 1.05f;
-            final int width = getWidth();
-            final int height = getHeight();
-            Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            Canvas c = new Canvas(dragBitmap);
-            mThumbnailView.draw(c);
-            mHeaderView.draw(c);
-            c.setBitmap(null);
+            animate()
+                    .scaleX(finalScale)
+                    .scaleY(finalScale)
+                    .setDuration(175)
+                    .setUpdateListener(null)
+                    .setListener(null)
+                    .setInterpolator(mFastOutSlowInInterpolator)
+                    .start();
 
-            // The downTouchPos is relative to the currently transformed TaskView, but we will be
-            // dragging a copy of the full task view, which makes it easier for us to animate them
-            // when the user drops
-            mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
-            mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
+            mDownTouchPos.x += ((1f - getScaleX()) * getWidth()) / 2;
+            mDownTouchPos.y += ((1f - getScaleY()) * getHeight()) / 2;
 
-            // Initiate the drag
-            final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
-            dragView.setOutlineProvider(new ViewOutlineProvider() {
-                @Override
-                public void getOutline(View view, Outline outline) {
-                    outline.setRect(0, 0, width, height);
-                }
-            });
-            dragView.setScaleX(getScaleX());
-            dragView.setScaleY(getScaleY());
-            dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-                @Override
-                public void onViewAttachedToWindow(View v) {
-                    // Hide this task view after the drag view is attached
-                    setVisibility(View.INVISIBLE);
-                    // Animate the alpha slightly to indicate dragging
-                    dragView.setElevation(getElevation());
-                    dragView.setTranslationZ(getTranslationZ());
-                    dragView.animate()
-                            .scaleX(finalScale)
-                            .scaleY(finalScale)
-                            .setDuration(175)
-                            .setInterpolator(mFastOutSlowInInterpolator)
-                            .start();
-                }
-
-                @Override
-                public void onViewDetachedFromWindow(View v) {
-                    // Do nothing
-                }
-            });
             EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
-            EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+            EventBus.getDefault().send(new DragStartEvent(mTask, this, mDownTouchPos));
             return true;
         }
         return false;
@@ -806,9 +753,6 @@
             event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
-                    // Show this task view
-                    setVisibility(View.VISIBLE);
-
                     // Animate the drag view back from where it is, to the view location, then after
                     // it returns, update the clip state
                     setClipViewInStack(true);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 76c6691..85b4b9b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -284,7 +284,7 @@
     }
 
     /** Animates this task bar dismiss button when launching a task. */
-    void startLaunchTaskDismissAnimation() {
+    void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) {
         if (mDismissButton.getVisibility() == View.VISIBLE) {
             int taskViewExitToAppDuration = mContext.getResources().getInteger(
                     R.integer.recents_task_exit_to_app_duration);
@@ -294,6 +294,7 @@
                     .setStartDelay(0)
                     .setInterpolator(mFastOutSlowInInterpolator)
                     .setDuration(taskViewExitToAppDuration)
+                    .withEndAction(postAnimationRunanble)
                     .start();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index b3d263e..c288afb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.recents.views;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
@@ -31,11 +28,12 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
 import android.view.View;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import com.android.systemui.R;
-import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
 
@@ -45,6 +43,19 @@
  */
 public class TaskViewThumbnail extends View {
 
+    public static final Property<TaskViewThumbnail, Float> BITMAP_SCALE =
+            new FloatProperty<TaskViewThumbnail>("bitmapScale") {
+                @Override
+                public void setValue(TaskViewThumbnail object, float scale) {
+                    object.setBitmapScale(scale);
+                }
+
+                @Override
+                public Float get(TaskViewThumbnail object) {
+                    return object.getBitmapScale();
+                }
+            };
+
     private Task mTask;
 
     // Drawing
@@ -60,18 +71,6 @@
 
     Interpolator mFastOutSlowInInterpolator;
 
-    // Thumbnail alpha
-    float mThumbnailAlpha;
-    ValueAnimator mThumbnailAlphaAnimator;
-    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
-            = new ValueAnimator.AnimatorUpdateListener() {
-        @Override
-        public void onAnimationUpdate(ValueAnimator animation) {
-            mThumbnailAlpha = (float) animation.getAnimatedValue();
-            updateThumbnailPaintFilter();
-        }
-    };
-
     // Task bar clipping, the top of this thumbnail can be clipped against the opaque header
     // bar that overlaps this thumbnail
     View mTaskBar;
@@ -105,17 +104,11 @@
     }
 
     @Override
-    protected void onFinishInflate() {
-        mThumbnailAlpha = getResources().getFloat(R.dimen.recents_task_view_thumbnail_alpha);
-        updateThumbnailPaintFilter();
-    }
-
-    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         if (changed) {
             mLayoutRect.set(0, 0, getWidth(), getHeight());
-            updateThumbnailScale();
+            setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
         }
     }
 
@@ -137,12 +130,15 @@
                     Shader.TileMode.CLAMP);
             mDrawPaint.setShader(mBitmapShader);
             mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
-            updateThumbnailScale();
         } else {
             mBitmapShader = null;
             mDrawPaint.setShader(null);
         }
-        updateThumbnailPaintFilter();
+        if (mTask != null) {
+            setBitmapScale(computeThumbnailScale(mTask != null ? mTask.isFreeformTask() : false));
+        } else {
+            setBitmapScale(1f);
+        }
     }
 
     /** Updates the paint to draw the thumbnail. */
@@ -150,36 +146,61 @@
         if (mInvisible) {
             return;
         }
-        int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
-        int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
+        int mul = (int) ((1.0f - mDimAlpha) * 255);
         if (mBitmapShader != null) {
             mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
-            mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
             mDrawPaint.setColorFilter(mLightingColorFilter);
             mDrawPaint.setColor(0xffffffff);
         } else {
-            int grey = mul + add;
+            int grey = mul;
             mDrawPaint.setColorFilter(null);
             mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
         }
         invalidate();
     }
 
-    /** Updates the thumbnail shader's scale transform. */
-    void updateThumbnailScale() {
-        if (mBitmapShader != null) {
-            if (mTask.isFreeformTask()) {
-                // For freeform tasks, we scale the bitmap rect to fit in the layout rect
-                mBitmapScale = Math.min(mLayoutRect.width() / mBitmapRect.width(),
-                        mLayoutRect.height() / mBitmapRect.height());
-            } else {
-                // For stack tasks, we scale the bitmap to fit the width
-                mBitmapScale = Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
-            }
+    /**
+     * Returns the scale to apply to a thumbnail bitmap relative to this view rect.
+     */
+    public float computeThumbnailScale(boolean isFreeformTask) {
+        if (isFreeformTask) {
+            // For freeform tasks, we scale the bitmap rect to fit in the layout rect
+            return Math.min(mLayoutRect.width() / mBitmapRect.width(),
+                    mLayoutRect.height() / mBitmapRect.height());
+        } else {
+            // For stack tasks, we scale the bitmap to fit the width
+            return Math.max(1f, mLayoutRect.width() / mBitmapRect.width());
+        }
+    }
 
+    /**
+     * Returns the scaled bitmap rect.
+     */
+    public RectF getScaledBitmapRect(float scale) {
+        RectF scaledBitmapRect = new RectF(mBitmapRect);
+        scaledBitmapRect.left *= scale;
+        scaledBitmapRect.top *= scale;
+        scaledBitmapRect.right *= scale;
+        scaledBitmapRect.bottom *= scale;
+        return scaledBitmapRect;
+    }
+
+    /**
+     * Sets the scale of the bitmap relative to this view.
+     */
+    public void setBitmapScale(float scale) {
+        if (mBitmapShader != null) {
+            mBitmapScale = scale;
             mScaleMatrix.setScale(mBitmapScale, mBitmapScale);
             mBitmapShader.setLocalMatrix(mScaleMatrix);
         }
+        if (!mInvisible) {
+            invalidate();
+        }
+    }
+
+    public float getBitmapScale() {
+        return mBitmapScale;
     }
 
     /** Updates the clip rect based on the given task bar. */
@@ -227,67 +248,4 @@
         mTask = null;
         setThumbnail(null);
     }
-
-    /** Handles focus changes. */
-    void onFocusChanged(boolean focused) {
-        if (focused) {
-            if (Float.compare(getAlpha(), 1f) != 0) {
-                startFadeAnimation(1f, 150, null);
-            }
-        } else {
-            float taskViewThumbnailAlpha = getResources().getFloat(
-                    R.dimen.recents_task_view_thumbnail_alpha);
-            if (Float.compare(getAlpha(), taskViewThumbnailAlpha) != 0) {
-                startFadeAnimation(taskViewThumbnailAlpha, 150, null);
-            }
-        }
-    }
-
-    /**
-     * Prepares for the enter recents animation, this gets called before the the view
-     * is first visible and will be followed by a startEnterRecentsAnimation() call.
-     */
-    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
-        if (isTaskViewLaunchTargetTask) {
-            mThumbnailAlpha = 1f;
-        } else {
-            mThumbnailAlpha = getResources().getFloat(
-                    R.dimen.recents_task_view_thumbnail_alpha);
-        }
-        updateThumbnailPaintFilter();
-    }
-
-    /** Animates this task thumbnail as it enters Recents. */
-    void startEnterRecentsAnimation(Runnable postAnimRunnable) {
-        float taskViewThumbnailAlpha = getResources().getFloat(
-                R.dimen.recents_task_view_thumbnail_alpha);
-        startFadeAnimation(taskViewThumbnailAlpha,
-                getResources().getInteger(R.integer.recents_task_enter_from_app_duration),
-                postAnimRunnable);
-    }
-
-    /** Animates this task thumbnail as it exits Recents. */
-    void startLaunchTaskAnimation(Runnable postAnimRunnable) {
-        int taskViewExitToAppDuration = mContext.getResources().getInteger(
-                R.integer.recents_task_exit_to_app_duration);
-        startFadeAnimation(1f, taskViewExitToAppDuration, postAnimRunnable);
-    }
-
-    /** Starts a new thumbnail alpha animation. */
-    void startFadeAnimation(float finalAlpha, int duration, final Runnable postAnimRunnable) {
-        Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
-        mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
-        mThumbnailAlphaAnimator.setDuration(duration);
-        mThumbnailAlphaAnimator.setInterpolator(mFastOutSlowInInterpolator);
-        mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
-        if (postAnimRunnable != null) {
-            mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    postAnimRunnable.run();
-                }
-            });
-        }
-        mThumbnailAlphaAnimator.start();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 174ff33..6d43f9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -122,6 +122,7 @@
             } else {
                 anim.setUpdateListener(null);
             }
+            anim.setListener(null);
             anim.setStartDelay(startDelay)
                     .setDuration(duration)
                     .setInterpolator(interp)
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 50e010f..6ff7a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -17,10 +17,14 @@
 package com.android.systemui.stackdivider;
 
 import android.content.res.Configuration;
+import android.view.IDockDividerVisibilityListener;
 import android.view.LayoutInflater;
+import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -33,6 +37,8 @@
     private int mDividerWindowWidth;
     private DividerWindowManager mWindowManager;
     private DividerView mView;
+    private DockDividerVisibilityListener mDockDividerVisibilityListener;
+    private boolean mVisible = false;
 
     @Override
     public void start() {
@@ -41,6 +47,9 @@
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         update(mContext.getResources().getConfiguration());
         putComponent(Divider.class, this);
+        mDockDividerVisibilityListener = new DockDividerVisibilityListener();
+        SystemServicesProxy ssp = Recents.getSystemServices();
+        ssp.registerDockDividerVisibilityListener(mDockDividerVisibilityListener);
     }
 
     @Override
@@ -56,6 +65,7 @@
     private void addDivider(Configuration configuration) {
         mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
+        mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
         final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
         final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
@@ -71,4 +81,23 @@
         removeDivider();
         addDivider(configuration);
     }
+
+    private void updateVisibility(final boolean visible) {
+        mView.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mVisible != visible) {
+                    mVisible = visible;
+                    mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+                }
+            }
+        });
+    }
+
+    class DockDividerVisibilityListener extends IDockDividerVisibilityListener.Stub {
+        @Override
+        public void onDockDividerVisibilityChanged(boolean visible) {
+            updateVisibility(visible);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 723989a..5d4c64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -40,7 +40,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
@@ -193,6 +192,7 @@
     protected IDreamManager mDreamManager;
     PowerManager mPowerManager;
     protected StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    protected int mRowMinHeightLegacy;
     protected int mRowMinHeight;
     protected int mRowMaxHeight;
 
@@ -887,15 +887,6 @@
                 entry.row.setShowingLegacyBackground(true);
                 entry.legacy = true;
             }
-        } else {
-            // Using platform templates
-            final int color = sbn.getNotification().color;
-            if (isMediaNotification(entry)) {
-                entry.row.setTintColor(color == Notification.COLOR_DEFAULT
-                        ? mContext.getColor(
-                                R.color.notification_material_background_media_default_color)
-                        : color);
-            }
         }
 
         if (entry.icon != null) {
@@ -1299,6 +1290,17 @@
         }
     }
 
+    /**
+     * Called when the panel was layouted expanded for the first time after being collapsed.
+     */
+    public void onPanelExpandedAndLayouted() {
+        if (mState == StatusBarState.KEYGUARD) {
+            // Since the number of notifications is determined based on the height of the view, we
+            // need to update them.
+            updateRowStates();
+        }
+    }
+
     protected class H extends Handler {
         public void handleMessage(Message m) {
             switch (m.what) {
@@ -1337,7 +1339,6 @@
         PackageManager pmUser = getPackageManagerForUser(mContext,
                 entry.notification.getUser().getIdentifier());
 
-        int maxHeight = mRowMaxHeight;
         final StatusBarNotification sbn = entry.notification;
         entry.cacheContentViews(mContext, null);
 
@@ -1498,18 +1499,6 @@
 
             Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
             icon.setImageDrawable(iconDrawable);
-            if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP
-                    || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
-                icon.setBackgroundResource(
-                        com.android.internal.R.drawable.notification_icon_legacy_bg);
-                int padding = mContext.getResources().getDimensionPixelSize(
-                        com.android.internal.R.dimen.notification_large_icon_circle_padding);
-                icon.setPadding(padding, padding, padding, padding);
-                if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
-                    icon.getBackground().setColorFilter(
-                            sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
-                }
-            }
 
             if (profileBadge != null) {
                 Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
@@ -1536,11 +1525,6 @@
                         R.style.TextAppearance_Material_Notification_Parenthetical);
             }
 
-            int topPadding = Notification.Builder.calculateTopPadding(mContext,
-                    false /* hasThreeLines */,
-                    mContext.getResources().getConfiguration().fontScale);
-            title.setPadding(0, topPadding, 0, 0);
-
             contentContainerPublic.setContractedChild(publicViewLocal);
             entry.autoRedacted = true;
         }
@@ -1553,7 +1537,7 @@
             }
         }
         entry.row = row;
-        entry.row.setHeightRange(mRowMinHeight, maxHeight);
+        updateNotificationHeightRange(entry);
         entry.row.setOnActivatedListener(this);
         entry.row.setExpandable(bigContentViewLocal != null);
 
@@ -1566,11 +1550,19 @@
             row.setUserExpanded(userExpanded);
         }
         row.setUserLocked(userLocked);
-        row.setEntry(entry);
+        row.updateStatusBarNotification(entry.notification);
         applyRemoteInput(entry);
         return true;
     }
 
+    private void updateNotificationHeightRange(Entry entry) {
+        boolean customView = entry.getContentView().getId()
+                != com.android.internal.R.id.status_bar_latest_event_content;
+        boolean beforeN = entry.targetSdk < Build.VERSION_CODES.N;
+        int minHeight = customView && beforeN ? mRowMinHeightLegacy : mRowMinHeight;
+        entry.row.setHeightRange(minHeight, mRowMaxHeight);
+    }
+
     /**
      * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
      * via first-class API.
@@ -1982,15 +1974,15 @@
     }
 
     /**
+     * @param recompute wheter the number should be recomputed
      * @return The number of notifications we show on Keyguard.
      */
-    protected abstract int getMaxKeyguardNotifications();
+    protected abstract int getMaxKeyguardNotifications(boolean recompute);
 
     /**
      * Updates expanded, dimmed and locked states of notification rows.
      */
     protected void updateRowStates() {
-        int maxKeyguardNotifications = getMaxKeyguardNotifications();
         mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
 
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
@@ -1998,6 +1990,10 @@
 
         int visibleNotifications = 0;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+        int maxNotifications = 0;
+        if (onKeyguard) {
+            maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
+        }
         for (int i = 0; i < N; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
             if (onKeyguard) {
@@ -2013,7 +2009,7 @@
                     == View.VISIBLE;
             boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
             if ((isLockscreenPublicMode() && !mShowLockscreenNotifications) ||
-                    (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
+                    (onKeyguard && (visibleNotifications >= maxNotifications
                             && !childWithVisibleSummary
                             || !showOnKeyguard))) {
                 entry.row.setVisibility(View.GONE);
@@ -2188,8 +2184,7 @@
         // update the contentIntent
         mNotificationClicker.register(entry.row, sbn);
 
-        entry.row.setEntry(entry);
-        entry.row.notifyContentUpdated();
+        entry.row.updateStatusBarNotification(entry.notification);
         entry.row.resetHeight();
 
         applyRemoteInput(entry);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index 14051916..8570198 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -120,12 +120,12 @@
                 if (mLastHeight > mMinDragDistance) {
                     if (!mDraggedFarEnough) {
                         mDraggedFarEnough = true;
-                        mDragDownCallback.onThresholdReached();
+                        mDragDownCallback.onCrossedThreshold(true);
                     }
                 } else {
                     if (mDraggedFarEnough) {
                         mDraggedFarEnough = false;
-                        mDragDownCallback.onDragDownReset();
+                        mDragDownCallback.onCrossedThreshold(false);
                     }
                 }
                 return true;
@@ -236,7 +236,12 @@
          */
         boolean onDraggedDown(View startingChild, int dragLengthY);
         void onDragDownReset();
-        void onThresholdReached();
+
+        /**
+         * The user has dragged either above or below the threshold
+         * @param above whether he dragged above it
+         */
+        void onCrossedThreshold(boolean above);
         void onTouchSlopExceeded();
         void setEmptyDragAmount(float amount);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 3603900..81e20aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -25,16 +25,17 @@
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.LinearInterpolator;
 import android.widget.Chronometer;
 import android.widget.ImageView;
+import android.widget.RemoteViews;
 
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.statusbar.notification.NotificationHeaderView;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.stack.StackScrollState;
@@ -89,22 +90,31 @@
     private boolean mIsHeadsUp;
     private boolean mLastChronometerRunning = true;
     private NotificationHeaderView mNotificationHeader;
-    private ViewStub mNotificationHeaderStub;
     private ViewStub mChildrenContainerStub;
     private NotificationGroupManager mGroupManager;
     private boolean mChildrenExpanded;
     private boolean mIsSummaryWithChildren;
     private NotificationChildrenContainer mChildrenContainer;
     private ViewStub mGutsStub;
-    private boolean mHasNotificationHeader;
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
     private FalsingManager mFalsingManager;
 
     private boolean mJustClicked;
-    private NotificationData.Entry mEntry;
+    private boolean mIconAnimationRunning;
     private boolean mShowNoBackground;
     private ExpandableNotificationRow mNotificationParent;
+    private OnClickListener mExpandClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
+                mGroupManager.toggleGroupExpansion(mStatusBarNotification);
+            } else {
+                setUserExpanded(!isExpanded());
+                notifyHeightChanged(true);
+            }
+        }
+    };
 
     public NotificationContentView getPrivateLayout() {
         return mPrivateLayout;
@@ -117,6 +127,16 @@
     public void setIconAnimationRunning(boolean running) {
         setIconAnimationRunning(running, mPublicLayout);
         setIconAnimationRunning(running, mPrivateLayout);
+        setIconAnimationRunningForChild(running, mNotificationHeader);
+        if (mIsSummaryWithChildren) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            for (int i = 0; i < notificationChildren.size(); i++) {
+                ExpandableNotificationRow child = notificationChildren.get(i);
+                child.setIconAnimationRunning(running);
+            }
+        }
+        mIconAnimationRunning = running;
     }
 
     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
@@ -161,10 +181,17 @@
         }
     }
 
-    private void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+    public void updateStatusBarNotification(StatusBarNotification statusBarNotification) {
         mStatusBarNotification = statusBarNotification;
-        mPrivateLayout.setStatusBarNotification(statusBarNotification);
+        mPrivateLayout.onNotificationUpdated(statusBarNotification);
+        mPublicLayout.onNotificationUpdated(statusBarNotification);
         updateVetoButton();
+        if (mIsSummaryWithChildren) {
+            recreateNotificationHeader();
+        }
+        if (mIconAnimationRunning) {
+            setIconAnimationRunning(true);
+        }
         onChildrenCountChanged();
     }
 
@@ -381,11 +408,6 @@
         }
     }
 
-    public void setEntry(NotificationData.Entry entry) {
-        mEntry = entry;
-        setStatusBarNotification(entry.notification);
-    }
-
     public CharSequence getSubText() {
         Notification notification = mStatusBarNotification.getNotification();
         CharSequence subText = notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT);
@@ -452,6 +474,8 @@
         super.onFinishInflate();
         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
+        mPrivateLayout.setExpandClickListener(mExpandClickListener);
+        mPublicLayout.setExpandClickListener(mExpandClickListener);
         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
             @Override
@@ -462,15 +486,6 @@
                 mGutsStub = null;
             }
         });
-        mNotificationHeaderStub = (ViewStub) findViewById(R.id.notification_header_stub);
-        mNotificationHeaderStub.setOnInflateListener(new ViewStub.OnInflateListener() {
-            @Override
-            public void onInflate(ViewStub stub, View inflated) {
-                mNotificationHeader = (NotificationHeaderView) inflated;
-                mNotificationHeader.setGroupManager(mGroupManager);
-                mNotificationHeader.bind(mEntry);
-            }
-        });
         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
 
@@ -494,6 +509,7 @@
             return;
         }
         mChildrenContainer.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
+        mNotificationHeader.setVisibility(mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
         mPrivateLayout.setVisibility(!mIsSummaryWithChildren ? VISIBLE : INVISIBLE);
     }
 
@@ -523,6 +539,8 @@
     public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
         mRowMinHeight = rowMinHeight;
         mMaxViewHeight = rowMaxHeight;
+        mPrivateLayout.setSmallHeight(mRowMinHeight);
+        mPublicLayout.setSmallHeight(mRowMinHeight);
     }
 
     public boolean isExpandable() {
@@ -534,6 +552,7 @@
 
     public void setExpandable(boolean expandable) {
         mExpandable = expandable;
+        mPrivateLayout.updateExpandButtons(isExpandable());
     }
 
     /**
@@ -654,8 +673,7 @@
         if (mSensitive && mHideSensitiveForIntrinsicHeight) {
             return mRowMinHeight;
         } else if (mIsSummaryWithChildren && !mOnKeyguard) {
-            return mChildrenContainer.getIntrinsicHeight()
-                    + mNotificationHeader.getHeight();
+            return mChildrenContainer.getIntrinsicHeight();
         } else if (mIsHeadsUp) {
             if (inExpansionState) {
                 return Math.max(mMaxExpandHeight, mHeadsUpHeight);
@@ -681,12 +699,18 @@
     }
 
     private void onChildrenCountChanged() {
-        mIsSummaryWithChildren  = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
+        mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
                 && mGroupManager.hasGroupChildren(mStatusBarNotification);
-        if (mIsSummaryWithChildren && mChildrenContainer == null) {
-            mChildrenContainerStub.inflate();
+        if (mIsSummaryWithChildren) {
+            if (mChildrenContainer == null) {
+                mChildrenContainerStub.inflate();
+            }
+            if (mNotificationHeader == null) {
+                recreateNotificationHeader();
+            }
         }
-        updateNotificationHeader();
+        mPrivateLayout.updateExpandButtons(isExpandable());
+        updateHeaderChildCount();
         updateChildrenVisibility(true);
     }
 
@@ -771,6 +795,7 @@
             animateShowingPublic(delay, duration);
         }
 
+        mPrivateLayout.updateExpandButtons(isExpandable());
         updateVetoButton();
         mShowingPublicInitialized = true;
     }
@@ -811,23 +836,11 @@
         }
     }
 
-    public void updateNotificationHeader() {
-        boolean hasHeader = hasNotificationHeader();
-        if (hasHeader != mHasNotificationHeader) {
-            if (hasHeader) {
-                if (mNotificationHeader == null) {
-                    mNotificationHeaderStub.inflate();
-                }
-                mNotificationHeader.setVisibility(View.VISIBLE);
-            } else if (mNotificationHeader != null) {
-                mNotificationHeader.setVisibility(View.GONE);
-            }
-            notifyHeightChanged(true  /* needsAnimation */);
+    public void updateHeaderChildCount() {
+        if (mIsSummaryWithChildren) {
+            mNotificationHeader.setChildCount(
+                    mChildrenContainer.getNotificationChildren().size());
         }
-        if (hasHeader) {
-            mNotificationHeader.bind(mEntry);
-        }
-        mHasNotificationHeader = hasHeader;
     }
 
     public static void applyTint(View v, int color) {
@@ -876,8 +889,7 @@
     @Override
     public int getMaxContentHeight() {
         if (mIsSummaryWithChildren && !mShowingPublic) {
-            return mChildrenContainer.getMaxContentHeight()
-                    + mNotificationHeader.getHeight();
+            return mChildrenContainer.getMaxContentHeight();
         }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMaxHeight();
@@ -885,15 +897,19 @@
 
     @Override
     public int getMinHeight() {
-        if (mIsSummaryWithChildren && !mOnKeyguard) {
-            return mChildrenContainer.getMinHeight()
-                    + mNotificationHeader.getHeight();
-        }
         NotificationContentView showingLayout = getShowingLayout();
         return showingLayout.getMinHeight();
     }
 
     @Override
+    public int getMinExpandHeight() {
+        if (mIsSummaryWithChildren && !mOnKeyguard) {
+            return mChildrenContainer.getMinHeight();
+        }
+        return getMinHeight();
+    }
+
+    @Override
     protected boolean shouldLimitViewHeight() {
         return !mIsSummaryWithChildren;
     }
@@ -908,9 +924,21 @@
         }
     }
 
-    public void notifyContentUpdated() {
-        mPublicLayout.notifyContentUpdated();
-        mPrivateLayout.notifyContentUpdated();
+    private void recreateNotificationHeader() {
+        final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
+                getStatusBarNotification().getNotification());
+        final RemoteViews header = builder.makeNotificationHeader();
+        if (mNotificationHeader == null) {
+            mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
+            final View expandButton = mNotificationHeader.findViewById(
+                    com.android.internal.R.id.expand_button);
+            expandButton.setVisibility(VISIBLE);
+            mNotificationHeader.setOnClickListener(mExpandClickListener);
+            addView(mNotificationHeader);
+        } else {
+            header.reapply(getContext(), mNotificationHeader);
+        }
+        updateHeaderChildCount();
     }
 
     public boolean isMaxExpandHeightInitialized() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index af59ac7..51602e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -195,6 +195,15 @@
     }
 
     /**
+     * @return The minimum height this child chan be expanded to. Note that this might be different
+     * than {@link #getMinHeight()} because some elements can't be collapsed by an expand gesture
+     * to it's absolute minimal height
+     */
+    public int getMinExpandHeight() {
+        return getHeight();
+    }
+
+    /**
      * Sets the notification as dimmed. The default implementation does nothing.
      *
      * @param dimmed Whether the notification should be dimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
deleted file mode 100644
index 91e5404..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigMediaNarrowViewWrapper.java
+++ /dev/null
@@ -1,36 +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
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a big media narrow notification template layout.
- */
-public class NotificationBigMediaNarrowViewWrapper extends NotificationMediaViewWrapper {
-
-    protected NotificationBigMediaNarrowViewWrapper(Context ctx,
-            View view) {
-        super(ctx, view);
-    }
-
-    @Override
-    public boolean needsRoundRectClipping() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
deleted file mode 100644
index ffe0cd1..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBigPictureViewWrapper.java
+++ /dev/null
@@ -1,35 +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
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a notification view inflated from a big picture style template.
- */
-public class NotificationBigPictureViewWrapper extends NotificationTemplateViewWrapper {
-
-    protected NotificationBigPictureViewWrapper(Context ctx, View view) {
-        super(ctx, view);
-    }
-
-    @Override
-    public boolean needsRoundRectClipping() {
-        return true;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 5aedaf1..bca6491 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
@@ -52,7 +53,6 @@
 
     private final Rect mClipBounds = new Rect();
     private final int mSingleLineHeight;
-    private final int mSmallHeight;
     private final int mHeadsUpHeight;
     private final int mRoundRectRadius;
     private final Interpolator mLinearInterpolator = new LinearInterpolator();
@@ -77,7 +77,9 @@
     private boolean mIsHeadsUp;
     private boolean mShowingLegacyBackground;
     private boolean mIsChildInGroup;
+    private int mSmallHeight;
     private StatusBarNotification mStatusBarNotification;
+    private NotificationGroupManager mGroupManager;
 
     private final ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
             = new ViewTreeObserver.OnPreDrawListener() {
@@ -96,7 +98,7 @@
                     mRoundRectRadius);
         }
     };
-    private NotificationGroupManager mGroupManager;
+    private OnClickListener mExpandClickListener;
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -104,7 +106,6 @@
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
         mSingleLineHeight = getResources().getDimensionPixelSize(
                 R.dimen.notification_single_line_height);
-        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         mHeadsUpHeight = getResources().getDimensionPixelSize(R.dimen.notification_mid_height);
         mRoundRectRadius = getResources().getDimensionPixelSize(
                 R.dimen.notification_material_rounded_rect_radius);
@@ -114,6 +115,10 @@
         setOutlineProvider(mOutlineProvider);
     }
 
+    public void setSmallHeight(int smallHeight) {
+        mSmallHeight = smallHeight;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
@@ -443,19 +448,6 @@
         }
     }
 
-    public void notifyContentUpdated() {
-        updateSingleLineView();
-        selectLayout(false /* animate */, true /* force */);
-        if (mContractedChild != null) {
-            mContractedWrapper.notifyContentUpdated();
-            mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
-        }
-        if (mExpandedChild != null) {
-            mExpandedWrapper.notifyContentUpdated();
-        }
-        updateRoundRectClipping();
-    }
-
     public boolean isContentExpandable() {
         return mExpandedChild != null;
     }
@@ -488,9 +480,21 @@
         updateSingleLineView();
     }
 
-    public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
+    public void onNotificationUpdated(StatusBarNotification statusBarNotification) {
         mStatusBarNotification = statusBarNotification;
         updateSingleLineView();
+        selectLayout(false /* animate */, true /* force */);
+        if (mContractedChild != null) {
+            mContractedWrapper.notifyContentUpdated();
+            mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        }
+        if (mExpandedChild != null) {
+            mExpandedWrapper.notifyContentUpdated();
+        }
+        if (mHeadsUpChild != null) {
+            mHeadsUpWrapper.notifyContentUpdated();
+        }
+        updateRoundRectClipping();
     }
 
     private void updateSingleLineView() {
@@ -515,4 +519,20 @@
     public void setGroupManager(NotificationGroupManager groupManager) {
         mGroupManager = groupManager;
     }
+
+    public void setExpandClickListener(OnClickListener expandClickListener) {
+        mExpandClickListener = expandClickListener;
+    }
+
+    public void updateExpandButtons(boolean expandable) {
+        if (mExpandedChild != null) {
+            mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+        }
+        if (mContractedChild != null) {
+            mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+        }
+        if (mHeadsUpChild != null) {
+            mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
deleted file mode 100644
index 953c373..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaViewWrapper.java
+++ /dev/null
@@ -1,37 +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.systemui.statusbar;
-
-import android.content.Context;
-import android.view.View;
-
-/**
- * Wraps a media notification.
- */
-public class NotificationMediaViewWrapper extends NotificationTemplateViewWrapper {
-
-    protected NotificationMediaViewWrapper(Context ctx, View view) {
-        super(ctx, view);
-    }
-
-    @Override
-    public void setDark(boolean dark, boolean fade, long delay) {
-
-        // Only update the large icon, because the rest is already inverted.
-        setPictureGrayscale(dark, fade, delay);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
index af6ccd8..f20ccd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationTemplateViewWrapper.java
@@ -20,24 +20,32 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.ImageView;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 
 import com.android.systemui.R;
 import com.android.systemui.ViewInvertHelper;
 import com.android.systemui.statusbar.phone.NotificationPanelView;
 
+import java.util.ArrayList;
+
 /**
  * Wraps a notification view inflated from a template.
  */
@@ -47,69 +55,64 @@
     private final PorterDuffColorFilter mIconColorFilter = new PorterDuffColorFilter(
             0, PorterDuff.Mode.SRC_ATOP);
     private final int mIconDarkAlpha;
-    private final int mIconBackgroundDarkColor;
+    private final int mIconDarkColor = 0xffffffff;
+    private final int mDarkProgressTint = 0xffffffff;
     private final Interpolator mLinearOutSlowInInterpolator;
 
-    private int mIconBackgroundColor;
+    private int mColor;
     private ViewInvertHelper mInvertHelper;
     private ImageView mIcon;
     protected ImageView mPicture;
 
-    /** Whether the icon needs to be forced grayscale when in dark mode. */
-    private boolean mIconForceGraysaleWhenDark;
     private TextView mSubText;
-    private TextView mInfoText;
-    private View mProfileBadge;
-    private View mThirdLineDivider;
-    private View mThirdLine;
+    private View mSubTextDivider;
+    private ImageView mExpandButton;
+    private ViewGroup mNotificationHeader;
+    private ProgressBar mProgressBar;
 
     protected NotificationTemplateViewWrapper(Context ctx, View view) {
         super(view);
         mIconDarkAlpha = ctx.getResources().getInteger(R.integer.doze_small_icon_alpha);
-        mIconBackgroundDarkColor =
-                ctx.getColor(R.color.doze_small_icon_background_color);
         mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(ctx,
                 android.R.interpolator.linear_out_slow_in);
+
         resolveViews();
     }
 
     private void resolveViews() {
         View mainColumn = mView.findViewById(com.android.internal.R.id.notification_main_column);
-        mInvertHelper = mainColumn != null
-                ? new ViewInvertHelper(mainColumn, NotificationPanelView.DOZE_ANIMATION_DURATION)
-                : null;
-        ImageView largeIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
-        ImageView rightIcon = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
-        mIcon = resolveIcon(largeIcon, rightIcon);
-        mPicture = resolvePicture(largeIcon);
-        mIconBackgroundColor = resolveBackgroundColor(mIcon);
-        mSubText = (TextView) mView.findViewById(com.android.internal.R.id.text);
-        mInfoText = (TextView) mView.findViewById(com.android.internal.R.id.info);
-        mProfileBadge = mView.findViewById(com.android.internal.R.id.profile_badge_line3);
-        mThirdLineDivider = mView.findViewById(com.android.internal.R.id.overflow_divider);
-        mThirdLine = mView.findViewById(com.android.internal.R.id.line3);
-
-        // If the icon already has a color filter, we assume that we already forced the icon to be
-        // white when we created the notification.
-        final Drawable iconDrawable = mIcon != null ? mIcon.getDrawable() : null;
-        mIconForceGraysaleWhenDark = iconDrawable != null && iconDrawable.getColorFilter() != null;
+        mIcon = (ImageView) mView.findViewById(com.android.internal.R.id.icon);
+        mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+        mSubText = (TextView) mView.findViewById(com.android.internal.R.id.header_sub_text);
+        mSubTextDivider = mView.findViewById(com.android.internal.R.id.sub_text_divider);
+        mExpandButton = (ImageView) mView.findViewById(com.android.internal.R.id.expand_button);
+        mColor = resolveColor(mExpandButton);
+        final View progress = mView.findViewById(com.android.internal.R.id.progress);
+        if (progress instanceof ProgressBar) {
+            mProgressBar = (ProgressBar) progress;
+        } else {
+            // It's still a viewstub
+            mProgressBar = null;
+        }
+        mNotificationHeader = (ViewGroup) mView.findViewById(
+                com.android.internal.R.id.notification_header);
+        ArrayList<View> viewsToInvert = new ArrayList<>();
+        if (mainColumn != null) {
+            viewsToInvert.add(mainColumn);
+        }
+        for (int i = 0; i < mNotificationHeader.getChildCount(); i++) {
+            View child = mNotificationHeader.getChildAt(i);
+            if (child != mIcon) {
+                viewsToInvert.add(child);
+            }
+        }
+        mInvertHelper = new ViewInvertHelper(viewsToInvert,
+                NotificationPanelView.DOZE_ANIMATION_DURATION);
     }
 
-    private ImageView resolveIcon(ImageView largeIcon, ImageView rightIcon) {
-        return largeIcon != null && largeIcon.getBackground() != null ? largeIcon
-                : rightIcon != null && rightIcon.getVisibility() == View.VISIBLE ? rightIcon
-                : null;
-    }
-
-    private ImageView resolvePicture(ImageView largeIcon) {
-        return largeIcon != null && largeIcon.getBackground() == null
-                ? largeIcon
-                : null;
-    }
-
-    private int resolveBackgroundColor(ImageView icon) {
-        if (icon != null && icon.getBackground() != null) {
-            ColorFilter filter = icon.getBackground().getColorFilter();
+    private int resolveColor(ImageView icon) {
+        if (icon != null && icon.getDrawable() != null) {
+            ColorFilter filter = icon.getDrawable().getColorFilter();
             if (filter instanceof PorterDuffColorFilter) {
                 return ((PorterDuffColorFilter) filter).getColor();
             }
@@ -138,18 +141,43 @@
             if (fade) {
                 fadeIconColorFilter(mIcon, dark, delay);
                 fadeIconAlpha(mIcon, dark, delay);
-                if (!mIconForceGraysaleWhenDark) {
-                    fadeGrayscale(mIcon, dark, delay);
-                }
             } else {
                 updateIconColorFilter(mIcon, dark);
                 updateIconAlpha(mIcon, dark);
-                if (!mIconForceGraysaleWhenDark) {
-                    updateGrayscale(mIcon, dark);
-                }
             }
         }
         setPictureGrayscale(dark, fade, delay);
+        setProgressBarDark(dark, fade, delay);
+    }
+
+    private void setProgressBarDark(boolean dark, boolean fade, long delay) {
+        if (mProgressBar != null) {
+            if (fade) {
+                fadeProgressDark(mProgressBar, dark, delay);
+            } else {
+                updateProgressDark(mProgressBar, dark);
+            }
+        }
+    }
+
+    private void fadeProgressDark(final ProgressBar target, final boolean dark, long delay) {
+        startIntensityAnimation(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                updateProgressDark(target, t);
+            }
+        }, dark, delay, null /* listener */);
+    }
+
+    private void updateProgressDark(ProgressBar target, float intensity) {
+        int color = interpolateColor(mColor, mDarkProgressTint, intensity);
+        target.getIndeterminateDrawable().mutate().setTint(color);
+        target.getProgressDrawable().mutate().setTint(color);
+    }
+
+    private void updateProgressDark(ProgressBar target, boolean dark) {
+        updateProgressDark(target, dark ? 1f : 0f);
     }
 
     protected void setPictureGrayscale(boolean grayscale, boolean fade, long delay) {
@@ -218,14 +246,14 @@
     }
 
     private void updateIconColorFilter(ImageView target, float intensity) {
-        int color = interpolateColor(mIconBackgroundColor, mIconBackgroundDarkColor, intensity);
+        int color = interpolateColor(mColor, mIconDarkColor, intensity);
         mIconColorFilter.setColor(color);
-        Drawable background = target.getBackground();
+        Drawable iconDrawable = target.getDrawable();
 
-        // The background might be null for legacy notifications. Also, the notification might have
-        // been modified during the animation, so background might be null here.
-        if (background != null) {
-            background.mutate().setColorFilter(mIconColorFilter);
+        // Also, the notification might have been modified during the animation, so background
+        // might be null here.
+        if (iconDrawable != null) {
+            iconDrawable.mutate().setColorFilter(mIconColorFilter);
         }
     }
 
@@ -250,33 +278,17 @@
         boolean subTextAvailable = !TextUtils.isEmpty(mSubText.getText());
         if (visible && subTextAvailable) {
             mSubText.setVisibility(View.VISIBLE);
+            mSubTextDivider.setVisibility(View.VISIBLE);
         } else {
             mSubText.setVisibility(View.GONE);
+            mSubTextDivider.setVisibility(View.GONE);
         }
-        // TODO: figure out what to do with the number (same place as contentInfo)
-        // work profile badge. For now we hide it since it looks nicer.
-        boolean infoAvailable = !TextUtils.isEmpty(mInfoText.getText());
-        if (visible && infoAvailable) {
-            mInfoText.setVisibility(View.VISIBLE);
-        } else {
-            mInfoText.setVisibility(View.GONE);
-        }
-        boolean showThirdLine = (visible && (infoAvailable || subTextAvailable))
-                || mProfileBadge.getVisibility() == View.VISIBLE;
-        if (mThirdLineDivider != null) {
-            if (showThirdLine) {
-                mThirdLineDivider.setVisibility(View.VISIBLE);
-            } else {
-                mThirdLineDivider.setVisibility(View.GONE);
-            }
-        }
-        if (mThirdLine != null) {
-            if (showThirdLine) {
-                mThirdLine.setVisibility(View.VISIBLE);
-            } else {
-                mThirdLine.setVisibility(View.GONE);
-            }
-        }
+    }
+
+    @Override
+    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+        mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+        mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
     }
 
     private void updateGrayscaleMatrix(float intensity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
index 9bce548..e83ecb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewWrapper.java
@@ -34,15 +34,7 @@
 
     public static NotificationViewWrapper wrap(Context ctx, View v) {
         if (v.getId() == com.android.internal.R.id.status_bar_latest_event_content) {
-            if (TAG_BIG_MEDIA_NARROW.equals(v.getTag())) {
-                return new NotificationBigMediaNarrowViewWrapper(ctx, v);
-            } else if (TAG_MEDIA.equals(v.getTag())) {
-                return new NotificationMediaViewWrapper(ctx, v);
-            } else if (TAG_BIG_PICTURE.equals(v.getTag())) {
-                return new NotificationBigMediaNarrowViewWrapper(ctx, v);
-            } else {
-                return new NotificationTemplateViewWrapper(ctx, v);
-            }
+            return new NotificationTemplateViewWrapper(ctx, v);
         } else {
             return new NotificationCustomViewWrapper(v);
         }
@@ -83,4 +75,12 @@
     public void setSubTextVisible(boolean visible) {
         mSubTextVisible = visible;
     }
+
+    /**
+     * Update the appearance of the expand button.
+     *
+     * @param expandable should this view be expandable
+     * @param onClickListener the listener to invoke when the expand affordance is clicked on
+     */
+    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
deleted file mode 100644
index ec26cc4..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderView.java
+++ /dev/null
@@ -1,209 +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
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.annotation.Nullable;
-import android.app.Notification;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.service.notification.StatusBarNotification;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
-import com.android.systemui.statusbar.NotificationData;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.List;
-
-/**
- * A header for a notification view
- */
-public class NotificationHeaderView extends FrameLayout {
-
-    private static final int DEFAULT_ICON_TINT_COLOR = 0xff616161;
-    private final NotificationColorUtil mNotificationColorUtil;
-    private NotificationData.Entry mNotificationEntry;
-    private ImageView mIconView;
-    private TextView mAppName;
-    private TextView mPostTime;
-    private TextView mChildCount;
-    private TextView mSubTextDivider;
-    private TextView mSubText;
-    private NotificationGroupManager mGroupManager;
-    private ImageButton mExpandButton;
-
-    public NotificationHeaderView(Context context) {
-        this(context, null);
-    }
-
-    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public NotificationHeaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        mNotificationColorUtil = NotificationColorUtil.getInstance(context);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIconView = (ImageView) findViewById(R.id.header_notification_icon);
-        mAppName = (TextView) findViewById(R.id.app_name_text);
-        mSubTextDivider = (TextView) findViewById(R.id.app_title_sub_text_divider);
-        mSubText = (TextView) findViewById(R.id.title_sub_text);
-        mPostTime = (TextView) findViewById(R.id.post_time);
-        mChildCount = (TextView) findViewById(R.id.number_of_children);
-        mExpandButton = (ImageButton) findViewById(R.id.notification_expand_button);
-        mExpandButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mGroupManager.toggleGroupExpansion(mNotificationEntry.notification);
-            }
-        });
-    }
-
-    public void bind(NotificationData.Entry notificationEntry) {
-        mNotificationEntry = notificationEntry;
-        StatusBarNotification sbn = notificationEntry.notification;
-        int notificationColor = getNotificationColor(sbn);
-        bindIcon(notificationColor);
-        bindNumber(notificationColor);
-        bindAppName(sbn);
-        bindSubText();
-        bindTime(sbn);
-        bindExpandButton(sbn);
-    }
-
-    private void bindExpandButton(StatusBarNotification sbn) {
-        boolean summaryOfGroup = mGroupManager.isSummaryOfGroup(sbn);
-        mExpandButton.setVisibility(summaryOfGroup ? VISIBLE : GONE);
-    }
-
-    private void bindSubText() {
-        List<ExpandableNotificationRow> notificationChildren =
-                mNotificationEntry.row.getNotificationChildren();
-        CharSequence subText = null;
-        if (notificationChildren != null) {
-            for (int i = 0; i < notificationChildren.size(); i++) {
-                ExpandableNotificationRow row = notificationChildren.get(i);
-                CharSequence rowSubText = row.getSubText();
-                if (TextUtils.isEmpty(rowSubText)
-                        || (subText != null && !subText.equals(rowSubText))) {
-                    // The children don't have a common subText
-                    subText = null;
-                    break;
-                } else if (subText == null) {
-                    subText = rowSubText;
-                }
-            }
-        };
-        setSubText(subText);
-    }
-
-    private void setSubText(CharSequence subText) {
-        boolean goneInHeader = TextUtils.isEmpty(subText);
-        if (goneInHeader) {
-            mSubText.setVisibility(GONE);
-            mSubTextDivider.setVisibility(GONE);
-        } else {
-            mSubText.setVisibility(VISIBLE);
-            mSubText.setText(subText);
-            mSubTextDivider.setVisibility(VISIBLE);
-        }
-        List<ExpandableNotificationRow> notificationChildren =
-                mNotificationEntry.row.getNotificationChildren();
-        if (notificationChildren != null) {
-            for (int i = 0; i < notificationChildren.size(); i++) {
-                ExpandableNotificationRow row = notificationChildren.get(i);
-                row.setContentSubTextVisible(goneInHeader);
-            }
-        }
-    }
-
-    private int getNotificationColor(StatusBarNotification sbn) {
-        int color = sbn.getNotification().color;
-        if (color == Notification.COLOR_DEFAULT) {
-            return DEFAULT_ICON_TINT_COLOR;
-        }
-        return color;
-    }
-
-    private void bindNumber(int notificationColor) {
-        int numberOfNotificationChildren = mNotificationEntry.row.getNumberOfNotificationChildren();
-        boolean visible = numberOfNotificationChildren > 0;
-        if (visible) {
-            mChildCount.setText("(" + numberOfNotificationChildren + ")");
-            mChildCount.setTextColor(notificationColor);
-            mChildCount.setVisibility(VISIBLE);
-        } else {
-            mChildCount.setVisibility(GONE);
-        }
-    }
-
-    private void bindTime(StatusBarNotification sbn) {
-
-    }
-
-    private void bindIcon(int notificationColor) {
-        Drawable icon = mNotificationEntry.icon.getDrawable().getConstantState()
-                .newDrawable(getResources()).mutate();
-        mIconView.setImageDrawable(icon);
-        if (NotificationUtils.isGrayscale(mIconView, mNotificationColorUtil)) {
-            icon.setTint(notificationColor);
-        }
-    }
-
-    private void bindAppName(StatusBarNotification sbn) {
-        PackageManager pmUser = BaseStatusBar.getPackageManagerForUser(getContext(),
-                sbn.getUser().getIdentifier());
-        final String pkg = sbn.getPackageName();
-        String appname = pkg;
-        try {
-            final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
-                    PackageManager.GET_UNINSTALLED_PACKAGES
-                            | PackageManager.GET_DISABLED_COMPONENTS);
-            if (info != null) {
-                appname = String.valueOf(pmUser.getApplicationLabel(info));
-
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // app is gone, just show package name
-        }
-        mAppName.setText(appname);
-    }
-
-    public void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a15d35e..784cb48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -96,6 +96,11 @@
         mEmptyDragAmount = emptyDragAmount;
     }
 
+    public float getMinStackScrollerPadding(int height, int keyguardStatusHeight) {
+        return mClockYFractionMin * height + keyguardStatusHeight / 2
+                + mClockNotificationsMarginMin;
+    }
+
     public void run(Result result) {
         int y = getClockY() - mKeyguardStatusHeight / 2;
         float clockAdjustment = getClockYExpansionAdjustment();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index fbe9730..08da0d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -92,8 +92,6 @@
         if (group.children.isEmpty()) {
             if (group.summary == null) {
                 mGroupMap.remove(groupKey);
-            } else if (!group.expanded) {
-                group.summary.row.updateNotificationHeader();
             }
         }
     }
@@ -109,9 +107,6 @@
         }
         if (notif.isGroupChild()) {
             group.children.add(added);
-            if (group.summary != null && group.children.size() == 1 && !group.expanded) {
-                group.summary.row.updateNotificationHeader();
-            }
         } else {
             group.summary = added;
             group.expanded = added.row.areChildrenExpanded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 79701ed..c0e3ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -44,6 +44,7 @@
 import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 import android.widget.TextView;
+
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.systemui.DejankUtils;
@@ -448,6 +449,36 @@
         requestScrollerTopPaddingUpdate(animate);
     }
 
+    /**
+     * @param maximum the maximum to return at most
+     * @return the maximum keyguard notifications that can fit on the screen
+     */
+    public int computeMaxKeyguardNotifications(int maximum) {
+        float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding(getHeight(),
+                mKeyguardStatusView.getHeight());
+        int keyguardPadding = getResources().getDimensionPixelSize(
+                R.dimen.notification_padding_dimmed);
+        final int overflowheight = getResources().getDimensionPixelSize(
+                R.dimen.notification_summary_height);
+        float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+        float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
+                - bottomStackSize;
+        int count = 0;
+        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            availableSpace -= child.getMinHeight() + keyguardPadding;
+            if (availableSpace >= 0 && count < maximum) {
+                count++;
+            } else {
+                return count;
+            }
+        }
+        return count;
+    }
+
     private void startClockAnimation(int y) {
         if (mClockAnimationTarget == y) {
             return;
@@ -1663,7 +1694,7 @@
     }
 
     private float getFadeoutAlpha() {
-        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getItemHeight())
+        float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight())
                 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
                 - mNotificationStackScroller.getCollapseSecondCardPadding());
         alpha = Math.max(0, Math.min(alpha, 1));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index fafedc3..21d803d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -36,9 +36,7 @@
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
-import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingManager;
-import com.android.systemui.classifier.HumanInteractionClassifier;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
@@ -891,6 +889,7 @@
                         if (mStatusBar.getStatusBarWindow().getHeight()
                                 != mStatusBar.getStatusBarHeight()) {
                             getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            mStatusBar.onPanelExpandedAndLayouted();
                             if (animate) {
                                 mBar.startOpeningPanel(PanelView.this);
                                 notifyExpandingStarted();
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 6d8e650..05660ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -110,8 +110,8 @@
 import com.android.systemui.EventLogTags;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
-import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.assist.AssistManager;
+import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -171,7 +171,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeSet;
@@ -338,7 +337,7 @@
     private long mKeyguardFadingAwayDelay;
     private long mKeyguardFadingAwayDuration;
 
-    int mKeyguardMaxNotificationCount;
+    int mMaxAllowedKeyguardNotifications;
 
     boolean mExpandedVisible;
 
@@ -428,6 +427,7 @@
     private boolean mAutohideSuspended;
     private int mStatusBarMode;
     private int mNavigationBarMode;
+    private int mMaxKeyguardNotifications;
 
     private ViewMediatorCallback mKeyguardViewMediatorCallback;
     private ScrimController mScrimController;
@@ -3129,10 +3129,11 @@
         mNaturalBarHeight = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_height);
 
+        mRowMinHeightLegacy =  res.getDimensionPixelSize(R.dimen.notification_min_height_legacy);
         mRowMinHeight =  res.getDimensionPixelSize(R.dimen.notification_min_height);
         mRowMaxHeight =  res.getDimensionPixelSize(R.dimen.notification_max_height);
 
-        mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count);
+        mMaxAllowedKeyguardNotifications = res.getInteger(R.integer.keyguard_max_notification_count);
 
         if (DEBUG) Log.v(TAG, "updateResources");
     }
@@ -3913,8 +3914,18 @@
     }
 
     @Override
-    protected int getMaxKeyguardNotifications() {
-        return mKeyguardMaxNotificationCount;
+    protected int getMaxKeyguardNotifications(boolean recompute) {
+        if (recompute) {
+            mMaxKeyguardNotifications = Math.max(1,
+                    mNotificationPanel.computeMaxKeyguardNotifications(
+                            mMaxAllowedKeyguardNotifications));
+            return mMaxKeyguardNotifications;
+        }
+        return mMaxKeyguardNotifications;
+    }
+
+    public int getMaxKeyguardNotifications() {
+        return getMaxKeyguardNotifications(false /* recompute */);
     }
 
     public NavigationBarView getNavigationBarView() {
@@ -3944,11 +3955,12 @@
     @Override
     public void onDragDownReset() {
         mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
+        mStackScroller.resetScrollPosition();
     }
 
     @Override
-    public void onThresholdReached() {
-        mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
+    public void onCrossedThreshold(boolean above) {
+        mStackScroller.setDimmed(!above /* dimmed */, true /* animate */);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 93a8fd8..0917528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -110,7 +110,7 @@
         // When enabling location, a user consent dialog will pop up, and the
         // setting won't be fully enabled until the user accepts the agreement.
         int mode = enabled
-                ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
+                ? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
         // QuickSettings always runs as the owner, so specifically set the settings
         // for the current foreground user.
         return Settings.Secure
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index c9ebc84..77a9871 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -78,7 +78,7 @@
         mNotificationAppearDistance = getResources().getDimensionPixelSize(
                 R.dimen.notification_appear_distance);
         mNotificationHeaderHeight = getResources().getDimensionPixelSize(
-                R.dimen.notification_header_height);
+                com.android.internal.R.dimen.notification_header_height);
         mHeaderTopPaddingSubstraction = 2 * getResources().getDisplayMetrics().density;
         mCollapsedBottompadding = 10 * getResources().getDisplayMetrics().density;
         mHybridViewManager = new HybridNotificationViewManager(getContext(), this);
@@ -246,7 +246,7 @@
      *         in @param maxAllowedVisibleChildren
      */
     private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
-        int intrinsicHeight = 0;
+        int intrinsicHeight = mNotificationHeaderHeight;;
         int visibleChildren = 0;
         int childCount = mChildren.size();
         for (int i = 0; i < childCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index aeca97c..185d32d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -272,7 +272,7 @@
     @Override
     protected void onDraw(Canvas canvas) {
         if (DEBUG) {
-            int y = mCollapsedSize;
+            int y = mTopPadding;
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
             y = (int) (getLayoutHeight() - mBottomStackPeekSize
                     - mBottomStackSlowDownHeight);
@@ -550,8 +550,9 @@
         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
     }
 
-    public int getItemHeight() {
-        return mCollapsedSize;
+    public int getFirstItemMinHeight() {
+        final ExpandableView firstChild = getFirstChildNotGone();
+        return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
     }
 
     public int getBottomStackPeekSize() {
@@ -1321,14 +1322,14 @@
         ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
         if (firstChild != null) {
             int contentHeight = getContentHeight();
-            int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
             scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
                     + mBottomStackSlowDownHeight);
             if (scrollRange > 0) {
-                View lastChild = getLastChildNotGone();
+                int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
                 // We want to at least be able collapse the first item and not ending in a weird
                 // end state.
-                scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
+                scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
+                        - firstChild.getMinHeight());
             }
         }
         return scrollRange;
@@ -1337,12 +1338,12 @@
     /**
      * @return the first child which has visibility unequal to GONE
      */
-    private View getFirstChildNotGone() {
+    private ExpandableView getFirstChildNotGone() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child.getVisibility() != View.GONE) {
-                return child;
+                return (ExpandableView) child;
             }
         }
         return null;
@@ -1504,7 +1505,10 @@
     }
 
     public int getMinStackHeight() {
-        return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
+        final ExpandableView firstChild = getFirstChildNotGone();
+        final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+                : mCollapsedSize;
+        return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
     }
 
     public float getTopPaddingOverflow() {
@@ -1512,7 +1516,10 @@
     }
 
     public int getPeekHeight() {
-        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
+        final ExpandableView firstChild = getFirstChildNotGone();
+        final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
+                : mCollapsedSize;
+        return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
                 + mCollapseSecondCardPadding;
     }
 
@@ -2266,6 +2273,11 @@
         return Math.max(emptyMargin, 0);
     }
 
+    public float getKeyguardBottomStackSize() {
+        return mBottomStackPeekSize + getResources().getDimensionPixelSize(
+                R.dimen.bottom_stack_slow_down_length);
+    }
+
     public void onExpansionStarted() {
         mIsExpansionChanging = true;
         mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 65ca95b..953f287 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -42,7 +42,7 @@
     private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
     private static final int MAX_ITEMS_IN_TOP_STACK = 3;
 
-    public static final float DIMMED_SCALE = 0.95f;
+    public static final float DIMMED_SCALE = 0.98f;
 
     private int mPaddingBetweenElements;
     private int mCollapsedSize;
@@ -72,6 +72,7 @@
     private int mMaxNotificationHeight;
     private boolean mScaleDimmed;
     private HeadsUpManager mHeadsUpManager;
+    private int mFirstChildMinHeight;
 
     public StackScrollAlgorithm(Context context) {
         initConstants(context);
@@ -155,7 +156,7 @@
         // Due to the overScroller, the stackscroller can have negative scroll state. This is
         // already accounted for by the top padding and doesn't need an additional adaption
         scrollY = Math.max(0, scrollY);
-        algorithmState.scrollY = (int) (scrollY + mCollapsedSize + bottomOverScroll);
+        algorithmState.scrollY = (int) (scrollY + mFirstChildMinHeight + bottomOverScroll);
 
         updateVisibleChildren(resultState, algorithmState);
 
@@ -424,7 +425,8 @@
             float yPositionInScrollViewAfterElement = yPositionInScrollView
                     + childHeight
                     + mPaddingBetweenElements;
-            float scrollOffset = yPositionInScrollView - algorithmState.scrollY + mCollapsedSize;
+            float scrollOffset = yPositionInScrollView - algorithmState.scrollY +
+                    mFirstChildMinHeight;
 
             if (i == algorithmState.lastTopStackIndex + 1) {
                 // Normally the position of this child is the position in the regular scrollview,
@@ -451,10 +453,10 @@
                         >= bottomStackStart && !mIsExpansionChanging && i != 0 && mIsSmallScreen) {
                     // we just collapse this element slightly
                     int newSize = (int) Math.max(bottomStackStart - mPaddingBetweenElements -
-                            childViewState.yTranslation, mCollapsedSize);
+                            childViewState.yTranslation, child.getMinHeight());
                     childViewState.height = newSize;
                     updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
-                            bottomPeekStart, childViewState.yTranslation, childViewState,
+                            child, childViewState.yTranslation, childViewState,
                             childHeight);
                 }
                 clampPositionToBottomStackStart(childViewState, childViewState.height,
@@ -471,7 +473,7 @@
                     // According to the regular scroll view we are currently translating out of /
                     // into the bottom of the screen
                     updateStateForChildTransitioningInBottom(algorithmState,
-                            bottomStackStart, bottomPeekStart, currentYPosition,
+                            bottomStackStart, child, currentYPosition,
                             childViewState, childHeight);
                 }
             } else {
@@ -484,12 +486,13 @@
             // The first card is always rendered.
             if (i == 0) {
                 childViewState.alpha = 1.0f;
-                childViewState.yTranslation = Math.max(mCollapsedSize - algorithmState.scrollY, 0);
+                childViewState.yTranslation = Math.max(
+                        mFirstChildMinHeight - algorithmState.scrollY, 0);
                 if (childViewState.yTranslation + childViewState.height
                         > bottomPeekStart - mCollapseSecondCardPadding) {
                     childViewState.height = (int) Math.max(
                             bottomPeekStart - mCollapseSecondCardPadding
-                                    - childViewState.yTranslation, mCollapsedSize);
+                                    - childViewState.yTranslation, mFirstChildMinHeight);
                 }
                 childViewState.location = StackViewState.LOCATION_FIRST_CARD;
             }
@@ -501,7 +504,8 @@
 
             if (ambientState.isShadeExpanded() && topHeadsUpEntry != null
                     && child != topHeadsUpEntry) {
-                childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() - mCollapsedSize;
+                childViewState.yTranslation += topHeadsUpEntry.getHeadsUpHeight() -
+                        mFirstChildMinHeight;
             }
             childViewState.yTranslation += ambientState.getTopPadding()
                     + ambientState.getStackTranslation();
@@ -528,7 +532,7 @@
             boolean isTopEntry = topHeadsUpEntry == row;
             if (mIsExpanded) {
                 if (isTopEntry) {
-                    childState.height += row.getHeadsUpHeight() - mCollapsedSize;
+                    childState.height += row.getHeadsUpHeight() - mFirstChildMinHeight;
                 }
                 childState.height = Math.max(childState.height, row.getHeadsUpHeight());
                 // Ensure that the heads up is always visible even when scrolled off from the bottom
@@ -588,7 +592,7 @@
     private void clampPositionToTopStackEnd(StackViewState childViewState,
             int childHeight) {
         childViewState.yTranslation = Math.max(childViewState.yTranslation,
-                mCollapsedSize - childHeight);
+                mFirstChildMinHeight - childHeight);
     }
 
     private int getMaxAllowedChildHeight(View child, AmbientState ambientState) {
@@ -597,7 +601,7 @@
             if (ambientState == null && row.isHeadsUp()
                     || ambientState != null && ambientState.getTopHeadsUpEntry() == child) {
                 int extraSize = row.getIntrinsicHeight() - row.getHeadsUpHeight();
-                return mCollapsedSize + extraSize;
+                return mFirstChildMinHeight + extraSize;
             }
             return row.getIntrinsicHeight();
         } else if (child instanceof ExpandableView) {
@@ -608,7 +612,7 @@
     }
 
     private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
-            float transitioningPositionStart, float bottomPeakStart, float currentYPosition,
+            float transitioningPositionStart, ExpandableView child, float currentYPosition,
             StackViewState childViewState, int childHeight) {
 
         // This is the transitioning element on top of bottom stack, calculate how far we are in.
@@ -620,9 +624,10 @@
         float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
         algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
         int newHeight = childHeight;
-        if (childHeight > mCollapsedSize && mIsSmallScreen) {
+        if (childHeight > child.getMinHeight() && mIsSmallScreen) {
             newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
-                    mPaddingBetweenElements - currentYPosition, childHeight), mCollapsedSize);
+                    mPaddingBetweenElements - currentYPosition, childHeight),
+                    child.getMinHeight());
             childViewState.height = newHeight;
         }
         childViewState.yTranslation = transitioningPositionStart + offset - newHeight
@@ -689,7 +694,7 @@
                     numItemsBefore = algorithmState.itemsInTopStack - i;
                 }
                 // The end position of the current child
-                float currentChildEndY = mCollapsedSize + mTopStackTotalSize
+                float currentChildEndY = mFirstChildMinHeight + mTopStackTotalSize
                         - mTopStackIndentationFunctor.getValue(numItemsBefore);
                 childViewState.yTranslation = currentChildEndY - childHeight;
             }
@@ -701,7 +706,7 @@
                 // We are hidden behind the top card and faded out, so we can hide ourselves.
                 childViewState.alpha = 0.0f;
             }
-            childViewState.yTranslation = mCollapsedSize - childHeight;
+            childViewState.yTranslation = mFirstChildMinHeight - childHeight;
             childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
         }
 
@@ -730,7 +735,7 @@
                     + childHeight
                     + mPaddingBetweenElements;
             if (yPositionInScrollView < algorithmState.scrollY) {
-                if (i == 0 && algorithmState.scrollY <= mCollapsedSize) {
+                if (i == 0 && algorithmState.scrollY <= mFirstChildMinHeight) {
 
                     // The starting position of the bottom stack peek
                     int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
@@ -740,14 +745,14 @@
                             ? mFirstChildMaxHeight
                             : childHeight;
                     childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
-                            mCollapsedSize);
+                            mFirstChildMinHeight);
                     algorithmState.itemsInTopStack = 1.0f;
 
                 } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
                     // According to the regular scroll view we are fully off screen
                     algorithmState.itemsInTopStack += 1.0f;
                     if (i == 0) {
-                        childViewState.height = mCollapsedSize;
+                        childViewState.height = child.getMinHeight();
                     }
                 } else {
                     // According to the regular scroll view we are partially off screen
@@ -766,8 +771,8 @@
                         // If it is expanded we have to collapse it to a new size
                         float newSize = yPositionInScrollViewAfterElement
                                 - mPaddingBetweenElements
-                                - algorithmState.scrollY + mCollapsedSize;
-                        newSize = Math.max(mCollapsedSize, newSize);
+                                - algorithmState.scrollY + mFirstChildMinHeight;
+                        newSize = Math.max(mFirstChildMinHeight, newSize);
                         algorithmState.itemsInTopStack = 1.0f;
                         childViewState.height = (int) newSize;
                     }
@@ -809,7 +814,7 @@
                     // Interpolate the index from 0 to 2 while the second item is
                     // translating in.
                     stackIndex -= 1.0f;
-                    if (algorithmState.scrollY > mCollapsedSize) {
+                    if (algorithmState.scrollY > mFirstChildMinHeight) {
 
                         // Since there is a shadow treshhold, we cant just interpolate from 0 to
                         // 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
@@ -863,7 +868,7 @@
                     ExpandableNotificationRow row =
                             (ExpandableNotificationRow) mFirstChildWhileExpanding;
                     if (row.isHeadsUp()) {
-                        mFirstChildMaxHeight += mCollapsedSize - row.getHeadsUpHeight();
+                        mFirstChildMaxHeight += mFirstChildMinHeight - row.getHeadsUpHeight();
                     }
                 }
             } else {
@@ -927,7 +932,11 @@
         this.mIsExpanded = isExpanded;
     }
 
-    public void notifyChildrenChanged(final ViewGroup hostView) {
+    public void notifyChildrenChanged(final NotificationStackScrollLayout hostView) {
+        int firstItemMinHeight = hostView.getFirstItemMinHeight();
+        if (firstItemMinHeight != mFirstChildMinHeight) {
+            mFirstChildMinHeight = firstItemMinHeight;
+        }
         if (mIsExpansionChanging) {
             hostView.post(new Runnable() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index bbe5dd9..39a2986 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -130,7 +130,7 @@
     }
 
     @Override
-    protected int getMaxKeyguardNotifications() {
+    protected int getMaxKeyguardNotifications(boolean recompute) {
         return 0;
     }
 
diff --git a/preloaded-classes b/preloaded-classes
index 79c0957..e44b25e 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1765,6 +1765,7 @@
 android.util.SuperNotCalledException
 android.util.TypedValue
 android.util.Xml
+android.util.jar.StrictJarFile
 android.view.AbsSavedState
 android.view.AbsSavedState$1
 android.view.AbsSavedState$2
@@ -3361,9 +3362,6 @@
 java.util.jar.JarEntry
 java.util.jar.JarFile
 java.util.jar.JarFile$JarFileEnumerator
-java.util.jar.Manifest
-java.util.jar.ManifestReader
-java.util.jar.StrictJarFile
 java.util.logging.ConsoleHandler
 java.util.logging.ErrorManager
 java.util.logging.Filter
@@ -3824,4 +3822,11 @@
 org.xmlpull.v1.XmlPullParser
 org.xmlpull.v1.XmlPullParserException
 org.xmlpull.v1.XmlSerializer
+<<<<<<< HEAD
 sun.misc.Unsafe
+=======
+<<<<<<< HEAD
+=======
+sun.misc.Unsafe
+>>>>>>> 631d21f... Move StrictJarFile from libcore to framework
+>>>>>>> 43ea2cc... DO NOT MERGE Move StrictJarFile from libcore to framework
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3d358ec..11fdbb5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -433,7 +433,6 @@
                         AccessibilityEvent.obtain(event)).sendToTarget();
             }
             event.recycle();
-            getUserStateLocked(resolvedUserId).mHandledFeedbackTypes = 0;
         }
         return (OWN_PROCESS_ID != Binder.getCallingPid());
     }
@@ -1051,9 +1050,7 @@
                 Service service = state.mBoundServices.get(i);
 
                 if (service.mIsDefault == isDefault) {
-                    if (canDispatchEventToServiceLocked(service, event,
-                            state.mHandledFeedbackTypes)) {
-                        state.mHandledFeedbackTypes |= service.mFeedbackType;
+                    if (canDispatchEventToServiceLocked(service, event)) {
                         service.notifyAccessibilityEvent(event);
                     }
                 }
@@ -1088,19 +1085,14 @@
 
     /**
      * Determines if given event can be dispatched to a service based on the package of the
-     * event source and already notified services for that event type. Specifically, a
-     * service is notified if it is interested in events from the package and no other service
-     * providing the same feedback type has been notified. Exception are services the
-     * provide generic feedback (feedback type left as a safety net for unforeseen feedback
-     * types) which are always notified.
+     * event source. Specifically, a service is notified if it is interested in events from the
+     * package.
      *
      * @param service The potential receiver.
      * @param event The event.
-     * @param handledFeedbackTypes The feedback types for which services have been notified.
      * @return True if the listener should be notified, false otherwise.
      */
-    private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event,
-            int handledFeedbackTypes) {
+    private boolean canDispatchEventToServiceLocked(Service service, AccessibilityEvent event) {
 
         if (!service.canReceiveEventsLocked()) {
             return false;
@@ -1121,15 +1113,7 @@
         String packageName = (event.getPackageName() != null)
                 ? event.getPackageName().toString() : null;
 
-        if (packageNames.isEmpty() || packageNames.contains(packageName)) {
-            int feedbackType = service.mFeedbackType;
-            if ((handledFeedbackTypes & feedbackType) != feedbackType
-                    || feedbackType == AccessibilityServiceInfo.FEEDBACK_GENERIC) {
-                return true;
-            }
-        }
-
-        return false;
+        return (packageNames.isEmpty() || packageNames.contains(packageName));
     }
 
     private void unbindAllServicesLocked(UserState userState) {
@@ -3886,8 +3870,6 @@
         public final Set<ComponentName> mTouchExplorationGrantedServices =
                 new HashSet<>();
 
-        public int mHandledFeedbackTypes = 0;
-
         public int mLastSentClientState = -1;
 
         public boolean mIsAccessibilityEnabled;
@@ -3950,7 +3932,6 @@
             mBindingServices.clear();
 
             // Clear event management state.
-            mHandledFeedbackTypes = 0;
             mLastSentClientState = -1;
 
             // Clear state persisted in settings.
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 78a4e35..42dd9a8 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -70,6 +70,7 @@
 import android.content.res.TypedArray;
 import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
+import android.hardware.input.InputManagerInternal;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
 import android.os.Binder;
@@ -1930,6 +1931,16 @@
         }
     }
 
+    private void notifyInputMethodSubtypeChanged(final int userId,
+            @Nullable final InputMethodInfo inputMethodInfo,
+            @Nullable final InputMethodSubtype subtype) {
+        final InputManagerInternal inputManagerInternal =
+                LocalServices.getService(InputManagerInternal.class);
+        if (inputManagerInternal != null) {
+            inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
+        }
+    }
+
     /* package */ void setInputMethodLocked(String id, int subtypeId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
@@ -1972,8 +1983,10 @@
                         mCurMethod.changeInputMethodSubtype(newSubtype);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
+                        return;
                     }
                 }
+                notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
             }
             return;
         }
@@ -1999,6 +2012,9 @@
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
+
+        notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
+                getCurrentInputMethodSubtypeLocked());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index da81528..d6c6f13 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
 import android.app.trust.IStrongAuthTracker;
@@ -388,6 +390,13 @@
         }
     }
 
+    private void unlockUser(int userId, byte[] token) {
+        try {
+            ActivityManagerNative.getDefault().unlockUser(userId, token);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
 
     private byte[] getCurrentHandle(int userId) {
         CredentialHash credential;
@@ -612,6 +621,7 @@
             byte[] hash = credentialUtil.toHash(credential, userId);
             if (Arrays.equals(hash, storedHash.hash)) {
                 unlockKeystore(credentialUtil.adjustForKeystore(credential), userId);
+                unlockUser(userId, null);
                 // migrate credential to GateKeeper
                 credentialUtil.setCredential(credential, null, userId);
                 if (!hasChallenge) {
@@ -664,6 +674,7 @@
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
             // credential has matched
             unlockKeystore(credential, userId);
+            unlockUser(userId, null);
             if (shouldReEnroll) {
                 credentialUtil.setCredential(credential, credential, userId);
             }
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index a32bb2f..bd43a71 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -781,6 +781,7 @@
     }
 
     private void handleSystemReady() {
+        initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
         // Start scheduling nominally-daily fstrim operations
@@ -828,6 +829,22 @@
         mVolumes.put(internal.id, internal);
     }
 
+    private void initIfReadyAndConnected() {
+        Slog.d(TAG, "Thinking about init, mSystemReady=" + mSystemReady
+                + ", mDaemonConnected=" + mDaemonConnected);
+        if (mSystemReady && mDaemonConnected && StorageManager.isFileBasedEncryptionEnabled()) {
+            final List<UserInfo> users = mContext.getSystemService(UserManager.class)
+                    .getUsers();
+            for (UserInfo user : users) {
+                try {
+                    mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
+                } catch (NativeDaemonConnectorException e) {
+                    Slog.w(TAG, "Failed to init vold", e);
+                }
+            }
+        }
+    }
+
     private void resetIfReadyAndConnected() {
         Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
                 + ", mDaemonConnected=" + mDaemonConnected);
@@ -928,6 +945,7 @@
     }
 
     private void handleDaemonConnected() {
+        initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
         /*
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d9580c5..2bb5911 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3356,10 +3356,8 @@
 
         try {
             try {
-                if (AppGlobals.getPackageManager().isPackageFrozen(app.info.packageName)) {
-                    // This is caught below as if we had failed to fork zygote
-                    throw new RuntimeException("Package " + app.info.packageName + " is frozen!");
-                }
+                final int userId = UserHandle.getUserId(app.uid);
+                AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             }
@@ -4399,8 +4397,10 @@
             if (r == null) {
                 return;
             }
-            if (r.task != null && r.task.mResizeable) {
-                // Fixed screen orientation isn't supported with resizeable activities.
+            TaskRecord task = r.task;
+            if (task != null && (!task.mFullscreen || !task.stack.mFullscreen)) {
+                // Fixed screen orientation isn't supported when activities aren't in full screen
+                // mode.
                 return;
             }
             final long origId = Binder.clearCallingIdentity();
@@ -17777,7 +17777,7 @@
      * @param userId is only used when persistent parameter is set to true to persist configuration
      *               for that particular user
      */
-    boolean updateConfigurationLocked(Configuration values,
+    private boolean updateConfigurationLocked(Configuration values,
             ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
         int changes = 0;
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c3bd982..94a200a 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -184,7 +184,6 @@
     static final int CONTAINER_CALLBACK_TASK_LIST_EMPTY = FIRST_SUPERVISOR_STACK_MSG + 11;
     static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
     static final int SHOW_LOCK_TASK_ESCAPE_MESSAGE_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
-    static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = FIRST_SUPERVISOR_STACK_MSG + 14;
 
     private static final String VIRTUAL_DISPLAY_BASE_NAME = "ActivityViewVirtualDisplay";
 
@@ -2592,6 +2591,11 @@
             mService.setFocusedActivityLocked(r, "startedActivity");
         }
         updateUserStackLocked(r.userId, targetStack);
+
+        if (!r.task.mResizeable && isStackDockedInEffect(targetStack.mStackId)) {
+            showNonResizeableDockToast(r.task.taskId);
+        }
+
         return ActivityManager.START_SUCCESS;
     }
 
@@ -3114,7 +3118,7 @@
                     for (int i = 0; i < count; i++) {
                         moveTaskToStackLocked(tasks.get(i).taskId,
                                 FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, FORCE_FOCUS, "resizeStack",
-                                true /* animate */);
+                                false /* animate */);
                     }
 
                     // stack shouldn't contain anymore activities, so nothing to resume.
@@ -3376,6 +3380,10 @@
         final ActivityStack stack = moveTaskToStackUncheckedLocked(
                 task, stackId, toTop, forceFocus, "moveTaskToStack:" + reason);
 
+        if (!animate) {
+            stack.mNoAnimActivities.add(topActivity);
+        }
+
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
             resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
@@ -3393,7 +3401,7 @@
         resumeTopActivitiesLocked();
 
         if (!task.mResizeable && isStackDockedInEffect(stackId)) {
-            showNonResizeableDockToast();
+            showNonResizeableDockToast(taskId);
         }
     }
 
@@ -4327,8 +4335,8 @@
         }
     }
 
-    void showNonResizeableDockToast() {
-        mHandler.sendEmptyMessage(SHOW_NON_RESIZEABLE_DOCK_TOAST);
+    private void showNonResizeableDockToast(int taskId) {
+        mWindowManager.scheduleShowNonResizeableDockToast(taskId);
     }
 
     void showLockTaskToast() {
@@ -4643,13 +4651,7 @@
                         }
                     }
                 } break;
-                case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
-                    final Toast toast = Toast.makeText(
-                            mService.mContext,
-                            mService.mContext.getString(R.string.dock_non_resizeble_text),
-                            Toast.LENGTH_LONG);
-                    toast.show();
-                } break;
+
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index b30905e..9c29149 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -624,6 +624,23 @@
             throw new SecurityException(msg);
         }
 
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return unlockUserCleared(userId, token);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    boolean unlockUserCleared(final int userId, byte[] token) {
+        synchronized (mService) {
+            final UserState uss = mStartedUsers.get(userId);
+            if (uss.unlocked) {
+                // Bail early when already unlocked
+                return true;
+            }
+        }
+
         final UserInfo userInfo = getUserInfo(userId);
         final IMountService mountService = IMountService.Stub
                 .asInterface(ServiceManager.getService("mount"));
@@ -631,7 +648,7 @@
             mountService.unlockUserKey(userId, userInfo.serialNumber, token);
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to unlock: " + e.getMessage());
-            throw e.rethrowAsRuntimeException();
+            return false;
         }
 
         synchronized (mService) {
@@ -639,6 +656,11 @@
             updateUserUnlockedState(uss);
         }
 
+        final Intent intent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mService.broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null,
+                AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID, userId);
+
         return true;
     }
 
@@ -1011,7 +1033,7 @@
         if ((flags & ActivityManager.FLAG_OR_STOPPED) != 0) {
             return true;
         }
-        if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
+        if ((flags & ActivityManager.FLAG_AND_LOCKED) != 0) {
             // If user is currently locked, we fall through to default "running"
             // behavior below
             if (state.unlocked) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 81ae8ac..ae8fca8 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.input;
 
+import android.annotation.Nullable;
 import android.view.Display;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.R;
@@ -83,6 +84,9 @@
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicy;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
 import android.widget.Toast;
 
 import java.io.File;
@@ -116,6 +120,7 @@
     private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6;
+    private static final int MSG_INPUT_METHOD_SUBTYPE_CHANGED = 7;
 
     // Pointer to native input manager service object.
     private final long mPtr;
@@ -1206,6 +1211,15 @@
         }
     }
 
+    // Must be called on handler.
+    private void handleSwitchInputMethodSubtype(int userId,
+            @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+        if (DEBUG) {
+            Slog.i(TAG, "InputMethodSubtype changed: userId=" + userId
+                    + " ime=" + inputMethodInfo + " subtype=" + subtype);
+        }
+    }
+
     public void switchKeyboardLayout(int deviceId, int direction) {
         mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
     }
@@ -1757,12 +1771,22 @@
                 case MSG_RELOAD_DEVICE_ALIASES:
                     reloadDeviceAliases();
                     break;
-                case MSG_DELIVER_TABLET_MODE_CHANGED:
+                case MSG_DELIVER_TABLET_MODE_CHANGED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     long whenNanos = (args.argi1 & 0xFFFFFFFFl) | ((long) args.argi2 << 32);
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
+                }
+                case MSG_INPUT_METHOD_SUBTYPE_CHANGED: {
+                    final int userId = msg.arg1;
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final InputMethodInfo inputMethodInfo = (InputMethodInfo) args.arg1;
+                    final InputMethodSubtype subtype = (InputMethodSubtype) args.arg2;
+                    args.recycle();
+                    handleSwitchInputMethodSubtype(userId, inputMethodInfo, subtype);
+                    break;
+                }
             }
         }
     }
@@ -1920,5 +1944,15 @@
         public void setInteractive(boolean interactive) {
             nativeSetInteractive(mPtr, interactive);
         }
+
+        @Override
+        public void onInputMethodSubtypeChanged(int userId,
+                @Nullable InputMethodInfo inputMethodInfo, @Nullable InputMethodSubtype subtype) {
+            final SomeArgs someArgs = SomeArgs.obtain();
+            someArgs.arg1 = inputMethodInfo;
+            someArgs.arg2 = subtype;
+            mHandler.obtainMessage(MSG_INPUT_METHOD_SUBTYPE_CHANGED, userId, 0, someArgs)
+                    .sendToTarget();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index f92f631..246da2e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -63,7 +63,7 @@
  */
 public class MediaSessionRecord implements IBinder.DeathRecipient {
     private static final String TAG = "MediaSessionRecord";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /**
      * The length of time a session will still be considered active after
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 90dd10ea..d5c3113 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,6 +287,9 @@
      * 6. We need to tell the session to do any final cleanup (onDestroy)
      */
     private void destroySessionLocked(MediaSessionRecord session) {
+        if (DEBUG) {
+            Log.d(TAG, "Destroying session : " + session.toString());
+        }
         int userId = session.getUserId();
         UserRecord user = mUserRecords.get(userId);
         if (user != null) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 611718e..61c320b 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,13 +16,17 @@
 
 package com.android.server.media;
 
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.media.session.MediaController.PlaybackInfo;
 import android.media.session.PlaybackState;
 import android.media.session.MediaSession;
+import android.os.RemoteException;
 import android.os.UserHandle;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Keeps track of media sessions and their priority for notifications, media
@@ -61,6 +65,36 @@
     private ArrayList<MediaSessionRecord> mCachedTransportControlList;
 
     /**
+     * Checks if a media session is created from the most recent app.
+     *
+     * @param record A media session record to be examined.
+     * @return true if the media session's package name equals to the most recent app, false
+     * otherwise.
+     */
+    private static boolean isFromMostRecentApp(MediaSessionRecord record) {
+        if (ActivityManager.getCurrentUser() != record.getUserId()) {
+            return false;
+        }
+        try {
+            List<ActivityManager.RecentTaskInfo> tasks =
+                    ActivityManagerNative.getDefault().getRecentTasks(1,
+                            ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
+                            ActivityManager.RECENT_IGNORE_UNAVAILABLE |
+                            ActivityManager.RECENT_INCLUDE_PROFILES |
+                            ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId());
+            if (tasks != null && !tasks.isEmpty()) {
+                ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
+                if (recentTask.baseIntent != null)
+                    return recentTask.baseIntent.getComponent().getPackageName()
+                            .equals(record.getPackageName());
+            }
+        } catch (RemoteException e) {
+            return false;
+        }
+        return false;
+    }
+
+    /**
      * Add a record to the priority tracker.
      *
      * @param record The record to add.
@@ -68,7 +102,9 @@
     public void addSession(MediaSessionRecord record) {
         mSessions.add(record);
         clearCache();
-        mLastInterestingRecord = record;
+        if (isFromMostRecentApp(record)) {
+            mLastInterestingRecord = record;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4bc79cb..c7d1171 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2800,15 +2800,24 @@
     }
 
     @Override
-    public boolean isPackageFrozen(String packageName) {
+    public void checkPackageStartable(String packageName, int userId) {
+        final boolean userKeyUnlocked = isUserKeyUnlocked(userId);
+
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(packageName);
-            if (ps != null) {
-                return ps.frozen;
+            if (ps == null) {
+                throw new SecurityException("Package " + packageName + " was not found!");
+            }
+
+            if (ps.frozen) {
+                throw new SecurityException("Package " + packageName + " is currently frozen!");
+            }
+
+            if (!userKeyUnlocked && !(ps.pkg.applicationInfo.isEncryptionAware()
+                    || ps.pkg.applicationInfo.isPartiallyEncryptionAware())) {
+                throw new SecurityException("Package " + packageName + " is not encryption aware!");
             }
         }
-        Slog.w(TAG, "Package " + packageName + " is missing; assuming frozen");
-        return true;
     }
 
     @Override
@@ -3143,29 +3152,36 @@
     }
 
     /**
-     * Augment the given flags depending on current user running state. This is
-     * purposefully done before acquiring {@link #mPackages} lock.
+     * Return if the user key is currently unlocked.
      */
-    private int augmentFlagsForUser(int flags, int userId) {
+    private boolean isUserKeyUnlocked(int userId) {
         if (StorageManager.isFileBasedEncryptionEnabled()) {
             final IMountService mount = IMountService.Stub
                     .asInterface(ServiceManager.getService("mount"));
             if (mount == null) {
-                // We must be early in boot, so the best we can do is assume the
-                // user is fully running.
-                Slog.w(TAG, "Early during boot, assuming not encrypted");
-                return flags;
+                Slog.w(TAG, "Early during boot, assuming locked");
+                return false;
             }
             final long token = Binder.clearCallingIdentity();
             try {
-                if (!mount.isUserKeyUnlocked(userId)) {
-                    flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
-                }
+                return mount.isUserKeyUnlocked(userId);
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Augment the given flags depending on current user running state. This is
+     * purposefully done before acquiring {@link #mPackages} lock.
+     */
+    private int augmentFlagsForUser(int flags, int userId) {
+        if (!isUserKeyUnlocked(userId)) {
+            flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
         }
         return flags;
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 424b902..da10a94 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -652,6 +652,7 @@
     private void initDefaultGuestRestrictions() {
         synchronized (mGuestRestrictions) {
             if (mGuestRestrictions.isEmpty()) {
+                mGuestRestrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
                 mGuestRestrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
                 mGuestRestrictions.putBoolean(UserManager.DISALLOW_SMS, true);
             }
@@ -1652,6 +1653,11 @@
             }
             updateUserIds();
             Bundle restrictions = new Bundle();
+            if (isGuest) {
+                synchronized (mGuestRestrictions) {
+                    restrictions.putAll(mGuestRestrictions);
+                }
+            }
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.append(userId, restrictions);
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ae6874f..639753a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2533,7 +2533,7 @@
                 }
             }
         } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
-            if (transit == TRANSIT_ENTER) {
+            if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
                 return R.anim.fade_in;
             } else if (transit == TRANSIT_EXIT) {
                 return R.anim.fade_out;
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index b32ec2d..4861acc 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -109,7 +109,7 @@
     public AppWindowAnimator(final AppWindowToken atoken) {
         mAppToken = atoken;
         mService = atoken.service;
-        mAnimator = atoken.mAnimator;
+        mAnimator = mService.mAnimator;
     }
 
     public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 425ff9b..77a29f3 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -24,6 +24,7 @@
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.wm.WindowManagerService.H;
 
+import android.annotation.NonNull;
 import android.content.pm.ActivityInfo;
 import android.os.Message;
 import android.os.RemoteException;
@@ -49,9 +50,7 @@
     // All of the windows and child windows that are included in this
     // application token.  Note this list is NOT sorted!
     final WindowList allAppWindows = new WindowList();
-    final AppWindowAnimator mAppAnimator;
-
-    final WindowAnimator mAnimator;
+    @NonNull final AppWindowAnimator mAppAnimator;
 
     final boolean voiceInteraction;
 
@@ -145,7 +144,6 @@
         appToken = _token;
         voiceInteraction = _voiceInteraction;
         mInputApplicationHandle = new InputApplicationHandle(this);
-        mAnimator = service.mAnimator;
         mAppAnimator = new AppWindowAnimator(this);
     }
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 6b62467..df8d5d6 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -18,7 +18,11 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.IDockDividerVisibilityListener;
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -40,6 +44,8 @@
     private WindowState mWindow;
     private final Rect mTmpRect = new Rect();
     private final Rect mLastRect = new Rect();
+    private IDockDividerVisibilityListener mListener;
+    private boolean mLastVisibility = false;
 
     DockedStackDividerController(Context context, DisplayContent displayContent) {
         mDisplayContent = displayContent;
@@ -67,12 +73,21 @@
     }
 
     void reevaluateVisibility() {
-        if (mWindow == null) return;
+        if (mWindow == null) {
+            return;
+        }
         TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(DOCKED_STACK_ID);
-        if (stack != null && stack.isVisibleLocked()) {
-            mWindow.showLw(true /* doAnimation */);
-        } else {
-            mWindow.hideLw(true /* doAnimation */);
+        final boolean visible = stack != null && stack.isVisibleLocked();
+        if (mLastVisibility == visible) {
+            return;
+        }
+        mLastVisibility = visible;
+        if (mListener != null) {
+            try {
+                mListener.onDockDividerVisibilityChanged(visible);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "visibility call failed: " + e);
+            }
         }
     }
 
@@ -110,4 +125,11 @@
         }
         mLastRect.set(frame);
     }
+
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (mListener != null && listener != null) {
+            throw new IllegalStateException("Dock divider visibility listener already set!");
+        }
+        mListener = listener;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 74e8e53..15a3c48 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -26,6 +26,11 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
 import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerService.H.RESIZE_TASK;
+import static com.android.server.wm.WindowManagerService.H.SHOW_NON_RESIZEABLE_DOCK_TOAST;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
 
 import android.app.ActivityManager.StackId;
 import android.content.res.Configuration;
@@ -76,6 +81,11 @@
     // Whether the task is resizeable
     private boolean mResizeable;
 
+    // Whether we need to show toast about the app being non-resizeable when it becomes visible.
+    // This flag is set when a non-resizeable task is docked (or side-by-side). It's cleared
+    // after we show the toast.
+    private boolean mShowNonResizeableDockToast;
+
     // Whether the task is currently being drag-resized
     private boolean mDragResizing;
 
@@ -92,6 +102,41 @@
         return mStack.getDisplayContent();
     }
 
+    void setShowNonResizeableDockToast() {
+        mShowNonResizeableDockToast = true;
+    }
+
+    void scheduleShowNonResizeableDockToastIfNeeded() {
+        if (!mShowNonResizeableDockToast) {
+            return;
+        }
+        final DisplayContent displayContent = mStack.getDisplayContent();
+        // If docked stack is not yet visible, we don't want to show the toast yet,
+        // since we need the visible rect of the docked task to position the toast.
+        if (displayContent == null || displayContent.getDockedStackLocked() == null) {
+            return;
+        }
+
+        mShowNonResizeableDockToast = false;
+
+        final int dockSide = mStack.getDockSide();
+        int xOffset = 0;
+        int yOffset = 0;
+        if (dockSide != DOCKED_INVALID) {
+            mStack.getBounds(mTmpRect);
+            displayContent.getLogicalDisplayRect(mTmpRect2);
+
+            if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
+                xOffset = mTmpRect.centerX() - mTmpRect2.centerX();
+            } else if (dockSide == DOCKED_TOP) {
+                // We don't adjust for DOCKED_BOTTOM case since it's already at the bottom.
+                yOffset = mTmpRect2.bottom - mTmpRect.bottom;
+            }
+            mService.mH.obtainMessage(
+                    SHOW_NON_RESIZEABLE_DOCK_TOAST, xOffset, yOffset).sendToTarget();
+        }
+    }
+
     void addAppToken(int addPos, AppWindowToken wtoken) {
         final int lastPos = mAppTokens.size();
         if (addPos >= lastPos) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 87deaa4..9788975 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -658,10 +658,11 @@
     }
 
     /**
-     * For docked workspace provides information which side of the screen was the dock anchored.
+     * For docked workspace (or workspace that's side-by-side to the docked), provides
+     * information which side of the screen was the dock anchored.
      */
     int getDockSide() {
-        if (mStackId != DOCKED_STACK_ID) {
+        if (mStackId != DOCKED_STACK_ID && !StackId.isResizeableByDockedStack(mStackId)) {
             return DOCKED_INVALID;
         }
         if (mDisplayContent == null) {
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 46fab2a..f20c4e5 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -220,8 +220,8 @@
 
         if (appShowWhenLocked != null) {
             allowWhenLocked |= appShowWhenLocked == win.mAppToken
-                    // Show all SHOW_WHEN_LOCKED windows while they're animating
-                    || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0 && win.isAnimatingLw()
+                    // Show all SHOW_WHEN_LOCKED windows if some apps are shown over lockscreen
+                    || (win.mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0
                     // Show error dialogs over apps that dismiss keyguard.
                     || (win.mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_ERROR) != 0;
         }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ac90daf..dab195f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -121,6 +121,7 @@
 import android.view.Gravity;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.IDockDividerVisibilityListener;
 import android.view.IInputFilter;
 import android.view.IOnKeyguardExitResult;
 import android.view.IRotationWatcher;
@@ -149,8 +150,10 @@
 import android.view.WindowManagerPolicy;
 import android.view.WindowManagerPolicy.PointerEventListener;
 import android.view.animation.Animation;
+import android.widget.Toast;
 
 import com.android.internal.app.IAssistScreenshotReceiver;
+import com.android.internal.R;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.view.IInputContext;
 import com.android.internal.view.IInputMethodClient;
@@ -2689,8 +2692,7 @@
                     // need to see about starting one.
                     final boolean notExitingOrAnimating =
                             !win.mExiting && !win.isAnimatingWithSavedSurface();
-                    result |= notExitingOrAnimating
-                            ? RELAYOUT_RES_SURFACE_CHANGED : 0;
+                    result |= notExitingOrAnimating ? RELAYOUT_RES_SURFACE_CHANGED : 0;
                     if (notExitingOrAnimating) {
                         focusMayChange = tryStartingAnimation(win, winAnimator, isDefaultDisplay,
                                 focusMayChange);
@@ -3075,7 +3077,7 @@
         // TODO:
     }
 
-    boolean checkCallingPermission(String permission, String func) {
+    private boolean checkCallingPermission(String permission, String func) {
         // Quick check: if the calling permission is me, it's all okay.
         if (Binder.getCallingPid() == Process.myPid()) {
             return true;
@@ -4787,6 +4789,17 @@
         }
     }
 
+    public void scheduleShowNonResizeableDockToast(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                if (DEBUG_STACK) Slog.i(TAG, "scheduleShowToast: could not find taskId=" + taskId);
+                return;
+            }
+            task.setShowNonResizeableDockToast();
+        }
+    }
+
     public void getStackBounds(int stackId, Rect bounds) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);
@@ -7452,6 +7465,7 @@
         public static final int RESIZE_TASK = 44;
 
         public static final int TWO_FINGER_SCROLL_START = 45;
+        public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
 
         /**
          * Used to denote that an integer field in a message will not be used.
@@ -8024,6 +8038,17 @@
                     }
                 }
                 break;
+                case SHOW_NON_RESIZEABLE_DOCK_TOAST: {
+                    final Toast toast = Toast.makeText(mContext,
+                            mContext.getString(R.string.dock_non_resizeble_text),
+                            Toast.LENGTH_LONG);
+                    final int gravity = toast.getGravity();
+                    final int xOffset = toast.getXOffset() + msg.arg1;
+                    final int yOffset = toast.getYOffset() + msg.arg2;
+                    toast.setGravity(gravity, xOffset, yOffset);
+                    toast.show();
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
@@ -10211,6 +10236,29 @@
         mDestroySurface.add(win);
     }
 
+    @Override
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+        if (!checkCallingPermission(android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS,
+                "registerDockDividerVisibilityListener()")) {
+            return;
+        }
+        // TODO(multi-display): The listener is registered on the default display only.
+        final DockedStackDividerController controller =
+                getDefaultDisplayContentLocked().getDockedDividerController();
+        controller.registerDockDividerVisibilityListener(listener);
+        try {
+            listener.asBinder().linkToDeath(new IBinder.DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    getDefaultDisplayContentLocked().getDockedDividerController()
+                            .registerDockDividerVisibilityListener(null);
+                }
+            }, 0);
+        } catch (RemoteException e) {
+            controller.registerDockDividerVisibilityListener(null);
+        }
+    }
+
     private final class LocalService extends WindowManagerInternal {
         @Override
         public void requestTraversalFromDisplayManager() {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 064b412..29cadf3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -730,7 +730,7 @@
             mVisibleFrame.set(mContentFrame);
             mStableFrame.set(mContentFrame);
         } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
-            if (isVisibleLw()) {
+            if (isVisibleLw() || mWinAnimator.isAnimating()) {
                 // We don't adjust the dock divider frame for reasons other than performance. The
                 // real reason is that if it gets adjusted before it is shown for the first time,
                 // it would get size (0, 0). This causes a problem when we finally show the dock
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 93c2ff6..132b1b6 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1474,6 +1474,11 @@
                 }
                 mWin.mAppToken.updateReportedVisibilityLocked();
             }
+
+            final Task task = mWin.getTask();
+            if (task != null) {
+                task.scheduleShowNonResizeableDockToastIfNeeded();
+            }
             return true;
         }
         return false;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 4115756..17bd08c 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -1805,6 +1805,13 @@
     public void onReject(String replyMessage) {}
 
     /**
+     * Notifies the Connection of a request to silence the ringer.
+     *
+     * @hide
+     */
+    public void onSilence() {}
+
+    /**
      * Notifies this Connection whether the user wishes to proceed with the post-dial DTMF codes.
      */
     public void onPostDialContinue(boolean proceed) {}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6223495..b4a7ce0 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -102,6 +102,7 @@
     private static final int MSG_MERGE_CONFERENCE = 18;
     private static final int MSG_SWAP_CONFERENCE = 19;
     private static final int MSG_REJECT_WITH_MESSAGE = 20;
+    private static final int MSG_SILENCE = 21;
 
     private static Connection sNullConnection;
 
@@ -177,6 +178,11 @@
         }
 
         @Override
+        public void silence(String callId) {
+            mHandler.obtainMessage(MSG_SILENCE, callId).sendToTarget();
+        }
+
+        @Override
         public void disconnect(String callId) {
             mHandler.obtainMessage(MSG_DISCONNECT, callId).sendToTarget();
         }
@@ -319,6 +325,9 @@
                 case MSG_DISCONNECT:
                     disconnect((String) msg.obj);
                     break;
+                case MSG_SILENCE:
+                    silence((String) msg.obj);
+                    break;
                 case MSG_HOLD:
                     hold((String) msg.obj);
                     break;
@@ -708,6 +717,11 @@
         findConnectionForAction(callId, "reject").onReject(rejectWithMessage);
     }
 
+    private void silence(String callId) {
+        Log.d(this, "silence %s", callId);
+        findConnectionForAction(callId, "silence").onSilence();
+    }
+
     private void disconnect(String callId) {
         Log.d(this, "disconnect %s", callId);
         if (mConnectionById.containsKey(callId)) {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index dd253cf..8a54add 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -54,6 +54,8 @@
 
     void disconnect(String callId);
 
+    void silence(String callId);
+
     void hold(String callId);
 
     void unhold(String callId);
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 4821678..c967c2b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -147,6 +147,11 @@
     }
 
     @Override
+    public SharedPreferences getSharedPreferences(File file, int mode) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public FileInputStream openFileInput(String name) throws FileNotFoundException {
         throw new UnsupportedOperationException();
     }
diff --git a/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
new file mode 100644
index 0000000..5ba5675
--- /dev/null
+++ b/tests/NetworkSecurityConfigTest/res/xml/override_dedup.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <!-- Entry with a bad pin. Connections to this will only succeed if overridePins is set. -->
+  <domain-config>
+    <domain>android.com</domain>
+    <pin-set>
+      <pin digest="SHA-256">aaaaaaaaIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+    </pin-set>
+    <trust-anchors>
+      <certificates src="system" overridePins="false" />
+    </trust-anchors>
+  </domain-config>
+  <!-- override that contains all of the system CA store. This should completely override the
+       anchors in the domain config-above with ones that have overridePins set. -->
+  <debug-overrides>
+    <trust-anchors>
+      <certificates src="system" />
+    </trust-anchors>
+  </debug-overrides>
+</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
index 92eadc0..69b2a9d 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestCertificateSource.java
@@ -19,15 +19,29 @@
 import java.util.Set;
 import java.security.cert.X509Certificate;
 
+import com.android.org.conscrypt.TrustedCertificateIndex;
+
 /** @hide */
 public class TestCertificateSource implements CertificateSource {
 
     private final Set<X509Certificate> mCertificates;
+    private final TrustedCertificateIndex mIndex = new TrustedCertificateIndex();
     public TestCertificateSource(Set<X509Certificate> certificates) {
         mCertificates = certificates;
+        for (X509Certificate cert : certificates) {
+            mIndex.index(cert);
+        }
     }
 
     public Set<X509Certificate> getCertificates() {
             return mCertificates;
     }
+
+    public X509Certificate findBySubjectAndPublicKey(X509Certificate cert) {
+        java.security.cert.TrustAnchor anchor = mIndex.findBySubjectAndPublicKey(cert);
+        if (anchor == null) {
+            return null;
+        }
+        return anchor.getTrustedCert();
+    }
 }
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index c6f3680..998bb68 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -402,4 +402,22 @@
         context.init(null, tms, null);
         TestUtils.assertConnectionSucceeds(context, "android.com" , 443);
     }
+
+    public void testDebugDedup() throws Exception {
+        XmlConfigSource source = new XmlConfigSource(getContext(), R.xml.override_dedup, true);
+        ApplicationConfig appConfig = new ApplicationConfig(source);
+        assertTrue(appConfig.hasPerDomainConfigs());
+        // Check android.com.
+        NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
+        PinSet pinSet = config.getPins();
+        assertFalse(pinSet.pins.isEmpty());
+        // Check that all TrustAnchors come from the override pins debug source.
+        for (TrustAnchor anchor : config.getTrustAnchors()) {
+            assertTrue(anchor.overridesPins);
+        }
+        // Try connections.
+        SSLContext context = TestUtils.getSSLContext(source);
+        TestUtils.assertConnectionSucceeds(context, "android.com", 443);
+        TestUtils.assertUrlConnectionSucceeds(context, "android.com", 443);
+    }
 }
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index e22e76d..fb0fe38 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1537,12 +1537,20 @@
     std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
     while (!workQueue.empty()) {
         CompileResourceWorkItem& workItem = workQueue.front();
-        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+        int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+                | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+        if (!workItem.needsCompiling) {
+            xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+            xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
+        }
+        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+                             workItem.file, &table, xmlCompilationFlags);
+
         if (err == NO_ERROR) {
             assets->addResource(workItem.resPath.getPathLeaf(),
-                    workItem.resPath,
-                    workItem.file,
-                    workItem.file->getResourceType());
+                                workItem.resPath,
+                                workItem.file,
+                                workItem.file->getResourceType());
         } else {
             hasErrors = true;
         }
@@ -1737,9 +1745,7 @@
             manifestFile->getGroupEntry(),
             manifestFile->getResourceType());
     err = compileXmlFile(bundle, assets, String16(), manifestFile,
-            outManifestFile, &table,
-            XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
-            | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+            outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
     if (err < NO_ERROR) {
         return err;
     }
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index d5a09d8..0e470d9 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -88,8 +88,11 @@
         root->setUTF8(true);
     }
 
-    bool hasErrors = false;
+    if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
+        return UNKNOWN_ERROR;
+    }
     
+    bool hasErrors = false;
     if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
         status_t err = root->assignResourceIds(assets, table);
         if (err != NO_ERROR) {
@@ -97,9 +100,11 @@
         }
     }
 
-    status_t err = root->parseValues(assets, table);
-    if (err != NO_ERROR) {
-        hasErrors = true;
+    if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+        status_t err = root->parseValues(assets, table);
+        if (err != NO_ERROR) {
+            hasErrors = true;
+        }
     }
 
     if (hasErrors) {
@@ -114,7 +119,7 @@
         printf("Input XML Resource:\n");
         root->print();
     }
-    err = root->flatten(target,
+    status_t err = root->flatten(target,
             (options&XML_COMPILE_STRIP_COMMENTS) != 0,
             (options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
     if (err != NO_ERROR) {
@@ -4755,9 +4760,9 @@
         newConfig.sdkVersion = sdkVersionToGenerate;
         sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
                 AaptGroupEntry(newConfig), target->getResourceType());
-        String8 resPath = String8::format("res/%s/%s",
+        String8 resPath = String8::format("res/%s/%s.xml",
                 newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
-                target->getSourceFile().getPathLeaf().string());
+                String8(resourceName).string());
         resPath.convertToResPath();
 
         // Add a resource table entry.
@@ -4784,9 +4789,11 @@
         item.resourceName = resourceName;
         item.resPath = resPath;
         item.file = newFile;
+        item.xmlRoot = newRoot;
+        item.needsCompiling = false;    // This step occurs after we parse/assign, so we don't need
+                                        // to do it again.
         mWorkQueue.push(item);
     }
-
     return NO_ERROR;
 }
 
@@ -4825,3 +4832,226 @@
         }
     }
 }
+
+static String16 buildNamespace(const String16& package) {
+    return String16("http://schemas.android.com/apk/res/") + package;
+}
+
+static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
+    const Vector<sp<XMLNode> >& children = parent->getChildren();
+    sp<XMLNode> onlyChild;
+    for (size_t i = 0; i < children.size(); i++) {
+        if (children[i]->getType() != XMLNode::TYPE_CDATA) {
+            if (onlyChild != NULL) {
+                return NULL;
+            }
+            onlyChild = children[i];
+        }
+    }
+    return onlyChild;
+}
+
+/**
+ * Detects use of the `bundle' format and extracts nested resources into their own top level
+ * resources. The bundle format looks like this:
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
+ *   <aapt:attr name="android:drawable">
+ *     <vector android:width="60dp"
+ *             android:height="60dp">
+ *       <path android:name="v"
+ *             android:fillColor="#000000"
+ *             android:pathData="M300,70 l 0,-70 70,..." />
+ *     </vector>
+ *   </aapt:attr>
+ * </animated-vector>
+ *
+ * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
+ * into a new high-level resource, assigning it a name and ID. Then value of the `name`
+ * attribute must be a resource attribute. That resource attribute is inserted into the parent
+ * with the reference to the extracted resource as the value.
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector android:drawable="@drawable/bundle_1.xml">
+ * </animated-vector>
+ *
+ * <!-- res/drawable/bundle_1.xml -->
+ * <vector android:width="60dp"
+ *         android:height="60dp">
+ *   <path android:name="v"
+ *         android:fillColor="#000000"
+ *         android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ */
+status_t ResourceTable::processBundleFormat(const Bundle* bundle,
+                                            const String16& resourceName,
+                                            const sp<AaptFile>& target,
+                                            const sp<XMLNode>& root) {
+    Vector<sp<XMLNode> > namespaces;
+    if (root->getType() == XMLNode::TYPE_NAMESPACE) {
+        namespaces.push(root);
+    }
+    return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
+}
+
+status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
+                                                const String16& resourceName,
+                                                const sp<AaptFile>& target,
+                                                const sp<XMLNode>& parent,
+                                                Vector<sp<XMLNode> >* namespaces) {
+    const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
+    const String16 kName16("name");
+    const String16 kAttr16("attr");
+    const String16 kAssetPackage16(mAssets->getPackage());
+
+    Vector<sp<XMLNode> >& children = parent->getChildren();
+    for (size_t i = 0; i < children.size(); i++) {
+        const sp<XMLNode>& child = children[i];
+
+        if (child->getType() == XMLNode::TYPE_CDATA) {
+            continue;
+        } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+            namespaces->push(child);
+        }
+
+        if (child->getElementNamespace() != kAaptNamespaceUri16 ||
+                child->getElementName() != kAttr16) {
+            status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
+                                                      namespaces);
+            if (result != NO_ERROR) {
+                return result;
+            }
+
+            if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+                namespaces->pop();
+            }
+            continue;
+        }
+
+        // This is the <aapt:attr> tag. Look for the 'name' attribute.
+        SourcePos source(child->getFilename(), child->getStartLineNumber());
+
+        sp<XMLNode> nestedRoot = findOnlyChildElement(child);
+        if (nestedRoot == NULL) {
+            source.error("<%s:%s> must have exactly one child element",
+                         String8(child->getElementNamespace()).string(),
+                         String8(child->getElementName()).string());
+            return UNKNOWN_ERROR;
+        }
+
+        // Find the special attribute 'parent-attr'. This attribute's value contains
+        // the resource attribute for which this element should be assigned in the parent.
+        const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
+        if (attr == NULL) {
+            source.error("inline resource definition must specify an attribute via 'name'");
+            return UNKNOWN_ERROR;
+        }
+
+        // Parse the attribute name.
+        const char* errorMsg = NULL;
+        String16 attrPackage, attrType, attrName;
+        bool result = ResTable::expandResourceRef(attr->string.string(),
+                                                  attr->string.size(),
+                                                  &attrPackage, &attrType, &attrName,
+                                                  &kAttr16, &kAssetPackage16,
+                                                  &errorMsg, NULL);
+        if (!result) {
+            source.error("invalid attribute name for 'name': %s", errorMsg);
+            return UNKNOWN_ERROR;
+        }
+
+        if (attrType != kAttr16) {
+            // The value of the 'name' attribute must be an attribute reference.
+            source.error("value of 'name' must be an attribute reference.");
+            return UNKNOWN_ERROR;
+        }
+
+        // Generate a name for this nested resource and try to add it to the table.
+        // We do this in a loop because the name may be taken, in which case we will
+        // increment a suffix until we succeed.
+        String8 nestedResourceName;
+        String8 nestedResourcePath;
+        int suffix = 1;
+        while (true) {
+            // This child element will be extracted into its own resource file.
+            // Generate a name and path for it from its parent.
+            nestedResourceName = String8::format("%s_%d",
+                        String8(resourceName).string(), suffix++);
+            nestedResourcePath = String8::format("res/%s/%s.xml",
+                        target->getGroupEntry().toDirName(target->getResourceType())
+                                               .string(),
+                        nestedResourceName.string());
+
+            // Lookup or create the entry for this name.
+            sp<Entry> entry = getEntry(kAssetPackage16,
+                                       String16(target->getResourceType()),
+                                       String16(nestedResourceName),
+                                       source,
+                                       false,
+                                       &target->getGroupEntry().toParams(),
+                                       true);
+            if (entry == NULL) {
+                return UNKNOWN_ERROR;
+            }
+
+            if (entry->getType() == Entry::TYPE_UNKNOWN) {
+                // The value for this resource has never been set,
+                // meaning we're good!
+                entry->setItem(source, String16(nestedResourcePath));
+                break;
+            }
+
+            // We failed (name already exists), so try with a different name
+            // (increment the suffix).
+        }
+
+        if (bundle->getVerbose()) {
+            source.printf("generating nested resource %s:%s/%s",
+                    mAssets->getPackage().string(), target->getResourceType().string(),
+                    nestedResourceName.string());
+        }
+
+        // Build the attribute reference and assign it to the parent.
+        String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
+                    mAssets->getPackage().string(), target->getResourceType().string(),
+                    nestedResourceName.string()));
+
+        String16 attrNs = buildNamespace(attrPackage);
+        if (parent->getAttribute(attrNs, attrName) != NULL) {
+            SourcePos(parent->getFilename(), parent->getStartLineNumber())
+                    .error("parent of nested resource already defines attribute '%s:%s'",
+                           String8(attrPackage).string(), String8(attrName).string());
+            return UNKNOWN_ERROR;
+        }
+
+        // Add the reference to the inline resource.
+        parent->addAttribute(attrNs, attrName, nestedResourceRef);
+
+        // Remove the <aapt:attr> child element from here.
+        children.removeAt(i);
+        i--;
+
+        // Append all namespace declarations that we've seen on this branch in the XML tree
+        // to this resource.
+        // We do this because the order of namespace declarations and prefix usage is determined
+        // by the developer and we do not want to override any decisions. Be conservative.
+        for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
+            const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
+            sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
+                                                      ns->getNamespaceUri());
+            newNs->addChild(nestedRoot);
+            nestedRoot = newNs;
+        }
+
+        // Schedule compilation of the nested resource.
+        CompileResourceWorkItem workItem;
+        workItem.resPath = nestedResourcePath;
+        workItem.resourceName = String16(nestedResourceName);
+        workItem.xmlRoot = nestedRoot;
+        workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
+                                     target->getResourceType());
+        mWorkQueue.push(workItem);
+    }
+    return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index c4bdf09..4b7b3cd 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@
 enum {
     XML_COMPILE_STRIP_COMMENTS = 1<<0,
     XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
-    XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
-    XML_COMPILE_STRIP_WHITESPACE = 1<<3,
-    XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
-    XML_COMPILE_UTF8 = 1<<5,
+    XML_COMPILE_PARSE_VALUES = 1 << 2,
+    XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+    XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+    XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+    XML_COMPILE_UTF8 = 1<<6,
 
     XML_COMPILE_STANDARD_RESOURCE =
-            XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+            XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
 };
 
@@ -83,6 +84,8 @@
     String16 resourceName;
     String8 resPath;
     sp<AaptFile> file;
+    sp<XMLNode> xmlRoot;
+    bool needsCompiling = true;
 };
 
 class ResourceTable : public ResTable::Accessor
@@ -206,6 +209,12 @@
                              const sp<AaptFile>& file,
                              const sp<XMLNode>& root);
 
+    status_t processBundleFormat(const Bundle* bundle,
+                                 const String16& resourceName,
+                                 const sp<AaptFile>& file,
+                                 const sp<XMLNode>& parent);
+
+
     sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
             const bool isBase);
 
@@ -586,6 +595,11 @@
                       Res_value* outValue);
     int getPublicAttributeSdkLevel(uint32_t attrId) const;
 
+    status_t processBundleFormatImpl(const Bundle* bundle,
+                                     const String16& resourceName,
+                                     const sp<AaptFile>& file,
+                                     const sp<XMLNode>& parent,
+                                     Vector<sp<XMLNode> >* namespaces);
 
     String16 mAssetsPackage;
     PackageType mPackageType;
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index dc08eb8..5b215da 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -693,6 +693,12 @@
     return mChildren;
 }
 
+
+Vector<sp<XMLNode> >& XMLNode::getChildren()
+{
+    return mChildren;
+}
+
 const String8& XMLNode::getFilename() const
 {
     return mFilename;
@@ -717,6 +723,18 @@
     return NULL;
 }
 
+bool XMLNode::removeAttribute(const String16& ns, const String16& name)
+{
+    for (size_t i = 0; i < mAttributes.size(); i++) {
+        const attribute_entry& ae(mAttributes.itemAt(i));
+        if (ae.ns == ns && ae.name == name) {
+            removeAttribute(i);
+            return true;
+        }
+    }
+    return false;
+}
+
 XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
         const String16& name)
 {
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index b9e5cd5..749bf9f 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -55,7 +55,7 @@
     sp<XMLNode> newCData(const String8& filename) {
         return new XMLNode(filename);
     }
-    
+
     enum type {
         TYPE_NAMESPACE,
         TYPE_ELEMENT,
@@ -70,6 +70,7 @@
     const String16& getElementNamespace() const;
     const String16& getElementName() const;
     const Vector<sp<XMLNode> >& getChildren() const;
+    Vector<sp<XMLNode> >& getChildren();
 
     const String8& getFilename() const;
     
@@ -97,6 +98,7 @@
     const Vector<attribute_entry>& getAttributes() const;
 
     const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+    bool removeAttribute(const String16& ns, const String16& name);
     
     attribute_entry* editAttribute(const String16& ns, const String16& name);
 
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 17a658e..90e35d5 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -22,6 +22,7 @@
 #include "compile/IdAssigner.h"
 #include "compile/Png.h"
 #include "compile/XmlIdCollector.h"
+#include "flatten/Archive.h"
 #include "flatten/FileExportWriter.h"
 #include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
@@ -31,6 +32,7 @@
 #include "xml/XmlDom.h"
 #include "xml/XmlPullParser.h"
 
+#include <dirent.h>
 #include <fstream>
 #include <string>
 
@@ -90,7 +92,7 @@
     }
 
     return ResourcePathData{
-            Source{ path },
+            Source(path),
             util::utf8ToUtf16(dirStr),
             util::utf8ToUtf16(name),
             extension.toString(),
@@ -101,25 +103,79 @@
 
 struct CompileOptions {
     std::string outputPath;
+    Maybe<std::string> resDir;
     Maybe<std::u16string> product;
     bool verbose = false;
 };
 
-static std::string buildIntermediateFilename(const std::string outDir,
-                                             const ResourcePathData& data) {
+static std::string buildIntermediateFilename(const ResourcePathData& data) {
     std::stringstream name;
     name << data.resourceDir;
     if (!data.configStr.empty()) {
         name << "-" << data.configStr;
     }
     name << "_" << data.name << "." << data.extension << ".flat";
-    std::string outPath = outDir;
-    file::appendPath(&outPath, name.str());
-    return outPath;
+    return name.str();
+}
+
+static bool isHidden(const StringPiece& filename) {
+    return util::stringStartsWith<char>(filename, ".");
+}
+
+/**
+ * Walks the res directory structure, looking for resource files.
+ */
+static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+                                  std::vector<ResourcePathData>* outPathData) {
+    const std::string& rootDir = options.resDir.value();
+    std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
+    if (!d) {
+        context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+        return false;
+    }
+
+    while (struct dirent* entry = readdir(d.get())) {
+        if (isHidden(entry->d_name)) {
+            continue;
+        }
+
+        std::string prefixPath = rootDir;
+        file::appendPath(&prefixPath, entry->d_name);
+
+        if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+            continue;
+        }
+
+        std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
+        if (!subDir) {
+            context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+            return false;
+        }
+
+        while (struct dirent* leafEntry = readdir(subDir.get())) {
+            if (isHidden(leafEntry->d_name)) {
+                continue;
+            }
+
+            std::string fullPath = prefixPath;
+            file::appendPath(&fullPath, leafEntry->d_name);
+
+            std::string errStr;
+            Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
+            if (!pathData) {
+                context->getDiagnostics()->error(DiagMessage() << errStr);
+                return false;
+            }
+
+            outPathData->push_back(std::move(pathData.value()));
+        }
+    }
+    return true;
 }
 
 static bool compileTable(IAaptContext* context, const CompileOptions& options,
-                         const ResourcePathData& pathData, const std::string& outputPath) {
+                         const ResourcePathData& pathData, IArchiveWriter* writer,
+                         const std::string& outputPath) {
     ResourceTable table;
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
@@ -150,6 +206,7 @@
     // Ensure we have the compilation package at least.
     table.createPackage(context->getCompilationPackage());
 
+    // Assign an ID to any package that has resources.
     for (auto& pkg : table.packages) {
         if (!pkg->id) {
             // If no package ID was set while parsing (public identifiers), auto assign an ID.
@@ -172,23 +229,24 @@
         return false;
     }
 
-    // Build the output filename.
-    std::ofstream fout(outputPath, std::ofstream::binary);
-    if (!fout) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+    if (!writer->startEntry(outputPath, 0)) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
         return false;
     }
 
-    // Write it to disk.
-    if (!util::writeAll(fout, buffer)) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
-        return false;
+    if (writer->writeEntry(buffer)) {
+        if (writer->finishEntry()) {
+            return true;
+        }
     }
-    return true;
+
+    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+    return false;
 }
 
 static bool compileXml(IAaptContext* context, const CompileOptions& options,
-                       const ResourcePathData& pathData, const std::string& outputPath) {
+                       const ResourcePathData& pathData, IArchiveWriter* writer,
+                       const std::string& outputPath) {
 
     std::unique_ptr<xml::XmlResource> xmlRes;
 
@@ -214,7 +272,7 @@
         return false;
     }
 
-    xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
     xmlRes->file.config = pathData.config;
     xmlRes->file.source = pathData.source;
 
@@ -230,25 +288,27 @@
 
     fileExportWriter.finish();
 
-    std::ofstream fout(outputPath, std::ofstream::binary);
-    if (!fout) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+    if (!writer->startEntry(outputPath, 0)) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
         return false;
     }
 
-    // Write it to disk.
-    if (!util::writeAll(fout, buffer)) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
-        return false;
+    if (writer->writeEntry(buffer)) {
+        if (writer->finishEntry()) {
+            return true;
+        }
     }
-    return true;
+
+    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+    return false;
 }
 
 static bool compilePng(IAaptContext* context, const CompileOptions& options,
-                       const ResourcePathData& pathData, const std::string& outputPath) {
+                       const ResourcePathData& pathData, IArchiveWriter* writer,
+                       const std::string& outputPath) {
     BigBuffer buffer(4096);
     ResourceFile resFile;
-    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
     resFile.config = pathData.config;
     resFile.source = pathData.source;
 
@@ -269,24 +329,27 @@
 
     fileExportWriter.finish();
 
-    std::ofstream fout(outputPath, std::ofstream::binary);
-    if (!fout) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+    if (!writer->startEntry(outputPath, 0)) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
         return false;
     }
 
-    if (!util::writeAll(fout, buffer)) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
-        return false;
+    if (writer->writeEntry(buffer)) {
+        if (writer->finishEntry()) {
+            return true;
+        }
     }
-    return true;
+
+    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+    return false;
 }
 
 static bool compileFile(IAaptContext* context, const CompileOptions& options,
-                        const ResourcePathData& pathData, const std::string& outputPath) {
+                        const ResourcePathData& pathData, IArchiveWriter* writer,
+                        const std::string& outputPath) {
     BigBuffer buffer(256);
     ResourceFile resFile;
-    resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+    resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
     resFile.config = pathData.config;
     resFile.source = pathData.source;
 
@@ -299,9 +362,8 @@
         return false;
     }
 
-    std::ofstream fout(outputPath, std::ofstream::binary);
-    if (!fout) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+    if (!writer->startEntry(outputPath, 0)) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
         return false;
     }
 
@@ -309,16 +371,17 @@
     // the buffer the entire file.
     fileExportWriter.getChunkHeader()->size =
             util::hostToDevice32(buffer.size() + f.value().getDataLength());
-    if (!util::writeAll(fout, buffer)) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
-        return false;
+
+    if (writer->writeEntry(buffer)) {
+        if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
+            if (writer->finishEntry()) {
+                return true;
+            }
+        }
     }
 
-    if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
-        context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
-        return false;
-    }
-    return true;
+    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+    return false;
 }
 
 class CompileContext : public IAaptContext {
@@ -359,6 +422,7 @@
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .optionalFlag("--product", "Product type to compile", &product)
+            .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
             .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
     if (!flags.parse("aapt2 compile", args, &std::cerr)) {
         return 1;
@@ -369,19 +433,42 @@
     }
 
     CompileContext context;
+    std::unique_ptr<IArchiveWriter> archiveWriter;
 
     std::vector<ResourcePathData> inputData;
-    inputData.reserve(flags.getArgs().size());
-
-    // Collect data from the path for each input file.
-    for (const std::string& arg : flags.getArgs()) {
-        std::string errorStr;
-        if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
-            inputData.push_back(std::move(pathData.value()));
-        } else {
-            context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+    if (options.resDir) {
+        if (!flags.getArgs().empty()) {
+            // Can't have both files and a resource directory.
+            context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
+            flags.usage("aapt2 compile", &std::cerr);
             return 1;
         }
+
+        if (!loadInputFilesFromDir(&context, options, &inputData)) {
+            return 1;
+        }
+
+        archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+
+    } else {
+        inputData.reserve(flags.getArgs().size());
+
+        // Collect data from the path for each input file.
+        for (const std::string& arg : flags.getArgs()) {
+            std::string errorStr;
+            if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+                inputData.push_back(std::move(pathData.value()));
+            } else {
+                context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+                return 1;
+            }
+        }
+
+        archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
+    }
+
+    if (!archiveWriter) {
+        return false;
     }
 
     bool error = false;
@@ -394,32 +481,34 @@
             // Overwrite the extension.
             pathData.extension = "arsc";
 
-            const std::string outputFilename = buildIntermediateFilename(
-                    options.outputPath, pathData);
-            if (!compileTable(&context, options, pathData, outputFilename)) {
+            const std::string outputFilename = buildIntermediateFilename(pathData);
+            if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
                 error = true;
             }
 
         } else {
-            const std::string outputFilename = buildIntermediateFilename(options.outputPath,
-                                                                         pathData);
+            const std::string outputFilename = buildIntermediateFilename(pathData);
             if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
                 if (*type != ResourceType::kRaw) {
                     if (pathData.extension == "xml") {
-                        if (!compileXml(&context, options, pathData, outputFilename)) {
+                        if (!compileXml(&context, options, pathData, archiveWriter.get(),
+                                        outputFilename)) {
                             error = true;
                         }
                     } else if (pathData.extension == "png" || pathData.extension == "9.png") {
-                        if (!compilePng(&context, options, pathData, outputFilename)) {
+                        if (!compilePng(&context, options, pathData, archiveWriter.get(),
+                                        outputFilename)) {
                             error = true;
                         }
                     } else {
-                        if (!compileFile(&context, options, pathData, outputFilename)) {
+                        if (!compileFile(&context, options, pathData, archiveWriter.get(),
+                                         outputFilename)) {
                             error = true;
                         }
                     }
                 } else {
-                    if (!compileFile(&context, options, pathData, outputFilename)) {
+                    if (!compileFile(&context, options, pathData, archiveWriter.get(),
+                                     outputFilename)) {
                         error = true;
                     }
                 }
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 6db13b8..3a244c0 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -18,7 +18,7 @@
 #include "util/Files.h"
 #include "util/StringPiece.h"
 
-#include <fstream>
+#include <cstdio>
 #include <memory>
 #include <string>
 #include <vector>
@@ -30,70 +30,85 @@
 
 struct DirectoryWriter : public IArchiveWriter {
     std::string mOutDir;
-    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
+    std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
 
-    explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+    bool open(IDiagnostics* diag, const StringPiece& outDir) {
+        mOutDir = outDir.toString();
+        file::FileType type = file::getFileType(mOutDir);
+        if (type == file::FileType::kNonexistant) {
+            diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
+            return false;
+        } else if (type != file::FileType::kDirectory) {
+            diag->error(DiagMessage() << mOutDir << " is not a directory");
+            return false;
+        }
+        return true;
     }
 
-    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
-                             const BigBuffer& buffer) override {
+    bool startEntry(const StringPiece& path, uint32_t flags) override {
+        if (mFile) {
+            return false;
+        }
+
         std::string fullPath = mOutDir;
         file::appendPath(&fullPath, path);
         file::mkdirs(file::getStem(fullPath));
 
-        std::ofstream fout(fullPath, std::ofstream::binary);
-        if (!fout) {
-            return nullptr;
+        mFile = { fopen(fullPath.data(), "wb"), fclose };
+        if (!mFile) {
+            return false;
         }
-
-        if (!util::writeAll(fout, buffer)) {
-            return nullptr;
-        }
-
-        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
-        return mEntries.back().get();
+        return true;
     }
 
-    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
-                             size_t offset, size_t len) override {
-        std::string fullPath = mOutDir;
-        file::appendPath(&fullPath, path);
-        file::mkdirs(file::getStem(fullPath));
-
-        std::ofstream fout(fullPath, std::ofstream::binary);
-        if (!fout) {
-            return nullptr;
+    bool writeEntry(const BigBuffer& buffer) override {
+        if (!mFile) {
+            return false;
         }
 
-        if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
-            return nullptr;
+        for (const BigBuffer::Block& b : buffer) {
+            if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
+                mFile.reset(nullptr);
+                return false;
+            }
         }
-
-        mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
-        return mEntries.back().get();
+        return true;
     }
 
-    virtual ~DirectoryWriter() {
+    bool writeEntry(const void* data, size_t len) override {
+        if (fwrite(data, 1, len, mFile.get()) != len) {
+            mFile.reset(nullptr);
+            return false;
+        }
+        return true;
+    }
 
+    bool finishEntry() override {
+        if (!mFile) {
+            return false;
+        }
+        mFile.reset(nullptr);
+        return true;
     }
 };
 
 struct ZipFileWriter : public IArchiveWriter {
-    FILE* mFile;
+    std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
     std::unique_ptr<ZipWriter> mWriter;
-    std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
 
-    explicit ZipFileWriter(const StringPiece& path) {
-        mFile = fopen(path.data(), "w+b");
-        if (mFile) {
-            mWriter = util::make_unique<ZipWriter>(mFile);
+    bool open(IDiagnostics* diag, const StringPiece& path) {
+        mFile = { fopen(path.data(), "w+b"), fclose };
+        if (!mFile) {
+            diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
+            return false;
         }
+        mWriter = util::make_unique<ZipWriter>(mFile.get());
+        return true;
     }
 
-    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
-                             const BigBuffer& buffer) override {
+    bool startEntry(const StringPiece& path, uint32_t flags) override {
         if (!mWriter) {
-            return nullptr;
+            return false;
         }
 
         size_t zipFlags = 0;
@@ -107,75 +122,63 @@
 
         int32_t result = mWriter->StartEntry(path.data(), zipFlags);
         if (result != 0) {
-            return nullptr;
+            return false;
         }
+        return true;
+    }
 
+    bool writeEntry(const void* data, size_t len) override {
+        int32_t result = mWriter->WriteBytes(data, len);
+        if (result != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    bool writeEntry(const BigBuffer& buffer) override {
         for (const BigBuffer::Block& b : buffer) {
-            result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
+            int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
             if (result != 0) {
-                return nullptr;
+                return false;
             }
         }
-
-        result = mWriter->FinishEntry();
-        if (result != 0) {
-            return nullptr;
-        }
-
-        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
-        return mEntries.back().get();
+        return true;
     }
 
-    ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
-                             size_t offset, size_t len) override {
-        if (!mWriter) {
-            return nullptr;
-        }
-
-        size_t zipFlags = 0;
-        if (flags & ArchiveEntry::kCompress) {
-            zipFlags |= ZipWriter::kCompress;
-        }
-
-        if (flags & ArchiveEntry::kAlign) {
-            zipFlags |= ZipWriter::kAlign32;
-        }
-
-        int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+    bool finishEntry() override {
+        int32_t result = mWriter->FinishEntry();
         if (result != 0) {
-            return nullptr;
+            return false;
         }
-
-        result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
-        if (result != 0) {
-            return nullptr;
-        }
-
-        result = mWriter->FinishEntry();
-        if (result != 0) {
-            return nullptr;
-        }
-
-        mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
-        return mEntries.back().get();
+        return true;
     }
 
     virtual ~ZipFileWriter() {
         if (mWriter) {
             mWriter->Finish();
-            fclose(mFile);
         }
     }
 };
 
 } // namespace
 
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
-    return util::make_unique<DirectoryWriter>(path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+                                                             const StringPiece& path) {
+
+    std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+    if (!writer->open(diag, path)) {
+        return {};
+    }
+    return std::move(writer);
 }
 
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
-    return util::make_unique<ZipFileWriter>(path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+                                                           const StringPiece& path) {
+    std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+    if (!writer->open(diag, path)) {
+        return {};
+    }
+    return std::move(writer);
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index c4ddeb3..6da1d2a 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,6 +17,7 @@
 #ifndef AAPT_FLATTEN_ARCHIVE_H
 #define AAPT_FLATTEN_ARCHIVE_H
 
+#include "Diagnostics.h"
 #include "util/BigBuffer.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
@@ -42,15 +43,17 @@
 struct IArchiveWriter {
     virtual ~IArchiveWriter() = default;
 
-    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
-                                     const BigBuffer& buffer) = 0;
-    virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
-                                     android::FileMap* fileMap, size_t offset, size_t len) = 0;
+    virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+    virtual bool writeEntry(const BigBuffer& buffer) = 0;
+    virtual bool writeEntry(const void* data, size_t len) = 0;
+    virtual bool finishEntry() = 0;
 };
 
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+                                                             const StringPiece& path);
 
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+                                                           const StringPiece& path);
 
 } // namespace aapt
 
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
new file mode 100644
index 0000000..9081c55
--- /dev/null
+++ b/tools/aapt2/io/Data.h
@@ -0,0 +1,85 @@
+/*
+ * 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_IO_DATA_H
+#define AAPT_IO_DATA_H
+
+#include <utils/FileMap.h>
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ */
+class IData {
+public:
+    virtual ~IData() = default;
+
+    virtual const void* data() const = 0;
+    virtual size_t size() const = 0;
+};
+
+/**
+ * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * object.
+ */
+class MmappedData : public IData {
+public:
+    explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
+    }
+
+    const void* data() const override {
+        return mMap.getDataPtr();
+    }
+
+    size_t size() const override {
+        return mMap.getDataLength();
+    }
+
+private:
+    android::FileMap mMap;
+};
+
+/**
+ * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * memory is owned by this object.
+ */
+class MallocData : public IData {
+public:
+    MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
+            mData(std::move(data)), mSize(size) {
+    }
+
+    const void* data() const override {
+        return mData.get();
+    }
+
+    size_t size() const override {
+        return mSize;
+    }
+
+private:
+    std::unique_ptr<const uint8_t[]> mData;
+    size_t mSize;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
new file mode 100644
index 0000000..9fca398
--- /dev/null
+++ b/tools/aapt2/io/File.h
@@ -0,0 +1,72 @@
+/*
+ * 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_IO_FILE_H
+#define AAPT_IO_FILE_H
+
+#include "Source.h"
+#include "io/Data.h"
+
+#include <memory>
+#include <vector>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a file, which could be a real file on the file system, or a file inside
+ * a ZIP archive.
+ */
+class IFile {
+public:
+    virtual ~IFile() = default;
+
+    /**
+     * Open the file and return it as a block of contiguous memory. How this occurs is
+     * implementation dependent. For example, if this is a file on the file system, it may
+     * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
+     * it may need to inflate it to memory, incurring a copy.
+     *
+     * Returns nullptr on failure.
+     */
+    virtual std::unique_ptr<IData> openAsData() = 0;
+
+    /**
+     * Returns the source of this file. This is for presentation to the user and may not be a
+     * valid file system path (for example, it may contain a '@' sign to separate the files within
+     * a ZIP archive from the path to the containing ZIP archive.
+     */
+    virtual const Source& getSource() const = 0;
+};
+
+/**
+ * Interface for a collection of files, all of which share a common source. That source may
+ * simply be the filesystem, or a ZIP archive.
+ */
+class IFileCollection {
+public:
+    virtual ~IFileCollection() = default;
+
+    using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator;
+
+    virtual const_iterator begin() const = 0;
+    virtual const_iterator end() const = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
new file mode 100644
index 0000000..5dbefcc
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.h
@@ -0,0 +1,78 @@
+/*
+ * 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_IO_FILESYSTEM_H
+#define AAPT_IO_FILESYSTEM_H
+
+#include "io/File.h"
+#include "util/Files.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * A regular file from the file system. Uses mmap to open the data.
+ */
+class RegularFile : public IFile {
+public:
+    RegularFile(const Source& source) : mSource(source) {
+    }
+
+    std::unique_ptr<IData> openAsData() override {
+        android::FileMap map;
+        if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
+            return util::make_unique<MmappedData>(std::move(map.value()));
+        }
+        return {};
+    }
+
+    const Source& getSource() const override {
+        return mSource;
+    }
+
+private:
+    Source mSource;
+};
+
+/**
+ * An IFileCollection representing the file system.
+ */
+class FileCollection : public IFileCollection {
+public:
+    /**
+     * Adds a file located at path. Returns the IFile representation of that file.
+     */
+    IFile* insertFile(const StringPiece& path) {
+        mFiles.push_back(util::make_unique<RegularFile>(Source(path)));
+        return mFiles.back().get();
+    }
+
+    const_iterator begin() const override {
+        return mFiles.begin();
+    }
+
+    const_iterator end() const override {
+        return mFiles.end();
+    }
+
+private:
+    std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
new file mode 100644
index 0000000..98afc49
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.h
@@ -0,0 +1,143 @@
+/*
+ * 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_IO_ZIPARCHIVE_H
+#define AAPT_IO_ZIPARCHIVE_H
+
+#include "io/File.h"
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+/**
+ * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ */
+class ZipFile : public IFile {
+public:
+    ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
+            mZipHandle(handle), mZipEntry(entry), mSource(source) {
+    }
+
+    std::unique_ptr<IData> openAsData() override {
+        if (mZipEntry.method == kCompressStored) {
+            int fd = GetFileDescriptor(mZipHandle);
+
+            android::FileMap fileMap;
+            bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
+                                         mZipEntry.uncompressed_length, true);
+            if (!result) {
+                return {};
+            }
+            return util::make_unique<MmappedData>(std::move(fileMap));
+
+        } else {
+            std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
+                    new uint8_t[mZipEntry.uncompressed_length]);
+            int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
+                                             static_cast<uint32_t>(mZipEntry.uncompressed_length));
+            if (result != 0) {
+                return {};
+            }
+            return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+        }
+    }
+
+    const Source& getSource() const override {
+        return mSource;
+    }
+
+private:
+    ZipArchiveHandle mZipHandle;
+    ZipEntry mZipEntry;
+    Source mSource;
+};
+
+/**
+ * An IFileCollection that represents a ZIP archive and the entries within it.
+ */
+class ZipFileCollection : public IFileCollection {
+public:
+    static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+                                                     std::string* outError) {
+        std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
+                new ZipFileCollection());
+
+        int32_t result = OpenArchive(path.data(), &collection->mHandle);
+        if (result != 0) {
+            if (outError) *outError = ErrorCodeString(result);
+            return {};
+        }
+
+        ZipString suffix(".flat");
+        void* cookie = nullptr;
+        result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix);
+        if (result != 0) {
+            if (outError) *outError = ErrorCodeString(result);
+            return {};
+        }
+
+        using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+        IterationEnder iterationEnder(cookie, EndIteration);
+
+        ZipString zipEntryName;
+        ZipEntry zipData;
+        while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
+            std::string nestedPath = path.toString();
+            nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name),
+                                            zipEntryName.name_length);
+            collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle,
+                                                                    zipData,
+                                                                    Source(nestedPath)));
+        }
+
+        if (result != -1) {
+            if (outError) *outError = ErrorCodeString(result);
+            return {};
+        }
+        return collection;
+    }
+
+    const_iterator begin() const override {
+        return mFiles.begin();
+    }
+
+    const_iterator end() const override {
+        return mFiles.end();
+    }
+
+    ~ZipFileCollection() override {
+        if (mHandle) {
+            CloseArchive(mHandle);
+        }
+    }
+
+private:
+    ZipFileCollection() : mHandle(nullptr) {
+    }
+
+    ZipArchiveHandle mHandle;
+    std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 9850ae5..33d9272 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -22,6 +22,8 @@
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
+#include "io/FileSystem.h"
+#include "io/ZipArchive.h"
 #include "java/JavaClassGenerator.h"
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
@@ -39,7 +41,6 @@
 
 #include <fstream>
 #include <sys/stat.h>
-#include <utils/FileMap.h>
 #include <vector>
 
 namespace aapt {
@@ -92,7 +93,15 @@
 class LinkCommand {
 public:
     LinkCommand(const LinkOptions& options) :
-            mOptions(options), mContext(), mFinalTable() {
+            mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+        std::unique_ptr<io::FileCollection> fileCollection =
+                util::make_unique<io::FileCollection>();
+
+        // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
+        mFileCollection = fileCollection.get();
+
+        // Move it to the collection.
+        mCollections.push_back(std::move(fileCollection));
     }
 
     std::string buildResourceFileName(const ResourceFile& resFile) {
@@ -136,20 +145,9 @@
         return builder.build();
     }
 
-    /**
-     * Loads the resource table (not inside an apk) at the given path.
-     */
-    std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
-        std::string errorStr;
-        Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
-        if (!map) {
-            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
-            return {};
-        }
-
+    std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
         std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
-        BinaryResourceParser parser(&mContext, table.get(), Source(input),
-                                    map.value().getDataPtr(), map.value().getDataLength());
+        BinaryResourceParser parser(&mContext, table.get(), source, data, len);
         if (!parser.parse()) {
             return {};
         }
@@ -159,90 +157,79 @@
     /**
      * Inflates an XML file from the source path.
      */
-    std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
+    static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
         std::ifstream fin(path, std::ifstream::binary);
         if (!fin) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
+            diag->error(DiagMessage(path) << strerror(errno));
             return {};
         }
 
-        return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
+        return xml::inflate(&fin, diag, Source(path));
     }
 
-    /**
-     * Inflates a binary XML file from the source path.
-     */
-    std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
-        // Read header for symbol info and export info.
+    static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
+            const Source& source,
+            const void* data, size_t len,
+            IDiagnostics* diag) {
         std::string errorStr;
-        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
-        if (!maybeF) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
-            return {};
-        }
-
-        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
-                                              maybeF.value().getDataLength(), &errorStr);
+        ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
         if (offset < 0) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            diag->error(DiagMessage(source) << errorStr);
             return {};
         }
 
         std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
-                (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
-                maybeF.value().getDataLength() - offset,
-                mContext.getDiagnostics(), Source(path));
+                reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
+                len - static_cast<size_t>(offset),
+                diag,
+                source);
         if (!xmlRes) {
             return {};
         }
         return xmlRes;
     }
 
-    Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
-        // Read header for symbol info and export info.
+    static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+                                                              const void* data, size_t len,
+                                                              IDiagnostics* diag) {
+        std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
         std::string errorStr;
-        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
-        if (!maybeF) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
-            return {};
-        }
-
-        ResourceFile resFile;
-        ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
-                                                maybeF.value().getDataLength(),
-                                                &resFile, &errorStr);
+        ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
         if (offset < 0) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            diag->error(DiagMessage(source) << errorStr);
             return {};
         }
-        return std::move(resFile);
+        return resFile;
     }
 
-    bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+    bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
                            IArchiveWriter* writer) {
+        std::unique_ptr<io::IData> data = file->openAsData();
+        if (!data) {
+            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+                                             << "failed to open file");
+            return false;
+        }
+
         std::string errorStr;
-        Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
-        if (!maybeF) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
-            return false;
-        }
-
-        ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
-                                              maybeF.value().getDataLength(),
-                                              &errorStr);
+        ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
         if (offset < 0) {
-            mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+            mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
             return false;
         }
 
-        ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
-                                                 offset, maybeF.value().getDataLength() - offset);
-        if (!entry) {
-            mContext.getDiagnostics()->error(
-                    DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
-            return false;
+        if (writer->startEntry(outPath, flags)) {
+            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
+                                   data->size() - static_cast<size_t>(offset))) {
+                if (writer->finishEntry()) {
+                    return true;
+                }
+            }
         }
-        return true;
+
+        mContext.getDiagnostics()->error(
+                DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+        return false;
     }
 
     Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
@@ -285,9 +272,9 @@
 
     std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
         if (mOptions.outputToDirectory) {
-            return createDirectoryArchiveWriter(mOptions.outputPath);
+            return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
         } else {
-            return createZipFileArchiveWriter(mOptions.outputPath);
+            return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
         }
     }
 
@@ -300,13 +287,17 @@
             return false;
         }
 
-        ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
-        if (!entry) {
-            mContext.getDiagnostics()->error(
-                    DiagMessage() << "failed to write resources.arsc to archive");
-            return false;
+        if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+            if (writer->writeEntry(buffer)) {
+                if (writer->finishEntry()) {
+                    return true;
+                }
+            }
         }
-        return true;
+
+        mContext.getDiagnostics()->error(
+                DiagMessage() << "failed to write resources.arsc to archive");
+        return false;
     }
 
     bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
@@ -320,13 +311,17 @@
             return false;
         }
 
-        ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
-        if (!entry) {
-            mContext.getDiagnostics()->error(
-                    DiagMessage() << "failed to write " << path << " to archive");
-            return false;
+
+        if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+            if (writer->writeEntry(buffer)) {
+                if (writer->finishEntry()) {
+                    return true;
+                }
+            }
         }
-        return true;
+        mContext.getDiagnostics()->error(
+                DiagMessage() << "failed to write " << path << " to archive");
+        return false;
     }
 
     bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
@@ -412,34 +407,44 @@
         return true;
     }
 
-    bool mergeResourceTable(const std::string& input, bool override) {
+    bool mergeResourceTable(io::IFile* file, bool override) {
         if (mOptions.verbose) {
-            mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+            mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
         }
 
-        std::unique_ptr<ResourceTable> table = loadTable(input);
+        std::unique_ptr<io::IData> data = file->openAsData();
+        if (!data) {
+            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+                                             << "failed to open file");
+            return false;
+        }
+
+        std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
+                                                         data->size());
         if (!table) {
             return false;
         }
 
-        if (!mTableMerger->merge(Source(input), table.get(), override)) {
+        if (!mTableMerger->merge(file->getSource(), table.get(), override)) {
             return false;
         }
         return true;
     }
 
-    bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
-        if (file.name.package.empty()) {
-            file.name.package = mContext.getCompilationPackage().toString();
+    bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) {
+        // Apply the package name used for this compilation phase if none was specified.
+        if (fileDesc->name.package.empty()) {
+            fileDesc->name.package = mContext.getCompilationPackage().toString();
         }
 
-        ResourceNameRef resName = file.name;
-
-        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+        // Mangle the name if necessary.
+        ResourceNameRef resName = fileDesc->name;
+        Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name);
         if (mangledName) {
             resName = mangledName.value();
         }
 
+        // If we are overriding resources, we supply a custom resolver function.
         std::function<int(Value*,Value*)> resolver;
         if (override) {
             resolver = [](Value* a, Value* b) -> int {
@@ -456,14 +461,14 @@
         }
 
         // Add this file to the table.
-        if (!mFinalTable.addFileReference(resName, file.config, file.source,
-                                          util::utf8ToUtf16(buildResourceFileName(file)),
+        if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source,
+                                          util::utf8ToUtf16(buildResourceFileName(*fileDesc)),
                                           resolver, mContext.getDiagnostics())) {
             return false;
         }
 
         // Add the exports of this file to the table.
-        for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+        for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
             if (exportedSymbol.name.package.empty()) {
                 exportedSymbol.name.package = mContext.getCompilationPackage().toString();
             }
@@ -477,32 +482,78 @@
             }
 
             std::unique_ptr<Id> id = util::make_unique<Id>();
-            id->setSource(file.source.withLine(exportedSymbol.line));
+            id->setSource(fileDesc->source.withLine(exportedSymbol.line));
             bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
-                    mContext.getDiagnostics());
+                                                              mContext.getDiagnostics());
             if (!result) {
                 return false;
             }
         }
 
-        mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
+        // Now add this file for later processing. Once the table is assigned IDs, we can compile
+        // this file.
+        mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file });
         return true;
     }
 
-    bool processFile(const std::string& input, bool override) {
-        if (util::stringEndsWith<char>(input, ".apk")) {
-            return mergeStaticLibrary(input);
-        } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
-            return mergeResourceTable(input, override);
-        } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
-            return mergeCompiledFile(input, std::move(maybeF.value()), override);
+    /**
+     * Creates an io::IFileCollection from the ZIP archive and processes the files within.
+     */
+    bool mergeArchive(const std::string& input, bool override) {
+        std::string errorStr;
+        std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
+                input, &errorStr);
+        if (!collection) {
+            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+            return false;
+        }
+
+        bool error = false;
+        for (const std::unique_ptr<io::IFile>& file : *collection) {
+            if (!processFile(file.get(), override)) {
+                error = true;
+            }
+        }
+
+        // Make sure to move the collection into the set of IFileCollections.
+        mCollections.push_back(std::move(collection));
+        return !error;
+    }
+
+    bool processFile(const std::string& path, bool override) {
+        if (util::stringEndsWith<char>(path, ".flata")) {
+            return mergeArchive(path, override);
+        }
+
+        io::IFile* file = mFileCollection->insertFile(path);
+        return processFile(file, override);
+    }
+
+    bool processFile(io::IFile* file, bool override) {
+        const Source& src = file->getSource();
+        if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
+            return mergeResourceTable(file, override);
+        } else {
+            // Try opening the file and looking for an Export header.
+            std::unique_ptr<io::IData> data = file->openAsData();
+            if (!data) {
+                mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+                return false;
+            }
+
+            std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
+                    src, data->data(), data->size(), mContext.getDiagnostics());
+            if (resourceFile) {
+                return mergeCompiledFile(file, std::move(resourceFile), override);
+            }
         }
         return false;
     }
 
     int run(const std::vector<std::string>& inputFiles) {
         // Load the AndroidManifest.xml
-        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
+                                                                mContext.getDiagnostics());
         if (!manifestXml) {
             return 1;
         }
@@ -648,20 +699,30 @@
         }
 
         for (const FileToProcess& file : mFilesToProcess) {
-            if (file.file.name.type != ResourceType::kRaw &&
-                    util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
+            const StringPiece path = file.file->getSource().path;
+
+            if (file.fileExport->name.type != ResourceType::kRaw &&
+                    util::stringEndsWith<char>(path, ".xml.flat")) {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage()
-                                                    << "linking " << file.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
+                }
+
+                std::unique_ptr<io::IData> data = file.file->openAsData();
+                if (!data) {
+                    mContext.getDiagnostics()->error(DiagMessage(file.file->getSource())
+                                                     << "failed to open file");
+                    return 1;
                 }
 
                 std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
-                        file.source.path);
+                        file.file->getSource(), data->data(), data->size(),
+                        mContext.getDiagnostics());
                 if (!xmlRes) {
                     return 1;
                 }
 
-                xmlRes->file = std::move(file.file);
+                // Move the file description over.
+                xmlRes->file = std::move(*file.fileExport);
 
                 XmlReferenceLinker xmlLinker;
                 if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -689,12 +750,13 @@
                                                                     xmlRes->file.config,
                                                                     sdkLevel)) {
                                 xmlRes->file.config.sdkVersion = sdkLevel;
-                                if (!mFinalTable.addFileReference(xmlRes->file.name,
-                                                                  xmlRes->file.config,
-                                                                  xmlRes->file.source,
-                                                                  util::utf8ToUtf16(
-                                                                     buildResourceFileName(xmlRes->file)),
-                                                             mContext.getDiagnostics())) {
+                                bool added = mFinalTable.addFileReference(
+                                        xmlRes->file.name,
+                                        xmlRes->file.config,
+                                        xmlRes->file.source,
+                                        util::utf8ToUtf16(buildResourceFileName(xmlRes->file)),
+                                        mContext.getDiagnostics());
+                                if (!added) {
                                     error = true;
                                     continue;
                                 }
@@ -712,11 +774,10 @@
                 }
             } else {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "copying "
-                                                    << file.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
                 }
 
-                if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
+                if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0,
                                        archiveWriter.get())) {
                     error = true;
                 }
@@ -802,14 +863,18 @@
     ResourceTable mFinalTable;
     std::unique_ptr<TableMerger> mTableMerger;
 
+    io::FileCollection* mFileCollection;
+    std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
     struct FileToProcess {
-        ResourceFile file;
-        Source source;
+        std::unique_ptr<ResourceFile> fileExport;
+        io::IFile* file;
     };
 
     struct FileToProcessComparator {
         bool operator()(const FileToProcess& a, const FileToProcess& b) {
-            return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
+            return std::tie(a.fileExport->name, a.fileExport->config) <
+                    std::tie(b.fileExport->name, b.fileExport->config);
         }
     };
 
diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
deleted file mode 100644
index 78aedc5..0000000
--- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
+++ /dev/null
@@ -1,52 +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 android.animation;
-
-/**
- * A fake implementation of Animator which doesn't do anything.
- */
-public class FakeAnimator extends Animator {
-    @Override
-    public long getStartDelay() {
-        return 0;
-    }
-
-    @Override
-    public void setStartDelay(long startDelay) {
-
-    }
-
-    @Override
-    public Animator setDuration(long duration) {
-        return this;
-    }
-
-    @Override
-    public long getDuration() {
-        return 0;
-    }
-
-    @Override
-    public void setInterpolator(TimeInterpolator value) {
-
-    }
-
-    @Override
-    public boolean isRunning() {
-        return false;
-    }
-}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
index 4603b63..54021c9 100644
--- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -16,9 +16,16 @@
 
 package android.animation;
 
+import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Delegate implementing the native methods of android.animation.PropertyValuesHolder
  *
@@ -29,81 +36,161 @@
  * around to map int to instance of the delegate.
  *
  * The main goal of this class' methods are to provide a native way to access setters and getters
- * on some object. In this case we want to default to using Java reflection instead so the native
- * methods do nothing.
+ * on some object. We override these methods to use reflection since the original reflection
+ * implementation of the PropertyValuesHolder won't be able to access protected methods.
  *
  */
-/*package*/ class PropertyValuesHolder_Delegate {
+/*package*/
+@SuppressWarnings("unused")
+class PropertyValuesHolder_Delegate {
+    // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
+    // We try several different types when searching for appropriate setter/getter functions.
+    // The caller may have supplied values in a type that does not match the setter/getter
+    // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+    // Also, the use of generics in constructors means that we end up with the Object versions
+    // of primitive types (Float vs. float). But most likely, the setter/getter functions
+    // will take primitive types instead.
+    // So we supply an ordered array of other types to try before giving up.
+    private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+            Double.class, Integer.class};
+    private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+            Float.class, Double.class};
+
+    private static final Object sMethodIndexLock = new Object();
+    private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
+    private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
+    private static long sNextId = 1;
+
+    private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
+            int nArgs) {
+        // Encode the number of arguments in the method name
+        String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs);
+        synchronized (sMethodIndexLock) {
+            Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
+
+            if (methodId != null) {
+                // The method was already registered
+                return methodId;
+            }
+
+            Class[] args = new Class[nArgs];
+            Method method = null;
+            for (Class typeVariant : types) {
+                for (int i = 0; i < nArgs; i++) {
+                    args[i] = typeVariant;
+                }
+                try {
+                    method = targetClass.getDeclaredMethod(methodName, args);
+                } catch (NoSuchMethodException ignore) {
+                }
+            }
+
+            if (method != null) {
+                methodId = sNextId++;
+                ID_TO_METHOD.put(methodId, method);
+                METHOD_NAME_TO_ID.put(methodIndexName, methodId);
+
+                return methodId;
+            }
+        }
+
+        // Method not found
+        return 0;
+    }
+
+    private static void callMethod(Object target, long methodID, Object... args) {
+        Method method = ID_TO_METHOD.get(methodID);
+        assert method != null;
+
+        try {
+            method.setAccessible(true);
+            method.invoke(target, args);
+        } catch (IllegalAccessException e) {
+            Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+        } catch (InvocationTargetException e) {
+            Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+        }
+    }
 
     @LayoutlibDelegate
     /*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
-        // return 0 to force PropertyValuesHolder to use Java reflection.
-        return 0;
+        return nGetMultipleIntMethod(targetClass, methodName, 1);
     }
 
     @LayoutlibDelegate
     /*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
-        // return 0 to force PropertyValuesHolder to use Java reflection.
-        return 0;
+        return nGetMultipleFloatMethod(targetClass, methodName, 1);
     }
 
     @LayoutlibDelegate
     /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
             int numParams) {
-        // TODO: return the right thing.
-        return 0;
+        return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
     }
 
     @LayoutlibDelegate
     /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
             int numParams) {
-        // TODO: return the right thing.
-        return 0;
+        return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
-        // do nothing
+        callMethod(target, methodID, arg);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
-        // do nothing
+        callMethod(target, methodID, arg);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
             int arg2) {
-        // do nothing
+        callMethod(target, methodID, arg1, arg2);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
             int arg2, int arg3, int arg4) {
-        // do nothing
+        callMethod(target, methodID, arg1, arg2, arg3, arg4);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
             int[] args) {
-        // do nothing
+        assert args != null;
+
+        // Box parameters
+        Object[] params = new Object[args.length];
+        for (int i = 0; i < args.length; i++) {
+            params[i] = args;
+        }
+        callMethod(target, methodID, params);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
             float arg2) {
-        // do nothing
+        callMethod(target, methodID, arg1, arg2);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
             float arg2, float arg3, float arg4) {
-        // do nothing
+        callMethod(target, methodID, arg1, arg2, arg3, arg4);
     }
 
     @LayoutlibDelegate
     /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
             float[] args) {
-        // do nothing
+        assert args != null;
+
+        // Box parameters
+        Object[] params = new Object[args.length];
+        for (int i = 0; i < args.length; i++) {
+            params[i] = args;
+        }
+        callMethod(target, methodID, params);
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
index dd2978f..3c71233 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -44,7 +44,7 @@
 
     // ---- delegate data ----
     // This governs how accurate the approximation of the Path is.
-    private static final float PRECISION = 0.002f;
+    private static final float PRECISION = 0.0002f;
 
     /**
      * Array containing the path points components. There are three components for each point:
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
index 5f0d98b..9677aaf 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -18,6 +18,7 @@
 
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
 
 /**
  * Delegate implementing the native methods of android.os.SystemClock
@@ -30,9 +31,6 @@
  *
  */
 public class SystemClock_Delegate {
-    private static long sBootTime = System.currentTimeMillis();
-    private static long sBootTimeNano = System.nanoTime();
-
     /**
      * Returns milliseconds since boot, not counting time spent in deep sleep.
      * <b>Note:</b> This value may get reset occasionally (before it would
@@ -42,7 +40,7 @@
      */
     @LayoutlibDelegate
     /*package*/ static long uptimeMillis() {
-        return System.currentTimeMillis() - sBootTime;
+        return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
     }
 
     /**
@@ -52,7 +50,7 @@
      */
     @LayoutlibDelegate
     /*package*/ static long elapsedRealtime() {
-        return System.currentTimeMillis() - sBootTime;
+        return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
     }
 
     /**
@@ -62,7 +60,7 @@
      */
     @LayoutlibDelegate
     /*package*/ static long elapsedRealtimeNanos() {
-        return System.nanoTime() - sBootTimeNano;
+        return System_Delegate.nanoTime() - System_Delegate.bootTime();
     }
 
     /**
@@ -72,7 +70,7 @@
      */
     @LayoutlibDelegate
     /*package*/ static long currentThreadTimeMillis() {
-        return System.currentTimeMillis();
+        return System_Delegate.currentTimeMillis();
     }
 
     /**
@@ -84,7 +82,7 @@
      */
     @LayoutlibDelegate
     /*package*/ static long currentThreadTimeMicro() {
-        return System.currentTimeMillis() * 1000;
+        return System_Delegate.currentTimeMillis() * 1000;
     }
 
     /**
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index f75ee50..01af669 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -17,6 +17,8 @@
 
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import java.util.concurrent.atomic.AtomicReference;
+
 /**
  * Delegate used to provide new implementation of a select few methods of {@link Choreographer}
  *
@@ -25,9 +27,41 @@
  *
  */
 public class Choreographer_Delegate {
+    static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+    @LayoutlibDelegate
+    public static Choreographer getInstance() {
+        if (mInstance.get() == null) {
+            mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+        }
+
+        return mInstance.get();
+    }
 
     @LayoutlibDelegate
     public static float getRefreshRate() {
         return 60.f;
     }
+
+    @LayoutlibDelegate
+    static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+        // do nothing
+    }
+
+    public static void doFrame(long frameTimeNanos) {
+        Choreographer thisChoreographer = Choreographer.getInstance();
+
+        thisChoreographer.mLastFrameTimeNanos = frameTimeNanos;
+
+        thisChoreographer.mFrameInfo.markInputHandlingStart();
+        thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+        thisChoreographer.mFrameInfo.markAnimationsStart();
+        thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+        thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+        thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+        thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6951ede..eea254b 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -541,4 +541,8 @@
     @Override
     public void endProlongedAnimations() {
     }
+
+    @Override
+    public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+    }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 48ca7d8..683c4aa 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -183,7 +183,7 @@
      */
     private static LayoutLog sCurrentLog = sDefaultLog;
 
-    private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER;
+    private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER;
 
     @Override
     public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index feb2590..2ac212c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -23,6 +23,7 @@
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.ViewInfo;
 import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.tools.layoutlib.java.System_Delegate;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -191,6 +192,21 @@
     }
 
     @Override
+    public void setSystemTimeNanos(long nanos) {
+        System_Delegate.setNanosTime(nanos);
+    }
+
+    @Override
+    public void setSystemBootTimeNanos(long nanos) {
+        System_Delegate.setBootTimeNanos(nanos);
+    }
+
+    @Override
+    public void setElapsedFrameTimeNanos(long nanos) {
+        mSession.setElapsedFrameTimeNanos(nanos);
+    }
+
+    @Override
     public void dispose() {
     }
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 0fcfa78..ff15f3b 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1438,6 +1438,14 @@
     }
 
     @Override
+    public SharedPreferences getSharedPreferences(File arg0, int arg1) {
+        if (mSharedPreferences == null) {
+            mSharedPreferences = new BridgeSharedPreferences();
+        }
+        return mSharedPreferences;
+    }
+
+    @Override
     public Drawable getWallpaper() {
         // pass
         return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 0ffa357..ec50cfe 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -46,6 +46,7 @@
 import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
 import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
 import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.java.System_Delegate;
 import com.android.util.Pair;
 
 import android.animation.AnimationThread;
@@ -62,6 +63,7 @@
 import android.preference.Preference_Delegate;
 import android.view.AttachInfo_Accessor;
 import android.view.BridgeInflater;
+import android.view.Choreographer_Delegate;
 import android.view.IWindowManager;
 import android.view.IWindowManagerImpl;
 import android.view.Surface;
@@ -120,6 +122,10 @@
     private int mMeasuredScreenWidth = -1;
     private int mMeasuredScreenHeight = -1;
     private boolean mIsAlphaChannelImage;
+    /** If >= 0, a frame will be executed */
+    private long mElapsedFrameTimeNanos = -1;
+    /** True if one frame has been already executed to start the animations */
+    private boolean mFirstFrameExecuted = false;
 
     // information being returned through the API
     private BufferedImage mImage;
@@ -252,6 +258,14 @@
     }
 
     /**
+     * Sets the time for which the next frame will be selected. The time is the elapsed time from
+     * the current system nanos time. You
+     */
+    public void setElapsedFrameTimeNanos(long nanos) {
+        mElapsedFrameTimeNanos = nanos;
+    }
+
+    /**
      * Renders the scene.
      * <p>
      * {@link #acquire(long)} must have been called before this.
@@ -428,6 +442,16 @@
                     gc.dispose();
                 }
 
+                if (mElapsedFrameTimeNanos >= 0) {
+                    long initialTime = System_Delegate.nanoTime();
+                    if (!mFirstFrameExecuted) {
+                        // The first frame will initialize the animations
+                        Choreographer_Delegate.doFrame(initialTime);
+                        mFirstFrameExecuted = true;
+                    }
+                    // Second frame will move the animations
+                    Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
+                }
                 mViewRoot.draw(mCanvas);
             }
 
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
new file mode 100644
index 0000000..9f26627
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
new file mode 100644
index 0000000..89009be
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
new file mode 100644
index 0000000..70d7396
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:padding="16dp"
+              android:orientation="horizontal"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent">
+
+    <ProgressBar
+             android:layout_height="fill_parent"
+             android:layout_width="fill_parent" />
+
+</LinearLayout>
+
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 9ebeebd..2dca07c 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -48,6 +48,8 @@
 import java.net.URL;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 import static org.junit.Assert.fail;
 
@@ -348,16 +350,46 @@
         renderAndVerify(params, "expand_horz_layout.png");
     }
 
+    /** Test expand_layout.xml */
+    @Test
+    public void testVectorAnimation() throws ClassNotFoundException {
+        // Create the layout pull parser.
+        LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+                "indeterminate_progressbar.xml");
+        // Create LayoutLibCallback.
+        LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+        layoutLibCallback.initResources();
+
+        SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+
+        renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+        parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+                "indeterminate_progressbar.xml");
+        params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+                layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+                RenderingMode.V_SCROLL, 22);
+        renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+    }
+
     /**
      * Create a new rendering session and test that rendering given layout on nexus 5
      * doesn't throw any exceptions and matches the provided image.
+     * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time
+     * indicates how far in the future is.
      */
-    private void renderAndVerify(SessionParams params, String goldenFileName)
+    private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
             throws ClassNotFoundException {
         // TODO: Set up action bar handler properly to test menu rendering.
         // Create session params.
         RenderSession session = sBridge.createSession(params);
 
+        if (frameTimeNanos != -1) {
+            session.setElapsedFrameTimeNanos(frameTimeNanos);
+        }
+
         if (!session.getResult().isSuccess()) {
             getLogger().error(session.getResult().getException(),
                     session.getResult().getErrorMessage());
@@ -380,6 +412,15 @@
      * Create a new rendering session and test that rendering given layout on nexus 5
      * doesn't throw any exceptions and matches the provided image.
      */
+    private void renderAndVerify(SessionParams params, String goldenFileName)
+            throws ClassNotFoundException {
+        renderAndVerify(params, goldenFileName, -1);
+    }
+
+    /**
+     * Create a new rendering session and test that rendering given layout on nexus 5
+     * doesn't throw any exceptions and matches the provided image.
+     */
     private void renderAndVerify(String layoutFileName, String goldenFileName)
             throws ClassNotFoundException {
         // Create the layout pull parser.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e480ead..22743c2 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -169,7 +169,9 @@
         "android.text.format.DateFormat#is24HourFormat",
         "android.text.Hyphenator#getSystemHyphenatorLocation",
         "android.util.Xml#newPullParser",
+        "android.view.Choreographer#getInstance",
         "android.view.Choreographer#getRefreshRate",
+        "android.view.Choreographer#scheduleVsyncLocked",
         "android.view.Display#updateDisplayInfoLocked",
         "android.view.Display#getWindowManager",
         "android.view.LayoutInflater#rInflate",
@@ -290,6 +292,13 @@
             "org.kxml2.io.KXmlParser"
         };
 
+    private final static String[] PROMOTED_FIELDS = new String[] {
+        "android.view.Choreographer#mLastFrameTimeNanos",
+        "android.widget.SimpleMonthView#mTitle",
+        "android.widget.SimpleMonthView#mCalendar",
+        "android.widget.SimpleMonthView#mDayOfWeekLabelCalendar"
+    };
+
     /**
      * List of classes for which the methods returning them should be deleted.
      * The array contains a list of null terminated section starting with the name of the class
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 0b85c48..5e47261 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -134,7 +134,33 @@
             }
         });
 
-        // Case 5: java.util.LinkedHashMap.eldest()
+        // Case 5: java.lang.System time calls
+        METHOD_REPLACERS.add(new MethodReplacer() {
+            @Override
+            public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+                return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
+            }
+
+            @Override
+            public void replace(MethodInformation mi) {
+                mi.name = "nanoTime";
+                mi.owner = Type.getInternalName(System_Delegate.class);
+            }
+        });
+        METHOD_REPLACERS.add(new MethodReplacer() {
+            @Override
+            public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+                return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
+            }
+
+            @Override
+            public void replace(MethodInformation mi) {
+                mi.name = "currentTimeMillis";
+                mi.owner = Type.getInternalName(System_Delegate.class);
+            }
+        });
+
+        // Case 6: java.util.LinkedHashMap.eldest()
         METHOD_REPLACERS.add(new MethodReplacer() {
 
             private final String VOID_TO_MAP_ENTRY =
@@ -157,7 +183,7 @@
             }
         });
 
-        // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+        // Case 7: android.content.Context.getClassLoader() in LayoutInflater
         METHOD_REPLACERS.add(new MethodReplacer() {
             // When LayoutInflater asks for a class loader, we must return the class loader that
             // cannot return app's custom views/classes. This is so that in case of any failure
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
index 613c8d9..be93744 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
@@ -18,12 +18,22 @@
 
 import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
 
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
 /**
  * Provides dummy implementation of methods that don't exist on the host VM.
+ * This also providers a time control that allows to set a specific system time.
  *
  * @see ReplaceMethodCallsAdapter
  */
+@SuppressWarnings("unused")
 public class System_Delegate {
+    // Current system time
+    private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime());
+    // Time that the system booted up in nanos
+    private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime());
+
     public static void log(String message) {
         // ignore.
     }
@@ -31,4 +41,28 @@
     public static void log(String message, Throwable th) {
         // ignore.
     }
+
+    public static void setNanosTime(long nanos) {
+        mNanosTime.set(nanos);
+    }
+
+    public static void setBootTimeNanos(long nanos) {
+        mBootNanosTime.set(nanos);
+    }
+
+    public static long nanoTime() {
+        return mNanosTime.get();
+    }
+
+    public static long currentTimeMillis() {
+        return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get());
+    }
+
+    public static long bootTime() {
+        return mBootNanosTime.get();
+    }
+
+    public static long bootTimeMillis() {
+        return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get());
+    }
 }