Merge "Fix cts ApplicationTest#testApplication failing."
diff --git a/api/current.txt b/api/current.txt
index 73962f8..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";
@@ -36200,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 c4d99b8..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";
@@ -38515,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 73962f8..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";
@@ -36200,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/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/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/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 2f385c1..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;
@@ -9708,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/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/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/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/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/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 82192fe..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>
@@ -258,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>
@@ -276,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>
@@ -296,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>
@@ -348,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/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/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/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 5616018..551f067 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -470,6 +470,25 @@
         if (event.dropTarget instanceof TaskStack.DockState) {
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
 
+            // 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);
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 c55f383..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;
 
@@ -111,6 +112,7 @@
     /**** Events ****/
 
     public final void onBusEvent(DragStartEvent event) {
+        SystemServicesProxy ssp = Recents.getSystemServices();
         mRv.getParent().requestDisallowInterceptTouchEvent(true);
         mDragging = true;
         mDragTask = event.task;
@@ -127,7 +129,7 @@
         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) {
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 c02eaf8..0395e99 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1559,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();
         }
     }
 
@@ -1568,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();
         }
     }
 
@@ -1580,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 fb84a22..f2c89e6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -395,6 +395,7 @@
                 .translationY(ctx.offscreenTranslationY)
                 .setStartDelay(0)
                 .setUpdateListener(null)
+                .setListener(null)
                 .setInterpolator(mFastOutLinearInInterpolator)
                 .setDuration(taskViewExitToHomeDuration)
                 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
@@ -431,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) {
@@ -442,6 +444,7 @@
                     .translationY(getTranslationY() + taskViewAffiliateGroupEnterOffset)
                     .setStartDelay(0)
                     .setUpdateListener(null)
+                    .setListener(null)
                     .setInterpolator(mFastOutLinearInInterpolator)
                     .setDuration(taskViewExitToAppDuration)
                     .start();
@@ -463,6 +466,7 @@
             .alpha(0f)
             .setStartDelay(delay)
             .setUpdateListener(null)
+            .setListener(null)
             .setInterpolator(mFastOutSlowInInterpolator)
             .setDuration(taskViewRemoveAnimDuration)
             .withEndAction(new Runnable() {
@@ -670,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;
     }
@@ -724,7 +720,8 @@
 
     @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);
 
@@ -734,6 +731,8 @@
                     .scaleX(finalScale)
                     .scaleY(finalScale)
                     .setDuration(175)
+                    .setUpdateListener(null)
+                    .setListener(null)
                     .setInterpolator(mFastOutSlowInInterpolator)
                     .start();
 
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/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/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/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/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 10f2ac4..f47459f 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();
             }
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/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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 395962c..dab195f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -150,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;
@@ -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");
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/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/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/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());
+    }
 }