Merge "API for overlaying app content over decor caption in freeform windows."
diff --git a/Android.mk b/Android.mk
index 71bba0f..cc0749c5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -281,6 +281,7 @@
 	core/java/com/android/internal/app/IAppOpsService.aidl \
 	core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
 	core/java/com/android/internal/app/IBatteryStats.aidl \
+	core/java/com/android/internal/app/IEphemeralResolver.aidl \
 	core/java/com/android/internal/app/IProcessStats.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
 	core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 17d349a..2d59de1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36264,6 +36264,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
     method public void destroyDrawingCache();
+    method public final boolean didLayoutParamsChange();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
     method public void dispatchDisplayHint(int);
@@ -36489,6 +36490,7 @@
     method public boolean isOpaque();
     method protected boolean isPaddingOffsetRequired();
     method public boolean isPaddingRelative();
+    method public final boolean isPartialLayoutRequested();
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
@@ -37098,6 +37100,7 @@
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method public int findDependentLayoutAxes(android.view.View, int);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -37164,6 +37167,8 @@
     method public void requestChildFocus(android.view.View, android.view.View);
     method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
     method public void requestDisallowInterceptTouchEvent(boolean);
+    method public void requestLayoutForChild(android.view.View);
+    method public void requestPartialLayoutForChild(android.view.View);
     method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public void requestTransparentRegion(android.view.View);
     method public void scheduleLayoutAnimation();
@@ -37278,6 +37283,7 @@
     method public abstract void childHasTransientStateChanged(android.view.View, boolean);
     method public abstract void clearChildFocus(android.view.View);
     method public abstract void createContextMenu(android.view.ContextMenu);
+    method public abstract int findDependentLayoutAxes(android.view.View, int);
     method public abstract android.view.View focusSearch(android.view.View, int);
     method public abstract void focusableViewAvailable(android.view.View);
     method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -37307,12 +37313,16 @@
     method public abstract void requestDisallowInterceptTouchEvent(boolean);
     method public abstract void requestFitSystemWindows();
     method public abstract void requestLayout();
+    method public abstract void requestLayoutForChild(android.view.View);
     method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public abstract void requestTransparentRegion(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View, float, float);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+    field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+    field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+    field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
   }
 
   public class ViewPropertyAnimator {
diff --git a/api/system-current.txt b/api/system-current.txt
index 4d41062..4719988 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8523,6 +8523,7 @@
     field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
     field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
     field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+    field public static final java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
     field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
     field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
     field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
@@ -8570,6 +8571,7 @@
     field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
     field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
     field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
+    field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
     field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
     field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
     field public static final java.lang.String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
@@ -38585,6 +38587,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
     method public void createContextMenu(android.view.ContextMenu);
     method public void destroyDrawingCache();
+    method public final boolean didLayoutParamsChange();
     method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
     method public void dispatchConfigurationChanged(android.content.res.Configuration);
     method public void dispatchDisplayHint(int);
@@ -38810,6 +38813,7 @@
     method public boolean isOpaque();
     method protected boolean isPaddingOffsetRequired();
     method public boolean isPaddingRelative();
+    method public final boolean isPartialLayoutRequested();
     method public boolean isPressed();
     method public boolean isSaveEnabled();
     method public boolean isSaveFromParentEnabled();
@@ -39419,6 +39423,7 @@
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
     method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
+    method public int findDependentLayoutAxes(android.view.View, int);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
     method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -39485,6 +39490,8 @@
     method public void requestChildFocus(android.view.View, android.view.View);
     method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
     method public void requestDisallowInterceptTouchEvent(boolean);
+    method public void requestLayoutForChild(android.view.View);
+    method public void requestPartialLayoutForChild(android.view.View);
     method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public void requestTransparentRegion(android.view.View);
     method public void scheduleLayoutAnimation();
@@ -39599,6 +39606,7 @@
     method public abstract void childHasTransientStateChanged(android.view.View, boolean);
     method public abstract void clearChildFocus(android.view.View);
     method public abstract void createContextMenu(android.view.ContextMenu);
+    method public abstract int findDependentLayoutAxes(android.view.View, int);
     method public abstract android.view.View focusSearch(android.view.View, int);
     method public abstract void focusableViewAvailable(android.view.View);
     method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -39628,12 +39636,16 @@
     method public abstract void requestDisallowInterceptTouchEvent(boolean);
     method public abstract void requestFitSystemWindows();
     method public abstract void requestLayout();
+    method public abstract void requestLayoutForChild(android.view.View);
     method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
     method public abstract void requestTransparentRegion(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View);
     method public abstract boolean showContextMenuForChild(android.view.View, float, float);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
     method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+    field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+    field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+    field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
   }
 
   public class ViewPropertyAnimator {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 62e0919a..daf01ec 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -65,6 +65,7 @@
 import android.view.IWindowManager;
 
 import com.android.internal.os.BaseCommand;
+import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
 
 import java.io.BufferedReader;
@@ -152,6 +153,7 @@
                 "       am to-app-uri [INTENT]\n" +
                 "       am switch-user <USER_ID>\n" +
                 "       am start-user <USER_ID>\n" +
+                "       am unlock-user <USER_ID> [TOKEN_HEX]\n" +
                 "       am stop-user [-w] <USER_ID>\n" +
                 "       am stack start <DISPLAY_ID> <INTENT>\n" +
                 "       am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
@@ -411,6 +413,8 @@
             runSwitchUser();
         } else if (op.equals("start-user")) {
             runStartUserInBackground();
+        } else if (op.equals("unlock-user")) {
+            runUnlockUser();
         } else if (op.equals("stop-user")) {
             runStopUser();
         } else if (op.equals("stack")) {
@@ -1086,6 +1090,21 @@
         }
     }
 
+    private void runUnlockUser() throws Exception {
+        int userId = Integer.parseInt(nextArgRequired());
+        String tokenHex = nextArg();
+        byte[] token = null;
+        if (tokenHex != null) {
+            token = HexDump.hexStringToByteArray(tokenHex);
+        }
+        boolean success = mAm.unlockUser(userId, token);
+        if (success) {
+            System.out.println("Success: user unlocked");
+        } else {
+            System.err.println("Error: could not unlock user");
+        }
+    }
+
     private static class StopUserCallback extends IStopUserCallback.Stub {
         private boolean mFinished = false;
 
diff --git a/cmds/appops/Android.mk b/cmds/appops/Android.mk
index 1e15204..6801ce9 100644
--- a/cmds/appops/Android.mk
+++ b/cmds/appops/Android.mk
@@ -3,14 +3,8 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := appops
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := appops
 LOCAL_SRC_FILES := appops
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
-
diff --git a/cmds/appops/appops b/cmds/appops/appops
index 407e551..25d2031 100755
--- a/cmds/appops/appops
+++ b/cmds/appops/appops
@@ -1,5 +1 @@
-# Script to start "appwidget" on the device, which has a very rudimentary shell.
-base=/system
-export CLASSPATH=$base/framework/appops.jar
-exec app_process $base/bin com.android.commands.appops.AppOpsCommand "$@"
-
+cmd appops $@
diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
deleted file mode 100644
index c9b9e58..0000000
--- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-** Copyright 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.commands.appops;
-
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import android.util.TimeUtils;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.os.BaseCommand;
-
-import java.io.PrintStream;
-import java.util.List;
-
-/**
- * This class is a command line utility for manipulating AppOps permissions.
- */
-public class AppOpsCommand extends BaseCommand {
-
-    public static void main(String[] args) {
-        new AppOpsCommand().run(args);
-    }
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println("usage: appops set [--user <USER_ID>] <PACKAGE> <OP> <MODE>\n"
-                + "       appops get [--user <USER_ID>] <PACKAGE> [<OP>]\n"
-                + "       appops reset [--user <USER_ID>] [<PACKAGE>]\n"
-                + "  <PACKAGE> an Android package name.\n"
-                + "  <OP>      an AppOps operation.\n"
-                + "  <MODE>    one of allow, ignore, deny, or default\n"
-                + "  <USER_ID> the user id under which the package is installed. If --user is not\n"
-                + "            specified, the current user is assumed.\n");
-    }
-
-    private static final String COMMAND_SET = "set";
-    private static final String COMMAND_GET = "get";
-    private static final String COMMAND_RESET = "reset";
-
-    @Override
-    public void onRun() throws Exception {
-        String command = nextArgRequired();
-        switch (command) {
-            case COMMAND_SET:
-                runSet();
-                break;
-
-            case COMMAND_GET:
-                runGet();
-                break;
-
-            case COMMAND_RESET:
-                runReset();
-                break;
-
-            default:
-                System.err.println("Error: Unknown command: '" + command + "'.");
-                break;
-        }
-    }
-
-    private static final String ARGUMENT_USER = "--user";
-
-    // Modes
-    private static final String MODE_ALLOW = "allow";
-    private static final String MODE_DENY = "deny";
-    private static final String MODE_IGNORE = "ignore";
-    private static final String MODE_DEFAULT = "default";
-
-    private int strOpToOp(String op) {
-        try {
-            return AppOpsManager.strOpToOp(op);
-        } catch (IllegalArgumentException e) {
-        }
-        try {
-            return Integer.parseInt(op);
-        } catch (NumberFormatException e) {
-        }
-        try {
-            return AppOpsManager.strDebugOpToOp(op);
-        } catch (IllegalArgumentException e) {
-            System.err.println("Error: " + e.getMessage());
-            return -1;
-        }
-    }
-
-    private void runSet() throws Exception {
-        String packageName = null;
-        String op = null;
-        String mode = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else if (mode == null) {
-                    mode = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        } else if (op == null) {
-            System.err.println("Error: Operation not specified.");
-            return;
-        } else if (mode == null) {
-            System.err.println("Error: Mode not specified.");
-            return;
-        }
-
-        final int opInt = strOpToOp(op);
-        if (opInt < 0) {
-            return;
-        }
-        final int modeInt;
-        switch (mode) {
-            case MODE_ALLOW:
-                modeInt = AppOpsManager.MODE_ALLOWED;
-                break;
-            case MODE_DENY:
-                modeInt = AppOpsManager.MODE_ERRORED;
-                break;
-            case MODE_IGNORE:
-                modeInt = AppOpsManager.MODE_IGNORED;
-                break;
-            case MODE_DEFAULT:
-                modeInt = AppOpsManager.MODE_DEFAULT;
-                break;
-            default:
-                System.err.println("Error: Mode " + mode + " is not valid,");
-                return;
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid;
-        if ("root".equals(packageName)) {
-            uid = 0;
-        } else {
-            uid = pm.getPackageUid(packageName, userId);
-        }
-        if (uid < 0) {
-            System.err.println("Error: No UID for " + packageName + " in user " + userId);
-            return;
-        }
-        appOpsService.setMode(opInt, uid, packageName, modeInt);
-    }
-
-    private void runGet() throws Exception {
-        String packageName = null;
-        String op = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        }
-
-        final int opInt = op != null ? strOpToOp(op) : 0;
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid;
-        if ("root".equals(packageName)) {
-            uid = 0;
-        } else {
-            uid = pm.getPackageUid(packageName, userId);
-        }
-        if (uid < 0) {
-            System.err.println("Error: No UID for " + packageName + " in user " + userId);
-            return;
-        }
-        List<AppOpsManager.PackageOps> ops = appOpsService.getOpsForPackage(uid, packageName,
-                op != null ? new int[] {opInt} : null);
-        if (ops == null || ops.size() <= 0) {
-            System.out.println("No operations.");
-            return;
-        }
-        final long now = System.currentTimeMillis();
-        for (int i=0; i<ops.size(); i++) {
-            List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
-            for (int j=0; j<entries.size(); j++) {
-                AppOpsManager.OpEntry ent = entries.get(j);
-                System.out.print(AppOpsManager.opToName(ent.getOp()));
-                System.out.print(": ");
-                switch (ent.getMode()) {
-                    case AppOpsManager.MODE_ALLOWED:
-                        System.out.print("allow");
-                        break;
-                    case AppOpsManager.MODE_IGNORED:
-                        System.out.print("ignore");
-                        break;
-                    case AppOpsManager.MODE_ERRORED:
-                        System.out.print("deny");
-                        break;
-                    case AppOpsManager.MODE_DEFAULT:
-                        System.out.print("default");
-                        break;
-                    default:
-                        System.out.print("mode=");
-                        System.out.print(ent.getMode());
-                        break;
-                }
-                if (ent.getTime() != 0) {
-                    System.out.print("; time=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getRejectTime() != 0) {
-                    System.out.print("; rejectTime=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getRejectTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getDuration() == -1) {
-                    System.out.print(" (running)");
-                } else if (ent.getDuration() != 0) {
-                    System.out.print("; duration=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(ent.getDuration(), sb);
-                    System.out.print(sb);
-                }
-                System.out.println();
-            }
-        }
-    }
-
-    private void runReset() throws Exception {
-        String packageName = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                String userStr = nextArgRequired();
-                if ("all".equals(userStr)) {
-                    userId = UserHandle.USER_ALL;
-                } else if ("current".equals(userStr)) {
-                    userId = UserHandle.USER_CURRENT;
-                } else if ("owner".equals(userStr) || "system".equals(userStr)) {
-                    userId = UserHandle.USER_SYSTEM;
-                } else {
-                    userId = Integer.parseInt(nextArgRequired());
-                }
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        appOpsService.resetAllModes(userId, packageName);
-        System.out.print("Reset all modes for: ");
-        if (userId == UserHandle.USER_ALL) {
-            System.out.print("all users");
-        } else {
-            System.out.print("user "); System.out.print(userId);
-        }
-        System.out.print(", ");
-        if (packageName == null) {
-            System.out.println("all packages");
-        } else {
-            System.out.print("package "); System.out.println(packageName);
-        }
-    }
-}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 084319a..f7aee75 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -528,6 +528,15 @@
             return stackId == FULLSCREEN_WORKSPACE_STACK_ID
                     || stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
         }
+
+        /**
+         * Returns true if animation specs should be constructed for app transition that moves
+         * the task to the specified stack.
+         */
+        public static boolean useAnimationSpecForAppTransition(int stackId) {
+            return stackId == FREEFORM_WORKSPACE_STACK_ID
+                    || stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID;
+        }
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 16cf254..0b7b6fc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1962,6 +1962,16 @@
             return true;
         }
 
+        case UNLOCK_USER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int userId = data.readInt();
+            byte[] token = data.createByteArray();
+            boolean result = unlockUser(userId, token);
+            reply.writeNoException();
+            reply.writeInt(result ? 1 : 0);
+            return true;
+        }
+
         case STOP_USER_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int userid = data.readInt();
@@ -5250,6 +5260,20 @@
         return result;
     }
 
+    public boolean unlockUser(int userId, byte[] token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(userId);
+        data.writeByteArray(token);
+        mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean result = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
+
     public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 88543e5..db4f5c1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -390,6 +390,7 @@
     // Multi-user APIs
     public boolean switchUser(int userid) throws RemoteException;
     public boolean startUserInBackground(int userid) throws RemoteException;
+    public boolean unlockUser(int userid, byte[] token) throws RemoteException;
     public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
     public UserInfo getCurrentUser() throws RemoteException;
     public boolean isUserRunning(int userid, int flags) throws RemoteException;
@@ -904,4 +905,5 @@
     int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
     int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
     int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
+    int UNLOCK_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 351;
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30fe531..9d941fd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1426,6 +1426,36 @@
     public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
 
     /**
+     * Activity Action: Launch ephemeral installer.
+     * <p>
+     * Input: The data must be a http: URI that the ephemeral application is registered
+     * to handle.
+     * <p class="note">
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+            = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+
+    /**
+     * Service Action: Resolve ephemeral application.
+     * <p>
+     * The system will have a persistent connection to this service.
+     * This is a protected intent that can only be sent by the system.
+     * </p>
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.SERVICE_ACTION)
+    public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+            = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+
+    /**
      * Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
      * package.  Specifies the installer package name; this package will receive the
      * {@link #ACTION_APP_ERROR} intent.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index eda4136..1996e0f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -982,7 +982,7 @@
                 .getAbsolutePath();
 
         if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0
-                && SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+                && StorageManager.isFileBasedEncryptionEnabled()) {
             dataDir = deviceEncryptedDataDir;
         } else {
             dataDir = credentialEncryptedDataDir;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 566de4e..42fef3b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -240,16 +240,15 @@
     public static final int GET_ENCRYPTION_UNAWARE_COMPONENTS = 0x00040000;
 
     /**
-     * {@link PackageInfo} flag: return components as if the given user is
-     * running with amnesia. This typically limits the component to only those
-     * marked as {@link ComponentInfo#encryptionAware}, unless
+     * {@link PackageInfo} flag: return components that are marked as
+     * {@link ComponentInfo#encryptionAware}, unless
      * {@link #GET_ENCRYPTION_UNAWARE_COMPONENTS} is also specified.
      * <p>
      * This flag is for internal use only.
      *
      * @hide
      */
-    public static final int FLAG_USER_RUNNING_WITH_AMNESIA = 0x00080000;
+    public static final int MATCH_ENCRYPTION_AWARE_ONLY = 0x00080000;
 
     /**
      * Flag for {@link addCrossProfileIntentFilter}: if this flag is set:
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 2e43ffc..c6510f0 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1301,23 +1301,6 @@
             }
 
             @Override
-            public boolean isPerUserEncryptionEnabled() throws RemoteException {
-                Parcel _data = Parcel.obtain();
-                Parcel _reply = Parcel.obtain();
-                boolean _result;
-                try {
-                    _data.writeInterfaceToken(DESCRIPTOR);
-                    mRemote.transact(Stub.TRANSACTION_isPerUserEncryptionEnabled, _data, _reply, 0);
-                    _reply.readException();
-                    _result = 0 != _reply.readInt();
-                } finally {
-                    _reply.recycle();
-                    _data.recycle();
-                }
-                return _result;
-            }
-
-            @Override
             public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
@@ -1459,7 +1442,6 @@
 
         static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66;
 
-        static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67;
         static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
 
         static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69;
@@ -2074,13 +2056,6 @@
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_isPerUserEncryptionEnabled: {
-                    data.enforceInterface(DESCRIPTOR);
-                    boolean result = isPerUserEncryptionEnabled();
-                    reply.writeNoException();
-                    reply.writeInt(result ? 1 : 0);
-                    return true;
-                }
                 case TRANSACTION_mountAppFuse: {
                     data.enforceInterface(DESCRIPTOR);
                     String name = data.readString();
@@ -2411,7 +2386,5 @@
     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
             throws RemoteException;
 
-    public boolean isPerUserEncryptionEnabled() throws RemoteException;
-
     public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d9090b..db12564 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -33,6 +33,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -77,11 +78,9 @@
     /** {@hide} */
     public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
     /** {@hide} */
-    public static final String PROP_HAS_FBE = "vold.has_fbe";
-    /** {@hide} */
     public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
     /** {@hide} */
-    public static final String PROP_EMULATE_FBE = "vold.emulate_fbe";
+    public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
 
     /** {@hide} */
     public static final String UUID_PRIVATE_INTERNAL = null;
@@ -1021,12 +1020,9 @@
     }
 
     /** {@hide} */
-    public boolean isPerUserEncryptionEnabled() {
-        try {
-            return mMountService.isPerUserEncryptionEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowAsRuntimeException();
-        }
+    public static boolean isFileBasedEncryptionEnabled() {
+        return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
+                || SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
     }
 
     /** {@hide} */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 30408c6..2415b4d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -83,6 +83,7 @@
 import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
 import android.view.AccessibilityIterators.WordTextSegmentIterator;
 import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.ViewGroup.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityEventSource;
 import android.view.accessibility.AccessibilityManager;
@@ -2416,6 +2417,8 @@
      *                    1              PFLAG3_SCROLL_INDICATOR_END
      *                   1               PFLAG3_ASSIST_BLOCKED
      *            1111111                PFLAG3_POINTER_ICON_MASK
+     *           1                       PFLAG3_PARTIAL_LAYOUT_REQUESTED
+     *          1                        PFLAG3_LAYOUT_PARAMS_CHANGED
      * |-------|-------|-------|-------|
      */
 
@@ -2504,6 +2507,7 @@
      */
     static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
 
+
     /* End of masks for mPrivateFlags3 */
 
     static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -2642,6 +2646,19 @@
     private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
 
     /**
+     * Flag indicating that this view has requested a partial layout and
+     * is added to the AttachInfo's list of views that need a partial layout
+     * request handled on the next traversal.
+     */
+    static final int PFLAG3_PARTIAL_LAYOUT_REQUESTED = 0x800000;
+
+    /**
+     * Flag indicating that this view's LayoutParams have been explicitly changed
+     * since the last layout pass.
+     */
+    static final int PFLAG3_LAYOUT_PARAMS_CHANGED = 0x1000000;
+
+    /**
      * Always allow a user to over-scroll this view, provided it is a
      * view that can scroll.
      *
@@ -12622,10 +12639,14 @@
      * ViewGroup.LayoutParams, and these correspond to the different subclasses
      * of ViewGroup that are responsible for arranging their children.
      *
-     * This method may return null if this View is not attached to a parent
+     * <p>This method may return null if this View is not attached to a parent
      * ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
      * was not invoked successfully. When a View is attached to a parent
-     * ViewGroup, this method must not return null.
+     * ViewGroup, this method must not return null.</p>
+     *
+     * <p>Callers that modify the returned LayoutParams object should call
+     * {@link #setLayoutParams(LayoutParams)} to explicitly inform the view that
+     * LayoutParams have changed.</p>
      *
      * @return The LayoutParams associated with this view, or null if no
      *         parameters have been set yet
@@ -12642,6 +12663,9 @@
      * correspond to the different subclasses of ViewGroup that are responsible
      * for arranging their children.
      *
+     * <p>If the View's existing LayoutParams object as obtained by {@link #getLayoutParams()} is
+     * modified, you should call this method to inform the view that it has changed.</p>
+     *
      * @param params The layout parameters for this view, cannot be null
      */
     public void setLayoutParams(ViewGroup.LayoutParams params) {
@@ -12649,6 +12673,7 @@
             throw new NullPointerException("Layout parameters cannot be null");
         }
         mLayoutParams = params;
+        mPrivateFlags3 |= PFLAG3_LAYOUT_PARAMS_CHANGED;
         resolveLayoutParams();
         if (mParent instanceof ViewGroup) {
             ((ViewGroup) mParent).onSetLayoutParams(this, params);
@@ -14324,7 +14349,12 @@
             mParent.requestTransparentRegion(this);
         }
 
-        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+        if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+            initialAwakenScrollBars();
+            mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
+        }
+
+        mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED);
 
         jumpDrawablesToCurrentState();
 
@@ -14662,8 +14692,13 @@
      */
     @CallSuper
     protected void onDetachedFromWindowInternal() {
+        if (mAttachInfo != null && isPartialLayoutRequested()) {
+            mAttachInfo.mPartialLayoutViews.remove(this);
+        }
+
         mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
-        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+        mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED
+                | PFLAG3_LAYOUT_PARAMS_CHANGED);
 
         removeUnsetPressCallback();
         removeLongPressCallback();
@@ -16850,6 +16885,29 @@
     }
 
     /**
+     * Indicates whether or not this view has requested a partial layout that
+     * may not affect its size or position within its parent. This state will be reset
+     * the next time this view is laid out.
+     *
+     * @return true if partial layout has been requested
+     */
+    public final boolean isPartialLayoutRequested() {
+        return (mPrivateFlags3 & PFLAG3_PARTIAL_LAYOUT_REQUESTED)
+                == PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+    }
+
+    /**
+     * Returns true if this view's {@link ViewGroup.LayoutParams LayoutParams} changed
+     * since the last time this view was successfully laid out. Typically this happens as a
+     * result of a call to {@link #setLayoutParams(LayoutParams)}.
+     *
+     * @return true if this view's LayoutParams changed since last layout.
+     */
+    public final boolean didLayoutParamsChange() {
+        return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
+    }
+
+    /**
      * Return true if o is a ViewGroup that is laying out using optical bounds.
      * @hide
      */
@@ -16906,6 +16964,7 @@
         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
             onLayout(changed, l, t, r, b);
             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+            mPrivateFlags3 &= ~PFLAG3_LAYOUT_PARAMS_CHANGED;
 
             ListenerInfo li = mListenerInfo;
             if (li != null && li.mOnLayoutChangeListeners != null) {
@@ -16919,6 +16978,7 @@
         }
 
         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+        mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED;
         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
     }
 
@@ -19012,7 +19072,7 @@
         mPrivateFlags |= PFLAG_INVALIDATED;
 
         if (mParent != null && !mParent.isLayoutRequested()) {
-            mParent.requestLayout();
+            mParent.requestLayoutForChild(this);
         }
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
             mAttachInfo.mViewRequestingLayout = null;
@@ -19031,6 +19091,11 @@
         mPrivateFlags |= PFLAG_INVALIDATED;
     }
 
+    void forcePartialLayout() {
+        forceLayout();
+        mPrivateFlags3 |= PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+    }
+
     /**
      * <p>
      * This is called to find out how big a view should be. The parent
@@ -21867,6 +21932,7 @@
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
+            void schedulePartialLayout();
         }
 
         /**
@@ -22242,6 +22308,12 @@
         IBinder mDragToken;
 
         /**
+         * Used to track views that need (at least) a partial relayout at their current size
+         * during the next traversal.
+         */
+        final List<View> mPartialLayoutViews = new ArrayList<View>();
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 25df004..6812fd1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -60,6 +60,8 @@
 import java.util.List;
 import java.util.Map;
 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 /**
  * <p>
@@ -5517,6 +5519,172 @@
             int l, int t, int r, int b);
 
     /**
+     * {@inheritDoc}
+     *
+     * <p>Most subclasses should not need to override this method. The default implementation
+     * will call {@link #findDependentLayoutAxes(View, int)} to determine how
+     * to optimally proceed. If neither horizontal nor vertical layout depends on the given
+     * child, this method will call {@link #requestPartialLayoutForChild(View)}. If one or both
+     * do, it will call {@link #requestLayout()}.</p>
+     *
+     * @param child Child requesting a layout
+     */
+    @Override
+    public void requestLayoutForChild(View child) {
+        if (child == null || child.getParent() != this) {
+            throw new IllegalArgumentException(
+                    "child parameter must be a direct child view of this ViewGroup");
+        }
+
+        // If we don't have a parent ourselves, record that we need a full layout.
+        // Our whole subtree is detached.
+        final ViewParent parent = getParent();
+        if (parent == null) {
+            requestLayout();
+            return;
+        }
+
+        // We can optimize the layout request for this child into a partial layout
+        // if the child has already been laid out at least once and neither horizontal nor
+        // vertical layout within ourselves is dependent on pending layout changes within
+        // this child. Otherwise we need to request a full layout for ourselves and continue
+        // to recurse up the view hierarchy.
+        if (child.isLaidOut() && findDependentLayoutAxes(child, FLAG_LAYOUT_AXIS_ANY) == 0) {
+            requestPartialLayoutForChild(child);
+        } else {
+            requestLayout();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The default implementation returns {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * Optimized implementations for specific ViewGroup subclasses may check if the child's
+     * {@link View#didLayoutParamsChange() LayoutParams changed} and in what ways.</p>
+     *
+     * @param child Direct child of this ViewParent to check
+     * @param axisFilter Which axes to check for dependencies. Can be
+     *                   {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+     *                   or {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * @return Axes of this ViewParent that depend on the given child's layout changes
+     */
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return FLAG_LAYOUT_AXIS_ANY;
+    }
+
+    /**
+     * This is a helper implementation for {@link #findDependentLayoutAxes(View, int)} that
+     * is not the default implementation in ViewGroup. This is to preserve compatibility with
+     * existing app-side ViewGroup subclasses that existed before the partial layout system was
+     * added to Android. It explicitly checks that the LayoutParams of the child are of the
+     * expected type so that subclasses of standard framework layouts do not erroneously
+     * start believing that it's safe to do a partial layout when that assertion can't
+     * reasonably be confirmed.
+     *
+     * <p>If you're reading this as an author of a custom ViewGroup's findDependentLayoutAxes
+     * method you might be frustrated to discover that it is not a part of the Android public API.
+     * Many ViewGroup implementations will need to make small but important modifications
+     * to an implementation like this one in order to be correct. Instead of encouraging
+     * view authors to call this method, then make their own redundant recursive calls to
+     * <code>getParent().findDependentLayoutAxes(...)</code> in addition to the one
+     * that can happen here, this method is hidden and only used internally.</p>
+     *
+     * <p>Do feel free to copy this implementation and adapt it to suit your own purposes.</p>
+     *
+     * @hide
+     */
+    protected final int findDependentLayoutAxesHelper(View child, int axisFilter,
+            Class<?> layoutParamsClass) {
+        if (!checkPartialLayoutParams(child, layoutParamsClass)) return axisFilter;
+        if (child.didLayoutParamsChange()) {
+            // Anything could have changed about our previous assumptions.
+            return axisFilter;
+        }
+
+        final LayoutParams lp = child.getLayoutParams();
+
+        // Our layout can always end up depending on a WRAP_CONTENT child.
+        final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (wrapAxisFilter == axisFilter) {
+            // We know all queried axes are affected, just return early.
+            return wrapAxisFilter;
+        }
+
+        // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+        // that our layout will remain stable within our parent. We need to ask.
+        final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (matchAxisFilter != 0) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                // If our parent depends on us for an axis, then our layout can also be affected
+                // by a MATCH_PARENT child along that axis.
+                return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+                        | wrapAxisFilter;
+            }
+
+            // If we don't have a parent, assume we're affected
+            // in any determined affected direction.
+            return matchAxisFilter | wrapAxisFilter;
+        }
+
+        // Two exact sizes and LayoutParams didn't change. We're safe.
+        return 0;
+    }
+
+    /**
+     * Throw an IllegalArgumentException if the supplied view is not a direct child of
+     * this ViewGroup and return false if this view's LayoutParams is not of class lpClass.
+     * Implementations of {@link ViewGroup#findDependentLayoutAxes(View, int)} use this
+     * to check input parameters and defensively return the full axis filter mask themselves
+     * if the LayoutParams class is not of the exact expected type; e.g. it is a subclass
+     * of one of the standard framework layouts and we can't make assumptions.
+     * @hide
+     */
+    protected final boolean checkPartialLayoutParams(View child, Class<?> lpClass) {
+        if (child.getParent() != this) {
+            throw new IllegalArgumentException("View " + child
+                    + " is not a direct child of " + this);
+        }
+        final ViewGroup.LayoutParams lp = child.getLayoutParams();
+        return lp != null || lp.getClass() == lpClass;
+    }
+
+    /**
+     * Called when a child of this ViewParent requires a relayout before the next frame
+     * is drawn, but the caller can guarantee that the size of the child will not change
+     * during a measure and layout pass.
+     *
+     * <p>A call to this method will schedule a partial layout for the supplied view as long as
+     * it is a direct child of this ViewGroup and this ViewGroup is attached to a window.
+     * On the next scheduled view hierarchy traversal the given child view will be re-measured
+     * at its current measured size and re-laid out at its current position within its parent.</p>
+     *
+     * @param child Child that requires a partial layout
+     */
+    public void requestPartialLayoutForChild(View child) {
+        if (!child.isPartialLayoutRequested()) {
+            child.forcePartialLayout();
+            if (mAttachInfo != null) {
+                final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+                final boolean schedule = partialLayoutViews.isEmpty();
+                partialLayoutViews.add(child);
+                if (schedule) {
+                    mAttachInfo.mRootCallbacks.schedulePartialLayout();
+                }
+                child.invalidate();
+            } else {
+                requestLayout();
+            }
+        }
+    }
+
+    /**
      * Indicates whether the view group has the ability to animate its children
      * after the first layout.
      *
@@ -5862,7 +6030,7 @@
      *         of its descendants
      */
     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return p;
+        return new LayoutParams(p);
     }
 
     /**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 07f1e2c..6ae448a 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -26,6 +26,11 @@
  * 
  */
 public interface ViewParent {
+    public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1;
+    public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2;
+    public static final int FLAG_LAYOUT_AXIS_ANY
+            = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL;
+
     /**
      * Called when something has changed which has invalidated the layout of a
      * child of this view parent. This will schedule a layout pass of the view
@@ -601,4 +606,48 @@
      * @return true if the action was consumed by this ViewParent
      */
     public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+    /**
+     * Called when a child of this ViewParent requires a relayout before
+     * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()}
+     * will implicitly result in a call to
+     * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this
+     * method directly. Call <code>child.requestLayout()</code> instead.
+     *
+     * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()}
+     * would cause a matching call to <code>requestLayout</code> on each parent view up to
+     * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may
+     * explicitly decide how to handle a layout request. This allows for optimizations when
+     * a view parent knows that a layout-altering change in a child will not affect its own
+     * measurement.</p>
+     *
+     * @param child Child requesting a layout
+     */
+    public void requestLayoutForChild(View child);
+
+    /**
+     * Determine which axes of this ViewParent's layout are dependent on the given
+     * direct child view. The returned value is a flag set that may contain
+     * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}.
+     * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for
+     * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>.
+     *
+     * <p>The given child must be a direct child view. Implementations should throw
+     * {@link IllegalArgumentException} otherwise.</p>
+     *
+     * <p>The caller may specify which axes it cares about. This should be treated as a filter.
+     * Implementations should never return a result that would be different from
+     * <code>result & axisFilter</code>.</p>
+     *
+     * @param child Direct child of this ViewParent to check
+     * @param axisFilter Which axes to check for dependencies. Can be
+     *                   {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+     *                   or {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * @return Axes of this ViewParent that depend on the given child's layout changes
+     *
+     * @see #FLAG_LAYOUT_AXIS_HORIZONTAL
+     * @see #FLAG_LAYOUT_AXIS_VERTICAL
+     * @see #FLAG_LAYOUT_AXIS_ANY
+     */
+    public int findDependentLayoutAxes(View child, int axisFilter);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f1d9f1ab..2c0cd7a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -91,6 +91,7 @@
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -952,6 +953,25 @@
     }
 
     @Override
+    public void requestLayoutForChild(View child) {
+        requestLayout();
+    }
+
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        if (child != mView) {
+            return 0;
+        }
+
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams();
+        final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0;
+        final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0;
+        return (horizontal | vertical) & axisFilter;
+    }
+
+    @Override
     public boolean isLayoutRequested() {
         return mLayoutRequested;
     }
@@ -1095,6 +1115,10 @@
         }
     }
 
+    public void schedulePartialLayout() {
+        scheduleTraversals();
+    }
+
     /**
      * Notifies the HardwareRenderer that a new frame will be coming soon.
      * Currently only {@link ThreadedRenderer} cares about this, and uses
@@ -1934,7 +1958,48 @@
                 || mAttachInfo.mRecomputeGlobalAttributes;
         if (didLayout) {
             performLayout(lp, desiredWindowWidth, desiredWindowHeight);
+        }
 
+        /*
+         * Handle partial layouts.
+         *
+         * Views that have requested partial layouts will not change size or position
+         * within their parent view, therefore we will re-measure and re-layout each one
+         * after any regularly scheduled layout pass. Any view that already had its
+         * isLayoutRequested bit cleared will be skipped, since this means the view has already
+         * been measured and laid out on this traversal pass naturally. Views won't be added
+         * to this list if layout was already requested when a partial layout is requested
+         * for a view, so there should not be duplicates in the list.
+         */
+        final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+        final boolean didPartialLayout;
+        if (!partialLayoutViews.isEmpty()) {
+            final int count = partialLayoutViews.size();
+            mInLayout = true;
+            for (int i = 0; i < count; i++) {
+                final View view = partialLayoutViews.get(i);
+
+                // Make sure the view is still attached and that it still has layout requested.
+                // We might have already serviced the layout request through the standard full-tree
+                // layout pass above or even through a previous partial layout view in this list.
+                if (view.isAttachedToWindow() && view.isLayoutRequested()) {
+                    final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
+                            MeasureSpec.EXACTLY);
+                    final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
+                            MeasureSpec.EXACTLY);
+                    view.measure(widthSpec, heightSpec);
+                    view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+                }
+            }
+            mInLayout = false;
+            partialLayoutViews.clear();
+            didPartialLayout = true;
+            triggerGlobalLayoutListener = true;
+        } else {
+            didPartialLayout = false;
+        }
+
+        if (didLayout || didPartialLayout) {
             // By this point all views have been sized and positioned
             // We can compute the transparent area
 
@@ -1964,7 +2029,7 @@
 
             if (DBG) {
                 System.out.println("======================================");
-                System.out.println("performTraversals -- after setFrame");
+                System.out.println("performTraversals -- after performLayout/partial layout");
                 host.debug();
             }
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b8faf0c..90de053 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2109,6 +2109,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mSelector == null) {
             useDefaultSelector();
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 280ff15..4d9f55c 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -21,12 +21,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
-import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
@@ -36,9 +32,6 @@
 import android.view.ViewHierarchyEncoder;
 import android.widget.RemoteViews.RemoteView;
 
-import com.android.internal.R;
-
-
 /**
  * FrameLayout is designed to block out an area on the screen to display
  * a single item. Generally, FrameLayout should be used to hold a single child view, because it can
@@ -171,6 +164,10 @@
             mPaddingBottom + mForegroundPaddingBottom;
     }
 
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
 
     /**
      * {@inheritDoc}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index ad939be..ba868a1 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import android.view.ViewParent;
 import com.android.internal.R;
 
 import android.annotation.IntDef;
@@ -37,6 +38,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 /**
  * A Layout that arranges its children in a single column or a single row. The direction of 
@@ -644,6 +647,60 @@
         }
     }
 
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        // This implementation is almost exactly equivalent to the default implementation
+        // offered to the rest of the framework in ViewGroup, but we treat weight to be
+        // functionally equivalent to MATCH_PARENT along the orientation axis.
+
+        if (!checkPartialLayoutParams(child, LayoutParams.class)) return axisFilter;
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        if (child.didLayoutParamsChange()) {
+            // Anything could have changed about our previous assumptions.
+            return axisFilter;
+        }
+
+        // Our layout can always end up depending on a WRAP_CONTENT child.
+        final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        if (wrapAxisFilter == axisFilter) {
+            // We know all queried axes are affected, just return early.
+            return wrapAxisFilter;
+        }
+
+        // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+        // that our layout will remain stable within our parent. We need to ask.
+        int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+                | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+        // For LinearLayout, a nonzero weight is equivalent to MATCH_PARENT for this purpose.
+        if (lp.weight > 0) {
+            if (mOrientation == HORIZONTAL) {
+                matchAxisFilter |= FLAG_LAYOUT_AXIS_HORIZONTAL & axisFilter;
+            } else {
+                matchAxisFilter |= FLAG_LAYOUT_AXIS_VERTICAL & axisFilter;
+            }
+        }
+
+        if (matchAxisFilter != 0) {
+            final ViewParent parent = getParent();
+            if (parent != null) {
+                // If our parent depends on us for an axis, then our layout can also be affected
+                // by a MATCH_PARENT child along that axis.
+                return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+                        | wrapAxisFilter;
+            }
+
+            // If we don't have a parent, assume we're affected
+            // in any determined affected direction.
+            return matchAxisFilter | wrapAxisFilter;
+        }
+
+        // Two exact sizes and LayoutParams didn't change. We're safe.
+        return 0;
+    }
+
     /**
      * Determines where to position dividers between children.
      *
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 53ca6d1..b43ea76 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1158,7 +1158,7 @@
             final View child = obtainView(0, mIsScrap);
 
             // Lay out child directly against the parent measure spec so that
-            // we can obtain exected minimum width and height.
+            // we can obtain expected minimum width and height.
             measureScrapChild(child, 0, widthMeasureSpec, heightSize);
 
             childWidth = child.getMeasuredWidth();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3e6d121..eaf4fe2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6801,10 +6801,11 @@
 
         if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
             if (!compressText(ellipsisWidth)) {
-                final int height = mLayoutParams.height;
                 // If the size of the view does not depend on the size of the text, try to
                 // start the marquee immediately
-                if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
+                final ViewParent parent = getParent();
+                if (parent != null && parent.findDependentLayoutAxes(this,
+                        ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) {
                     startMarquee();
                 } else {
                     // Defer the start of the marquee until we know our width (see setFrame())
@@ -7200,37 +7201,9 @@
      * new view layout.
      */
     private void checkForResize() {
-        boolean sizeChanged = false;
-
-        if (mLayout != null) {
-            // Check if our width changed
-            if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
-                sizeChanged = true;
-                invalidate();
-            }
-
-            // Check if our height changed
-            if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
-                int desiredHeight = getDesiredHeight();
-
-                if (desiredHeight != this.getHeight()) {
-                    sizeChanged = true;
-                }
-            } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
-                if (mDesiredHeightAtMeasure >= 0) {
-                    int desiredHeight = getDesiredHeight();
-
-                    if (desiredHeight != mDesiredHeightAtMeasure) {
-                        sizeChanged = true;
-                    }
-                }
-            }
-        }
-
-        if (sizeChanged) {
-            requestLayout();
-            // caller will have already invalidated
-        }
+        // Always request a layout. The parent will perform the correct version
+        // of the intended optimizations as part of requestLayoutForChild.
+        requestLayout();
     }
 
     /**
@@ -7238,56 +7211,10 @@
      * or merely a new text layout.
      */
     private void checkForRelayout() {
-        // If we have a fixed width, we can just swap in a new text layout
-        // if the text height stays the same or if the view height is fixed.
-
-        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
-                (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
-                (mHint == null || mHintLayout != null) &&
-                (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
-            // Static width, so try making a new text layout.
-
-            int oldht = mLayout.getHeight();
-            int want = mLayout.getWidth();
-            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
-
-            /*
-             * No need to bring the text into view, since the size is not
-             * changing (unless we do the requestLayout(), in which case it
-             * will happen at measure).
-             */
-            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
-                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
-                          false);
-
-            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
-                // In a fixed-height view, so use our new text layout.
-                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
-                    mLayoutParams.height != LayoutParams.MATCH_PARENT) {
-                    invalidate();
-                    return;
-                }
-
-                // Dynamic height, but height has stayed the same,
-                // so use our new text layout.
-                if (mLayout.getHeight() == oldht &&
-                    (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
-                    invalidate();
-                    return;
-                }
-            }
-
-            // We lose: the height has changed and we have a dynamic height.
-            // Request a new view layout using our new text layout.
-            requestLayout();
-            invalidate();
-        } else {
-            // Dynamic width, so we have no choice but to request a new
-            // view layout with a new text layout.
-            nullLayouts();
-            requestLayout();
-            invalidate();
-        }
+        // Always request a layout. The parent will perform the correct version
+        // of the intended optimizations as part of requestLayoutForChild.
+        nullLayouts();
+        requestLayout();
     }
 
     @Override
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index acbf5eb..6e56513 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -1368,6 +1368,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = 0;
         int height = 0;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.aidl b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
new file mode 100644
index 0000000..529527b
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 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.app;
+
+parcelable EphemeralResolveInfo;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.java b/core/java/com/android/internal/app/EphemeralResolveInfo.java
new file mode 100644
index 0000000..0e7ef05
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.java
@@ -0,0 +1,113 @@
+/*
+ * 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.app;
+
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information that is returned when resolving ephemeral
+ * applications.
+ */
+public final class EphemeralResolveInfo implements Parcelable {
+    public static final String SHA_ALGORITHM = "SHA-256";
+    private byte[] mDigestBytes;
+    private int mDigestPrefix;
+    private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
+
+    public EphemeralResolveInfo(Uri uri, List<IntentFilter> filters) {
+        generateDigest(uri);
+        mFilters.addAll(filters);
+    }
+
+    private EphemeralResolveInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public byte[] getDigestBytes() {
+        return mDigestBytes;
+    }
+
+    public int getDigestPrefix() {
+        return mDigestPrefix;
+    }
+
+    public List<IntentFilter> getFilters() {
+        return mFilters;
+    }
+
+    private void generateDigest(Uri uri) {
+        try {
+            final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+            final byte[] hostBytes = uri.getHost().getBytes();
+            final byte[] digestBytes = digest.digest(hostBytes);
+            mDigestBytes = digestBytes;
+            mDigestPrefix =
+                    digestBytes[0] << 24
+                    | digestBytes[1] << 16
+                    | digestBytes[2] << 8
+                    | digestBytes[3] << 0;
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("could not find digest algorithm");
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (mDigestBytes == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(mDigestBytes.length);
+            out.writeByteArray(mDigestBytes);
+        }
+        out.writeInt(mDigestPrefix);
+        out.writeList(mFilters);
+    }
+
+    private void readFromParcel(Parcel in) {
+        int digestBytesSize = in.readInt();
+        if (digestBytesSize > 0) {
+            mDigestBytes = new byte[digestBytesSize];
+            in.readByteArray(mDigestBytes);
+        }
+        mDigestPrefix = in.readInt();
+        in.readList(mFilters, null /*loader*/);
+    }
+
+    public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
+            = new Parcelable.Creator<EphemeralResolveInfo>() {
+        public EphemeralResolveInfo createFromParcel(Parcel in) {
+            return new EphemeralResolveInfo(in);
+        }
+
+        public EphemeralResolveInfo[] newArray(int size) {
+            return new EphemeralResolveInfo[size];
+        }
+    };
+}
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
new file mode 100644
index 0000000..65530f2
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolverService.java
@@ -0,0 +1,98 @@
+/*
+ * 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.app;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Base class for implementing the resolver service.
+ * @hide
+ */
+public abstract class EphemeralResolverService extends Service {
+    public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
+    public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+    private Handler mHandler;
+
+    /**
+     * Called to retrieve resolve info for ephemeral applications.
+     *
+     * @param digestPrefix The hash prefix of the ephemeral's domain.
+     */
+    protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+
+    @Override
+    protected final void attachBaseContext(Context base) {
+        super.attachBaseContext(base);
+        mHandler = new ServiceHandler(base.getMainLooper());
+    }
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return new IEphemeralResolver.Stub() {
+            @Override
+            public void getEphemeralResolveInfoList(
+                    IRemoteCallback callback, int digestPrefix, int sequence) {
+                mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
+                        digestPrefix, sequence, callback)
+                    .sendToTarget();
+            }
+        };
+    }
+
+    private final class ServiceHandler extends Handler {
+        public static final int MSG_GET_EPHEMERAL_RESOLVE_INFO = 1;
+
+        public ServiceHandler(Looper looper) {
+            super(looper, null /*callback*/, true /*async*/);
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public void handleMessage(Message message) {
+            final int action = message.what;
+            switch (action) {
+                case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
+                    final IRemoteCallback callback = (IRemoteCallback) message.obj;
+                    final List<EphemeralResolveInfo> resolveInfo =
+                            getEphemeralResolveInfoList(message.arg1);
+                    final Bundle data = new Bundle();
+                    data.putInt(EXTRA_SEQUENCE, message.arg2);
+                    data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+                    try {
+                        callback.sendResult(data);
+                    } catch (RemoteException e) {
+                    }
+                } break;
+
+                default: {
+                    throw new IllegalArgumentException("Unknown message: " + action);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl
new file mode 100644
index 0000000..40429ee
--- /dev/null
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -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.
+ */
+
+package com.android.internal.app;
+
+import android.content.Intent;
+import android.os.IRemoteCallback;
+
+oneway interface IEphemeralResolver {
+    void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
+}
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c3a7460..3e65320 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -360,6 +360,11 @@
     }
 
     @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         pullChildren();
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b87d9e2..057790a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1091,6 +1091,14 @@
          This feature should be disabled for most devices. -->
     <integer name="config_virtualKeyQuietTimeMillis">0</integer>
 
+    <!-- A list of potential packages, in priority order, that may contain an
+         ephemeral resolver. Each package will be be queried for a component
+         that has been granted the PACKAGE_EPHEMERAL_AGENT permission.
+         This may be empty if ephemeral apps are not supported. -->
+    <string-array name="config_ephemeralResolverPackage" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- Component name of the default wallpaper. This will be ImageWallpaper if not
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ce9d8d..1e325b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -628,6 +628,7 @@
   <java-symbol type="string" name="widget_default_package_name" />
   <java-symbol type="string" name="widget_default_class_name" />
   <java-symbol type="string" name="emergency_calls_only" />
+  <java-symbol type="array" name="config_ephemeralResolverPackage" />
   <java-symbol type="string" name="enable_accessibility_canceled" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index 6e22be8..b8c105d 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -512,7 +512,7 @@
 onStartCommand()} directly.)</p>
 
 <p>For example, an activity can start the example service in the previous section ({@code
-HelloSevice}) using an explicit intent with {@link android.content.Context#startService
+HelloService}) using an explicit intent with {@link android.content.Context#startService
 startService()}:</p>
 
 <pre>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 92226f5..0a57d50 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -30,6 +30,7 @@
     utils/StringUtils.cpp \
     utils/TestWindowContext.cpp \
     utils/VectorDrawableUtils.cpp \
+    utils/TestUtils.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -253,9 +254,11 @@
 
 LOCAL_SRC_FILES += \
     tests/TestContext.cpp \
-    tests/TreeContentAnimation.cpp \
+    tests/TestSceneRunner.cpp \
     tests/main.cpp
 
+LOCAL_SRC_FILES += $(call all-cpp-files-under, tests/scenes)
+
 include $(BUILD_EXECUTABLE)
 
 # ------------------------
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index 7a62037..4be1f99 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -23,7 +23,7 @@
 #include "DisplayListCanvas.h"
 #endif
 #include "microbench/MicroBench.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index b24858e..eea0c7f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -21,7 +21,7 @@
 #include "OpReorderer.h"
 #include "RecordedOp.h"
 #include "RecordingCanvas.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 #include "microbench/MicroBench.h"
 
 #include <vector>
diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h
index e16310e..3f87d7f 100644
--- a/libs/hwui/tests/Benchmark.h
+++ b/libs/hwui/tests/Benchmark.h
@@ -16,6 +16,8 @@
 #ifndef TESTS_BENCHMARK_H
 #define TESTS_BENCHMARK_H
 
+#include "TestScene.h"
+
 #include <string>
 #include <vector>
 
@@ -26,12 +28,17 @@
     int count;
 };
 
-typedef void (*BenchmarkFunctor)(const BenchmarkOptions&);
+typedef test::TestScene* (*CreateScene)(const BenchmarkOptions&);
+
+template <class T>
+test::TestScene* simpleCreateScene(const BenchmarkOptions&) {
+    return new T();
+}
 
 struct BenchmarkInfo {
     std::string name;
     std::string description;
-    BenchmarkFunctor functor;
+    CreateScene createScene;
 };
 
 class Benchmark {
diff --git a/libs/hwui/tests/TestScene.h b/libs/hwui/tests/TestScene.h
new file mode 100644
index 0000000..b5d8954
--- /dev/null
+++ b/libs/hwui/tests/TestScene.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTS_TESTSCENE_H
+#define TESTS_TESTSCENE_H
+
+namespace android {
+namespace uirenderer {
+class RenderNode;
+
+#if HWUI_NEW_OPS
+class RecordingCanvas;
+typedef RecordingCanvas TestCanvas;
+#else
+class DisplayListCanvas;
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+namespace test {
+
+class TestScene {
+public:
+    virtual ~TestScene() {}
+    virtual void createContent(int width, int height, TestCanvas& renderer) = 0;
+    virtual void doFrame(int frameNr) = 0;
+};
+
+} // namespace test
+} // namespace uirenderer
+} // namespace android
+
+#endif /* TESTS_TESTSCENE_H */
diff --git a/libs/hwui/tests/TestSceneRunner.cpp b/libs/hwui/tests/TestSceneRunner.cpp
new file mode 100644
index 0000000..0376e10
--- /dev/null
+++ b/libs/hwui/tests/TestSceneRunner.cpp
@@ -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.
+ */
+
+#include "AnimationContext.h"
+#include "Benchmark.h"
+#include "RenderNode.h"
+#include "TestContext.h"
+#include "scenes/TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "renderthread/RenderTask.h"
+
+#include <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+        return new AnimationContext(clock);
+    }
+};
+
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts) {
+    // Switch to the real display
+    gDisplay = getBuiltInDisplay();
+
+    std::unique_ptr<TestScene> scene(info.createScene(opts));
+
+    TestContext testContext;
+
+    // create the native surface
+    const int width = gDisplay.w;
+    const int height = gDisplay.h;
+    sp<Surface> surface = testContext.surface();
+
+    sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height,
+            [&scene, width, height](RenderProperties& props, TestCanvas& canvas) {
+        props.setClipToBounds(false);
+        scene->createContent(width, height, canvas);
+    });
+
+    ContextFactory factory;
+    std::unique_ptr<RenderProxy> proxy(new RenderProxy(false,
+            rootNode.get(), &factory));
+    proxy->loadSystemProperties();
+    proxy->initialize(surface);
+    float lightX = width / 2.0;
+    proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
+    proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
+
+    // Do a few cold runs then reset the stats so that the caches are all hot
+    for (int i = 0; i < 3; i++) {
+        testContext.waitForVsync();
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        proxy->syncAndDrawFrame();
+    }
+    proxy->resetProfileInfo();
+
+    for (int i = 0; i < opts.count; i++) {
+        testContext.waitForVsync();
+
+        ATRACE_NAME("UI-Draw Frame");
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        scene->doFrame(i);
+        proxy->syncAndDrawFrame();
+    }
+
+    proxy->dumpProfileInfo(STDOUT_FILENO, 0);
+}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
deleted file mode 100644
index 81bf9ed..0000000
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ /dev/null
@@ -1,428 +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.
- */
-
-#include <cutils/log.h>
-#include <gui/Surface.h>
-#include <ui/PixelFormat.h>
-
-#include <AnimationContext.h>
-#include <DisplayListCanvas.h>
-#include <RecordingCanvas.h>
-#include <RenderNode.h>
-#include <renderthread/RenderProxy.h>
-#include <renderthread/RenderTask.h>
-#include <unit_tests/TestUtils.h>
-
-#include "Benchmark.h"
-#include "TestContext.h"
-
-#include "protos/hwui.pb.h"
-
-#include <stdio.h>
-#include <unistd.h>
-#include <getopt.h>
-#include <vector>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-using namespace android::uirenderer::test;
-
-#if HWUI_NEW_OPS
-typedef RecordingCanvas TestCanvas;
-#else
-typedef DisplayListCanvas TestCanvas;
-#endif
-
-
-class ContextFactory : public IContextFactory {
-public:
-    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
-        return new AnimationContext(clock);
-    }
-};
-
-static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
-    TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
-    contentCallback(canvas);
-    node.setStagingDisplayList(canvas.finishRecording());
-}
-
-class TreeContentAnimation {
-public:
-    virtual ~TreeContentAnimation() {}
-    int frameCount = 150;
-    virtual int getFrameCount() { return frameCount; }
-    virtual void setFrameCount(int fc) {
-        if (fc > 0) {
-            frameCount = fc;
-        }
-    }
-    virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
-    virtual void doFrame(int frameNr) = 0;
-
-    template <class T>
-    static void run(const BenchmarkOptions& opts) {
-        // Switch to the real display
-        gDisplay = getBuiltInDisplay();
-
-        T animation;
-        animation.setFrameCount(opts.count);
-
-        TestContext testContext;
-
-        // create the native surface
-        const int width = gDisplay.w;
-        const int height = gDisplay.h;
-        sp<Surface> surface = testContext.surface();
-
-        RenderNode* rootNode = new RenderNode();
-        rootNode->incStrong(nullptr);
-        rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
-        rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        rootNode->mutateStagingProperties().setClipToBounds(false);
-        rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
-
-        ContextFactory factory;
-        std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
-        proxy->loadSystemProperties();
-        proxy->initialize(surface);
-        float lightX = width / 2.0;
-        proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
-        proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
-
-        recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
-            animation.createContent(width, height, &canvas); //TODO: no&
-        });
-
-        // Do a few cold runs then reset the stats so that the caches are all hot
-        for (int i = 0; i < 3; i++) {
-            testContext.waitForVsync();
-            proxy->syncAndDrawFrame();
-        }
-        proxy->resetProfileInfo();
-
-        for (int i = 0; i < animation.getFrameCount(); i++) {
-            testContext.waitForVsync();
-
-            ATRACE_NAME("UI-Draw Frame");
-            nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
-            UiFrameInfoBuilder(proxy->frameInfo())
-                    .setVsync(vsync, vsync);
-            animation.doFrame(i);
-            proxy->syncAndDrawFrame();
-        }
-
-        proxy->dumpProfileInfo(STDOUT_FILENO, 0);
-        rootNode->decStrong(nullptr);
-    }
-};
-
-class ShadowGridAnimation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        recordNode(*node, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _ShadowGrid(BenchmarkInfo{
-    "shadowgrid",
-    "A grid of rounded rects that cast a shadow. Simplified scenario of an "
-    "Android TV-style launcher interface. High CPU/GPU load.",
-    TreeContentAnimation::run<ShadowGridAnimation>
-});
-
-class ShadowGrid2Animation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
-            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
-                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        recordNode(*node, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _ShadowGrid2(BenchmarkInfo{
-    "shadowgrid2",
-    "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
-    "variant of shadowgrid. Very high CPU load, high GPU load.",
-    TreeContentAnimation::run<ShadowGrid2Animation>
-});
-
-class RectGridAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
-            SkRegion region;
-            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
-                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
-                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
-                }
-            }
-
-            SkPaint paint;
-            paint.setColor(0xff00ffff);
-            canvas.drawRegion(region, paint);
-        });
-        canvas->drawRenderNode(card.get());
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _RectGrid(BenchmarkInfo{
-    "rectgrid",
-    "A dense grid of 1x1 rects that should visually look like a single rect. "
-    "Low CPU/GPU load.",
-    TreeContentAnimation::run<RectGridAnimation>
-});
-
-class OvalAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setColor(0xFF000000);
-            canvas.drawOval(0, 0, 200, 200, paint);
-        });
-        canvas->drawRenderNode(card.get());
-
-        canvas->insertReorderBarrier(false);
-    }
-
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _Oval(BenchmarkInfo{
-    "oval",
-    "Draws 1 oval.",
-    TreeContentAnimation::run<OvalAnimation>
-});
-
-class PartialDamageTest : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        static SkColor COLORS[] = {
-                0xFFF44336,
-                0xFF9C27B0,
-                0xFF2196F3,
-                0xFF4CAF50,
-        };
-
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
-                        COLORS[static_cast<int>((y / dp(116))) % 4]);
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
-        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
-        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
-            canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
-                    SkXfermode::kSrcOver_Mode);
-        });
-    }
-
-    static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
-        int startA = (start >> 24) & 0xff;
-        int startR = (start >> 16) & 0xff;
-        int startG = (start >> 8) & 0xff;
-        int startB = start & 0xff;
-
-        int endA = (end >> 24) & 0xff;
-        int endR = (end >> 16) & 0xff;
-        int endG = (end >> 8) & 0xff;
-        int endB = end & 0xff;
-
-        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
-                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
-                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
-                (int)((startB + (int)(fraction * (endB - startB))));
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        recordNode(*node, [color](TestCanvas& canvas) {
-            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _PartialDamage(BenchmarkInfo{
-    "partialdamage",
-    "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
-    "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
-    "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
-    TreeContentAnimation::run<PartialDamageTest>
-});
-
-
-class SaveLayerAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-
-        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
-            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
-            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
-            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
-            canvas.restore();
-            canvas.restore();
-        });
-
-        canvas->drawRenderNode(card.get());
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _SaveLayer(BenchmarkInfo{
-    "savelayer",
-    "A nested pair of clipped saveLayer operations. "
-    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
-    TreeContentAnimation::run<SaveLayerAnimation>
-});
-
-
-class HwLayerAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
-        canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
-    }, TestUtils::getHwLayerSetupCallback());
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-        canvas->drawRenderNode(card.get());
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _HwLayer(BenchmarkInfo{
-    "hwlayer",
-    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
-    "Tests the hardware layer codepath.",
-    TreeContentAnimation::run<HwLayerAnimation>
-});
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index aee84de..48566e8 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -43,6 +43,8 @@
 static int gRepeatCount = 1;
 static std::vector<BenchmarkInfo> gRunTests;
 
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts);
+
 static void printHelp() {
     printf("\
 USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
@@ -186,7 +188,7 @@
     opts.count = gFrameCount;
     for (int i = 0; i < gRepeatCount; i++) {
         for (auto&& test : gRunTests) {
-            test.functor(opts);
+            run(test, opts);
         }
     }
     printf("Success!\n");
diff --git a/libs/hwui/tests/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
new file mode 100644
index 0000000..e316eca
--- /dev/null
+++ b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class HwLayerAnimation;
+
+static Benchmark _HwLayer(BenchmarkInfo{
+    "hwlayer",
+    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+    "Tests the hardware layer codepath.",
+    simpleCreateScene<HwLayerAnimation>
+});
+
+class HwLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](RenderProperties& props, TestCanvas& canvas) {
+            props.mutateLayerProperties().setType(LayerType::RenderLayer);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+        });
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+        canvas.drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
new file mode 100644
index 0000000..919a53d
--- /dev/null
+++ b/libs/hwui/tests/scenes/OvalAnimation.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class OvalAnimation;
+
+static Benchmark _Oval(BenchmarkInfo{
+    "oval",
+    "Draws 1 oval.",
+    simpleCreateScene<OvalAnimation>
+});
+
+class OvalAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setColor(0xFF000000);
+            canvas.drawOval(0, 0, 200, 200, paint);
+        });
+
+        canvas.drawRenderNode(card.get());
+        canvas.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
new file mode 100644
index 0000000..0fba4eb
--- /dev/null
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class PartialDamageAnimation;
+
+static Benchmark _PartialDamage(BenchmarkInfo{
+    "partialdamage",
+    "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
+    "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
+    "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
+    simpleCreateScene<PartialDamageAnimation>
+});
+
+class PartialDamageAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
+                sp<RenderNode> card = TestUtils::createNode(x, y,
+                        x + dp(100), y + dp(100),
+                        [color](TestCanvas& canvas) {
+                    canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+                });
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+            SkColor color = TestUtils::interpolateColor(
+                    curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
+            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/RecentsAnimation.cpp b/libs/hwui/tests/scenes/RecentsAnimation.cpp
new file mode 100644
index 0000000..1e38d84
--- /dev/null
+++ b/libs/hwui/tests/scenes/RecentsAnimation.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class RecentsAnimation;
+
+static Benchmark _Recents(BenchmarkInfo{
+    "recents",
+    "A recents-like scrolling list of textures. "
+    "Consists of updating a texture every frame",
+    simpleCreateScene<RecentsAnimation>
+});
+
+class RecentsAnimation : public TestScene {
+public:
+    void createContent(int width, int height, TestCanvas& renderer) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        thumbnailSize = std::min(std::min(width, height) / 2, 720);
+        int cardsize = std::min(width, height) - dp(64);
+
+        renderer.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        renderer.insertReorderBarrier(true);
+
+        int x = dp(32);
+        for (int i = 0; i < 4; i++) {
+            int y = (height / 4) * i;
+            SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
+            thumb.eraseColor(COLORS[i]);
+            sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+            card->mutateStagingProperties().setElevation(i * dp(8));
+            renderer.drawRenderNode(card.get());
+            mThumbnail = thumb;
+            mCards.push_back(card);
+        }
+
+        renderer.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < mCards.size(); ci++) {
+            mCards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            mCards[ci]->setPropertyFieldsDirty(RenderNode::Y);
+        }
+        mThumbnail.eraseColor(TestUtils::interpolateColor(
+                curFrame / 150.0f, 0xFF4CAF50, 0xFFFF5722));
+    }
+
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height,
+            const SkBitmap& thumb) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+            props.mutableOutline().setShouldClip(true);
+
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+            canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
+                    0, 0, width, height, nullptr);
+        });
+    }
+
+    SkBitmap mThumbnail;
+    std::vector< sp<RenderNode> > mCards;
+    int thumbnailSize;
+};
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
new file mode 100644
index 0000000..254f828
--- /dev/null
+++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "TestSceneBase.h"
+
+class RectGridAnimation;
+
+static Benchmark _RectGrid(BenchmarkInfo{
+    "rectgrid",
+    "A dense grid of 1x1 rects that should visually look like a single rect. "
+    "Low CPU/GPU load.",
+    simpleCreateScene<RectGridAnimation>
+});
+
+class RectGridAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(50, 50, 250, 250,
+                [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+            SkRegion region;
+            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+                }
+            }
+
+            SkPaint paint;
+            paint.setColor(0xff00ffff);
+            canvas.drawRegion(region, paint);
+        });
+        canvas.drawRenderNode(card.get());
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
new file mode 100644
index 0000000..c62dd19
--- /dev/null
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class SaveLayerAnimation;
+
+static Benchmark _SaveLayer(BenchmarkInfo{
+    "savelayer",
+    "A nested pair of clipped saveLayer operations. "
+    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+    simpleCreateScene<SaveLayerAnimation>
+});
+
+class SaveLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](TestCanvas& canvas) {
+            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+            canvas.restore();
+            canvas.restore();
+        });
+
+        canvas.drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
new file mode 100644
index 0000000..26c86aa
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGrid2Animation;
+
+static Benchmark _ShadowGrid2(BenchmarkInfo{
+    "shadowgrid2",
+    "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
+    "variant of shadowgrid. Very high CPU load, high GPU load.",
+    simpleCreateScene<ShadowGrid2Animation>
+});
+
+class ShadowGrid2Animation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
+            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
+                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
new file mode 100644
index 0000000..ee3c590
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGridAnimation;
+
+static Benchmark _ShadowGrid(BenchmarkInfo{
+    "shadowgrid",
+    "A grid of rounded rects that cast a shadow. Simplified scenario of an "
+    "Android TV-style launcher interface. High CPU/GPU load.",
+    simpleCreateScene<ShadowGridAnimation>
+});
+
+class ShadowGridAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/TestSceneBase.h b/libs/hwui/tests/scenes/TestSceneBase.h
new file mode 100644
index 0000000..a208509
--- /dev/null
+++ b/libs/hwui/tests/scenes/TestSceneBase.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTS_SCENES_TESTSCENEBASE_H
+#define TESTS_SCENES_TESTSCENEBASE_H
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "tests/Benchmark.h"
+#include "tests/TestContext.h"
+#include "tests/TestScene.h"
+#include "utils/TestUtils.h"
+
+#include <functional>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 4e00fb3..7ad2f9b 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -18,7 +18,7 @@
 
 #include <BakedOpState.h>
 #include <RecordedOp.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index 3ef329a..c6ccf4d 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/FatVector.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index ef205ec..05fd08a 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -19,7 +19,7 @@
 #include <LayerUpdateQueue.h>
 #include <RenderNode.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 0f6b249..0591db6 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/LinearAllocator.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
index ba92157..de86aed 100644
--- a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <renderstate/OffscreenBufferPool.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07a1855..ec8048d 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -21,7 +21,7 @@
 #include <OpReorderer.h>
 #include <RecordedOp.h>
 #include <RecordingCanvas.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 #include <unordered_map>
 
@@ -186,14 +186,14 @@
         }
     };
 
-    sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+    sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
     });
 
     RenderNode* childPtr = child.get();
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
         SkPaint paint;
         paint.setColor(SK_ColorDKGRAY);
         canvas.drawRect(0, 0, 200, 200, paint);
@@ -221,7 +221,7 @@
         }
     };
 
-    sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
+    sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
         canvas.drawBitmap(bitmap, 0, 0, nullptr);
     });
@@ -396,11 +396,13 @@
         }
     };
 
-    sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+    sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    }, TestUtils::getHwLayerSetupCallback());
+    });
     OffscreenBuffer** layerHandle = node->getLayerHandle();
 
     // create RenderNode's layer here in same way prepareTree would
@@ -483,18 +485,20 @@
         }
     };
 
-    auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
-            [](RecordingCanvas& canvas) {
+    auto child = TestUtils::createNode(50, 50, 150, 150,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    }, TestUtils::getHwLayerSetupCallback());
+    });
     OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
     *(child->getLayerHandle()) = &childLayer;
 
     RenderNode* childPtr = child.get();
-    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
-            [childPtr](RecordingCanvas& canvas) {
+    auto parent = TestUtils::createNode(0, 0, 200, 200,
+            [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         SkPaint paint;
         paint.setColor(SK_ColorDKGRAY);
         canvas.drawRect(0, 0, 200, 200, paint);
@@ -502,7 +506,7 @@
         canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
         canvas.drawRenderNode(childPtr);
         canvas.restore();
-    }, TestUtils::getHwLayerSetupCallback());
+    });
     OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
     *(parent->getLayerHandle()) = &parentLayer;
 
@@ -529,7 +533,7 @@
     canvas->drawRect(0, 0, 100, 100, paint);
 }
 static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
-    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+    auto node = TestUtils::createNode(0, 0, 100, 100,
             [expectedDrawOrder](RecordingCanvas& canvas) {
         drawOrderedRect(&canvas, expectedDrawOrder);
     });
@@ -546,7 +550,7 @@
         }
     };
 
-    auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+    auto parent = TestUtils::createNode(0, 0, 100, 100,
             [](RecordingCanvas& canvas) {
         drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
         drawOrderedRect(&canvas, 1);
@@ -570,15 +574,13 @@
 
 // creates a 100x100 shadow casting node with provided translationZ
 static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
-    return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
-            [](RecordingCanvas& canvas) {
+    return TestUtils::createNode(0, 0, 100, 100,
+            [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setTranslationZ(translationZ);
+        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    }, [translationZ] (RenderProperties& properties) {
-        properties.setTranslationZ(translationZ);
-        properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
-        return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
     });
 }
 
@@ -600,7 +602,7 @@
         }
     };
 
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
             [] (RecordingCanvas& canvas) {
         canvas.insertReorderBarrier(true);
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
@@ -636,7 +638,7 @@
         }
     };
 
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
             [] (RecordingCanvas& canvas) {
         // save/restore outside of reorderBarrier, so they don't get moved out of place
         canvas.translate(20, 10);
@@ -676,14 +678,15 @@
         }
     };
 
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
-            [] (RecordingCanvas& canvas) {
+    sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        props.mutateLayerProperties().setType(LayerType::RenderLayer);
         canvas.insertReorderBarrier(true);
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.translate(20, 10);
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
         canvas.restore();
-    }, TestUtils::getHwLayerSetupCallback());
+    });
     OffscreenBuffer** layerHandle = parent->getLayerHandle();
 
     // create RenderNode's layer here in same way prepareTree would, setting windowTransform
@@ -718,7 +721,7 @@
             EXPECT_TRUE(index == 2 || index == 3);
         }
     };
-    sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+    sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
             [] (RecordingCanvas& canvas) {
         canvas.insertReorderBarrier(true);
         canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
@@ -732,7 +735,7 @@
     EXPECT_EQ(4, renderer.getIndex());
 }
 
-static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
+static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
         std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
     class PropertyTestRenderer : public TestRendererBase {
     public:
@@ -745,11 +748,13 @@
         std::function<void(const RectOp&, const BakedOpState&)> mCallback;
     };
 
-    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
+    auto node = TestUtils::createNode(0, 0, 100, 100,
+            [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) {
+        propSetupCallback(props);
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 100, 100, paint);
-    }, propSetupCallback);
+    });
 
     OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
             createSyncedNodeList(node), sLightCenter);
@@ -762,7 +767,6 @@
     testProperty([](RenderProperties& properties) {
         properties.setAlpha(0.5f);
         properties.setHasOverlappingRendering(false);
-        return RenderNode::ALPHA | RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
     });
@@ -772,7 +776,6 @@
     testProperty([](RenderProperties& properties) {
         properties.setClipToBounds(true);
         properties.setClipBounds(Rect(10, 20, 300, 400));
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
                 << "Clip rect should be intersection of node bounds and clip bounds";
@@ -782,7 +785,6 @@
 TEST(OpReorderer, renderPropRevealClip) {
     testProperty([](RenderProperties& properties) {
         properties.mutableRevealClip().set(true, 50, 50, 25);
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         ASSERT_NE(nullptr, state.roundRectClipState);
         EXPECT_TRUE(state.roundRectClipState->highPriority);
@@ -795,7 +797,6 @@
     testProperty([](RenderProperties& properties) {
         properties.mutableOutline().setShouldClip(true);
         properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
-        return RenderNode::GENERIC;
     }, [](const RectOp& op, const BakedOpState& state) {
         ASSERT_NE(nullptr, state.roundRectClipState);
         EXPECT_FALSE(state.roundRectClipState->highPriority);
@@ -819,9 +820,6 @@
         properties.setTranslationY(20);
         properties.setScaleX(0.5f);
         properties.setScaleY(0.7f);
-        return RenderNode::GENERIC
-                | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
-                | RenderNode::SCALE_X | RenderNode::SCALE_Y;
     }, [](const RectOp& op, const BakedOpState& state) {
         Matrix4 matrix;
         matrix.loadTranslate(10, 10, 0); // left, top
@@ -857,7 +855,7 @@
  * (for efficiency, and to fit in layer size constraints) based on parent clip.
  */
 void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
-        TestUtils::PropSetupCallback propSetupCallback) {
+        std::function<void(RenderProperties&)> propSetupCallback) {
     class SaveLayerAlphaClipTestRenderer : public TestRendererBase {
     public:
         SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData)
@@ -887,17 +885,16 @@
 
     ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize())
             << "Node must be bigger than max texture size to exercise saveLayer codepath";
-    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 10000, 10000, [](RecordingCanvas& canvas) {
+    auto node = TestUtils::createNode(0, 0, 10000, 10000,
+            [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setHasOverlappingRendering(true);
+        properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
+        // apply other properties
+        propSetupCallback(properties);
+
         SkPaint paint;
         paint.setColor(SK_ColorWHITE);
         canvas.drawRect(0, 0, 10000, 10000, paint);
-    }, [&propSetupCallback](RenderProperties& properties) {
-        properties.setHasOverlappingRendering(true);
-        properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
-
-        // apply other properties
-        int flags = propSetupCallback(properties);
-        return RenderNode::GENERIC | RenderNode::ALPHA | flags;
     });
     auto nodes = createSyncedNodeList(node); // sync before querying height
 
@@ -914,7 +911,6 @@
     testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
         properties.setTranslationX(10); // offset rendering content
         properties.setTranslationY(-2000); // offset rendering content
-        return RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y;
     });
     EXPECT_EQ(190u, observedData.layerWidth);
     EXPECT_EQ(200u, observedData.layerHeight);
@@ -937,9 +933,6 @@
         properties.setPivotX(0);
         properties.setPivotY(0);
         properties.setRotation(45);
-        return RenderNode::GENERIC
-                | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
-                | RenderNode::ROTATION;
     });
     // ceil(sqrt(2) / 2 * 200) = 142
     EXPECT_EQ(142u, observedData.layerWidth);
@@ -955,7 +948,6 @@
         properties.setPivotY(0);
         properties.setScaleX(2);
         properties.setScaleY(0.5f);
-        return RenderNode::GENERIC | RenderNode::SCALE_X | RenderNode::SCALE_Y;
     });
     EXPECT_EQ(100u, observedData.layerWidth);
     EXPECT_EQ(400u, observedData.layerHeight);
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 83b37ab..22190f5 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -18,7 +18,7 @@
 
 #include <RecordedOp.h>
 #include <RecordingCanvas.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
new file mode 100644
index 0000000..84230a7
--- /dev/null
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) {
+    int startA = (start >> 24) & 0xff;
+    int startR = (start >> 16) & 0xff;
+    int startG = (start >> 8) & 0xff;
+    int startB = start & 0xff;
+
+    int endA = (end >> 24) & 0xff;
+    int endR = (end >> 16) & 0xff;
+    int endG = (end >> 8) & 0xff;
+    int endB = end & 0xff;
+
+    return (int)((startA + (int)(fraction * (endA - startA))) << 24)
+            | (int)((startR + (int)(fraction * (endR - startR))) << 16)
+            | (int)((startG + (int)(fraction * (endG - startG))) << 8)
+            | (int)((startB + (int)(fraction * (endB - startB))));
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/utils/TestUtils.h
similarity index 76%
rename from libs/hwui/unit_tests/TestUtils.h
rename to libs/hwui/utils/TestUtils.h
index 38bafd5..f7f4f2d 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -27,8 +27,10 @@
 
 #if HWUI_NEW_OPS
 #include <RecordedOp.h>
+#include <RecordingCanvas.h>
 #else
 #include <DisplayListOp.h>
+#include <DisplayListCanvas.h>
 #endif
 
 #include <memory>
@@ -36,6 +38,12 @@
 namespace android {
 namespace uirenderer {
 
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
 #define EXPECT_MATRIX_APPROX_EQ(a, b) \
     EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
 
@@ -97,7 +105,8 @@
 
     static SkBitmap createSkBitmap(int width, int height) {
         SkBitmap bitmap;
-        SkImageInfo info = SkImageInfo::MakeUnknown(width, height);
+        SkImageInfo info = SkImageInfo::Make(width, height,
+                kN32_SkColorType, kPremul_SkAlphaType);
         bitmap.setInfo(info);
         bitmap.allocPixels(info);
         return bitmap;
@@ -111,18 +120,8 @@
         return std::unique_ptr<DisplayList>(canvas.finishRecording());
     }
 
-    typedef std::function<int(RenderProperties&)> PropSetupCallback;
-
-    static PropSetupCallback getHwLayerSetupCallback() {
-        static PropSetupCallback sLayerSetupCallback = [] (RenderProperties& properties) {
-            properties.mutateLayerProperties().setType(LayerType::RenderLayer);
-            return RenderNode::GENERIC;
-        };
-        return sLayerSetupCallback;
-    }
-
     static sp<RenderNode> createNode(int left, int top, int right, int bottom,
-            PropSetupCallback propSetupCallback = nullptr) {
+            std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
 #if HWUI_NULL_GPU
         // if RenderNodes are being sync'd/used, device info will be needed, since
         // DeviceInfo::maxTextureSize() affects layer property
@@ -130,25 +129,39 @@
 #endif
 
         sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        if (propSetupCallback) {
-            node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
+        RenderProperties& props = node->mutateStagingProperties();
+        props.setLeftTopRightBottom(left, top, right, bottom);
+        if (setup) {
+            TestCanvas canvas(props.getWidth(), props.getHeight());
+            setup(props, canvas);
+            node->setStagingDisplayList(canvas.finishRecording());
         }
+        node->setPropertyFieldsDirty(0xFFFFFFFF);
         return node;
     }
 
-    template<class CanvasType>
     static sp<RenderNode> createNode(int left, int top, int right, int bottom,
-            std::function<void(CanvasType& canvas)> canvasCallback,
-            PropSetupCallback propSetupCallback = nullptr) {
-        sp<RenderNode> node = createNode(left, top, right, bottom, propSetupCallback);
+            std::function<void(RenderProperties& props)> setup) {
+        return createNode(left, top, right, bottom,
+                [&setup](RenderProperties& props, TestCanvas& canvas) {
+            setup(props);
+        });
+    }
 
-        auto&& props = node->stagingProperties(); // staging, since not sync'd yet
-        CanvasType canvas(props.getWidth(), props.getHeight());
-        canvasCallback(canvas);
-        node->setStagingDisplayList(canvas.finishRecording());
-        return node;
+    static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+            std::function<void(TestCanvas& canvas)> setup) {
+        return createNode(left, top, right, bottom,
+                [&setup](RenderProperties& props, TestCanvas& canvas) {
+            setup(canvas);
+        });
+    }
+
+    static void recordNode(RenderNode& node,
+            std::function<void(TestCanvas&)> contentCallback) {
+       TestCanvas canvas(node.stagingProperties().getWidth(),
+               node.stagingProperties().getHeight());
+       contentCallback(canvas);
+       node.setStagingDisplayList(canvas.finishRecording());
     }
 
     static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
@@ -180,6 +193,9 @@
         TestTask task(rtCallback);
         renderthread::RenderThread::getInstance().queueAndWait(&task);
     }
+
+    static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
         node->syncProperties();
diff --git a/packages/DocumentsUI/res/drawable/item_root_background.xml b/packages/DocumentsUI/res/drawable/item_root_background.xml
deleted file mode 100644
index c403159..0000000
--- a/packages/DocumentsUI/res/drawable/item_root_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true" android:state_activated="true">
-        <color android:color="@color/material_grey_300" />
-    </item>
-    <item android:state_focused="false" android:state_activated="true">
-        <color android:color="@color/material_grey_300" />
-    </item>
-</selector>
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index 381e1c89..fe06eaf 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -17,7 +17,7 @@
 <com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/item_doc_list_background"
+    android:background="@color/item_doc_background"
     android:orientation="horizontal"
     android:focusable="true">
 
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index 0dac0d5..0146f14 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -61,7 +61,7 @@
             android:layout_gravity="start"
             android:orientation="vertical"
             android:elevation="16dp"
-            android:background="@*android:color/white">
+            android:background="@color/window_background">
 
             <Toolbar
                 android:id="@+id/roots_toolbar"
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 403c667..3135977 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -50,9 +50,7 @@
             android:layout_height="0dp"
             android:layout_weight="1"
             android:orientation="horizontal"
-            android:baselineAligned="false"
-            android:divider="?android:attr/dividerVertical"
-            android:showDividers="middle">
+            android:baselineAligned="false">
 
             <FrameLayout
                 android:id="@+id/container_roots"
@@ -62,8 +60,7 @@
             <include layout="@layout/directory_cluster"
                 android:layout_width="0dp"
                 android:layout_weight="1"
-                android:elevation="8dp"
-                android:background="@color/material_grey_50" />
+                android:elevation="8dp" />
 
         </LinearLayout>
 
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index ada7f49..f9bbccb 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -17,7 +17,6 @@
 <com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@color/material_grey_50"
     android:orientation="vertical"
     android:animateLayoutChanges="true">
 
@@ -78,8 +77,7 @@
             android:paddingBottom="0dp"
             android:clipToPadding="false"
             android:scrollbarStyle="outsideOverlay"
-            android:drawSelectorOnTop="true"
-            android:background="@color/directory_background" />
+            android:drawSelectorOnTop="true" />
 
     </FrameLayout>
 
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index 1dfb34a..dcd5cfd 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -18,101 +18,101 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_margin="@dimen/grid_item_margin"
-    android:background="@color/item_doc_grid_background"
+    android:background="@color/item_doc_background"
     android:focusable="true">
 
     <!-- Main item thumbnail.  Comprised of two overlapping images, the
          visibility of which is controlled by code in
          DirectoryFragment.java. -->
+
     <FrameLayout
         android:id="@+id/thumbnail"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingBottom="8dp">
-      
+        android:layout_height="wrap_content">
+
         <com.android.documentsui.GridItemThumbnail
             android:id="@+id/icon_thumb"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:scaleType="centerCrop"
             android:contentDescription="@null" />
-  
-        <ImageView
+
+        <com.android.documentsui.GridItemThumbnail
             android:id="@+id/icon_mime"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:scaleType="centerInside"
             android:contentDescription="@null" />
-        
+
     </FrameLayout>
-  
+
     <!-- Item nameplate.  Has a mime-type icon and some text fields (title,
          size, mod-time, etc). -->
-    <TextView
-        android:id="@android:id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/thumbnail"
-        android:layout_toEndOf="@android:id/icon1"
-        android:singleLine="true"
-        android:ellipsize="middle"
-        android:textAlignment="viewStart"
-        android:paddingEnd="12dp"
-        android:textAppearance="@android:style/TextAppearance.Material.Subhead"
-        android:textColor="@*android:color/primary_text_default_material_light" />
 
-    <TextView
-        android:id="@+id/size"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@android:id/title"
-        android:layout_toEndOf="@android:id/icon1"
-        android:paddingEnd="4dp"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Caption"
-        android:textColor="@*android:color/primary_text_default_material_light" />
-
-    <TextView
-        android:id="@+id/date"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@android:id/title"
-        android:layout_toEndOf="@id/size"
-        android:paddingEnd="12dp"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:textAlignment="viewStart"
-        android:textAppearance="@android:style/TextAppearance.Material.Caption"
-        android:textColor="@*android:color/primary_text_default_material_light" />
-    
-    <ImageView
-        android:id="@android:id/icon1"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:layout_below="@id/thumbnail"
-        android:layout_alignParentLeft="true"
-        android:layout_alignBottom="@id/size"
-        android:layout_alignTop="@android:id/title"
-        android:scaleType="centerInside"
-        android:contentDescription="@null"
-        android:paddingStart="12dp"
-        android:paddingEnd="8dp"/>
-
-    <!-- Use an explicit spacer so we can align things to it. -->
-    <Space
-        android:id="@+id/bottomPadding"
+    <RelativeLayout
+        android:id="@+id/nameplate"
         android:layout_width="match_parent"
-        android:layout_height="8dp"
-        android:layout_below="@id/size" />
+        android:layout_height="wrap_content"
+        android:layout_below="@id/thumbnail"
+        android:paddingTop="8dp"
+        android:paddingBottom="8dp"
+        android:paddingLeft="12dp"
+        android:paddingRight="12dp">
+
+        <ImageView
+            android:id="@android:id/icon1"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="8dp"
+            android:layout_alignParentStart="true"
+            android:layout_centerVertical="true"
+            android:scaleType="centerInside"
+            android:contentDescription="@null"/>
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_toEndOf="@android:id/icon1"
+            android:singleLine="true"
+            android:ellipsize="middle"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+            android:textColor="@*android:color/primary_text_default_material_light" />
+
+        <TextView
+            android:id="@+id/size"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_toEndOf="@android:id/icon1"
+            android:layout_below="@android:id/title"
+            android:layout_marginEnd="4dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption"
+            android:textColor="@*android:color/primary_text_default_material_light" />
+
+        <TextView
+            android:id="@+id/date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_toEndOf="@id/size"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAlignment="viewStart"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption"
+            android:textColor="@*android:color/primary_text_default_material_light" />
+
+    </RelativeLayout>
 
     <!-- An overlay that draws the item border when it is focused. -->
     <View
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignBottom="@id/bottomPadding"
+        android:layout_alignBottom="@id/nameplate"
         android:layout_alignTop="@id/thumbnail"
         android:layout_alignLeft="@id/thumbnail"
         android:layout_alignRight="@id/thumbnail"
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index c409166..e068423 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -17,10 +17,10 @@
 <com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/item_doc_list_background"
+    android:background="@color/item_doc_background"
     android:orientation="horizontal"
     android:focusable="true">
-  
+
     <View
         android:id="@+id/focus_indicator"
         android:layout_width="4dp"
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 90b1ff6..ff80d07 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -22,8 +22,7 @@
     android:paddingEnd="@dimen/list_item_padding"
     android:gravity="center_vertical"
     android:orientation="horizontal"
-    android:baselineAligned="false"
-    android:background="@drawable/item_root_background">
+    android:baselineAligned="false">
 
     <FrameLayout
         android:layout_width="@dimen/icon_size"
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 68c8b65..153c673 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -17,14 +17,20 @@
 <resources>
     <color name="material_grey_400">#ffbdbdbd</color>
 
+    <!-- This is the window background, but also the background for anything
+         else that needs to manually declare a background matching the "default"
+         app background (e.g. the drawer overlay). -->
+    <color name="window_background">#fff1f1f1</color>
+
     <color name="primary_dark">@*android:color/primary_dark_material_dark</color>
     <color name="primary">@*android:color/material_blue_grey_900</color>
     <color name="accent">@*android:color/accent_material_light</color>
     <color name="action_mode">@color/material_grey_400</color>
-    
-    <color name="directory_background">@*android:color/material_grey_300</color>
-    <color name="item_doc_grid_background">@android:color/white</color>
-    <color name="item_doc_grid_protect_background">@android:color/white</color>
+
     <color name="band_select_background">#88ffffff</color>
     <color name="band_select_border">#44000000</color>
+
+    <color name="item_doc_background">#fffafafa</color>
+    <color name="item_doc_background_selected">#ffe0f2f1</color>
+
 </resources>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index 15d17cc..6712e2d 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -25,6 +25,7 @@
         <item name="actionBarTheme">@style/ActionBarTheme</item>
         <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
 
+        <item name="android:windowBackground">@color/window_background</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorAccent">@color/accent</item>
@@ -44,6 +45,7 @@
         <item name="actionBarTheme">@style/ActionBarTheme</item>
         <item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
 
+        <item name="android:windowBackground">@color/window_background</item>
         <item name="android:colorPrimaryDark">@color/primary_dark</item>
         <item name="android:colorPrimary">@color/primary</item>
         <item name="android:colorAccent">@color/accent</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 18dd8c8..8b3893f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -943,7 +943,6 @@
 
         public void setSelected(boolean selected) {
             itemView.setActivated(selected);
-            itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
         }
 
         @Override
@@ -1080,8 +1079,6 @@
 
             holder.setSelected(isSelected(position));
 
-            final View line2 = itemView.findViewById(R.id.line2);
-
             final ImageView iconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
             final ImageView iconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
             final TextView title = (TextView) itemView.findViewById(android.R.id.title);
@@ -1138,14 +1135,11 @@
                         getDocumentIcon(mContext, docAuthority, docId, docMimeType, docIcon, state));
             }
 
-            boolean hasLine2 = false;
-
-            final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles;
-            if (!hideTitle) {
+            if ((state.derivedMode == MODE_GRID) && mHideGridTitles) {
+                title.setVisibility(View.GONE);
+            } else {
                 title.setText(docDisplayName);
                 title.setVisibility(View.VISIBLE);
-            } else {
-                title.setVisibility(View.GONE);
             }
 
             Drawable iconDrawable = null;
@@ -1161,7 +1155,6 @@
                     if (alwaysShowSummary) {
                         summary.setText(root.getDirectoryString());
                         summary.setVisibility(View.VISIBLE);
-                        hasLine2 = true;
                     } else {
                         if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
                             // No summary needed if icon speaks for itself
@@ -1170,7 +1163,6 @@
                             summary.setText(root.getDirectoryString());
                             summary.setVisibility(View.VISIBLE);
                             summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
-                            hasLine2 = true;
                         }
                     }
                 }
@@ -1187,48 +1179,37 @@
                     if (docSummary != null) {
                         summary.setText(docSummary);
                         summary.setVisibility(View.VISIBLE);
-                        hasLine2 = true;
                     } else {
                         summary.setVisibility(View.INVISIBLE);
                     }
                 }
             }
 
-            if (icon1 != null) icon1.setVisibility(View.GONE);
-
             if (iconDrawable != null) {
                 icon1.setVisibility(View.VISIBLE);
                 icon1.setImageDrawable(iconDrawable);
+            } else {
+                icon1.setVisibility(View.GONE);
             }
 
             if (docLastModified == -1) {
                 date.setText(null);
             } else {
                 date.setText(formatTime(mContext, docLastModified));
-                hasLine2 = true;
             }
 
-            if (state.showSize) {
-                size.setVisibility(View.VISIBLE);
-                if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
-                    size.setText(null);
-                } else {
-                    size.setText(Formatter.formatFileSize(mContext, docSize));
-                    hasLine2 = true;
-                }
-            } else {
+            if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
                 size.setVisibility(View.GONE);
-            }
-
-            if (line2 != null) {
-                line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
+            } else {
+                size.setVisibility(View.VISIBLE);
+                size.setText(Formatter.formatFileSize(mContext, docSize));
             }
 
             setEnabledRecursive(itemView, enabled);
 
             iconMime.setAlpha(iconAlpha);
             iconThumb.setAlpha(iconAlpha);
-            if (icon1 != null) icon1.setAlpha(iconAlpha);
+            icon1.setAlpha(iconAlpha);
 
             if (DEBUG_ENABLE_DND) {
                 setupDragAndDropOnDocumentView(itemView, cursor);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
index 0963845..1135c21 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
@@ -25,6 +25,8 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.TypedValue;
 
+import com.android.documentsui.R;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -43,12 +45,8 @@
     private final Integer mSelectedColor;
 
     public DirectoryItemAnimator(Context context) {
-        mDefaultColor = context.getResources().getColor(android.R.color.transparent);
-        // Get the accent color.
-        TypedValue selColor = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
-        // Set the opacity to 10%.
-        mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+        mDefaultColor = context.getResources().getColor(R.color.item_doc_background);
+        mSelectedColor = context.getResources().getColor(R.color.item_doc_background_selected);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index 66ece52..f3c66a5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -17,6 +17,8 @@
 package com.android.systemui.recents.views;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -47,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
@@ -243,8 +246,7 @@
         // Ensure we have a valid target stack id
         final int targetStackId = destinationStack != INVALID_STACK_ID ?
                 destinationStack : task.key.stackId;
-        if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
-                && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
+        if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
             return null;
         }
 
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index c131628..a5cef1a 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -47,7 +47,9 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.os.storage.MountServiceInternal;
 import android.util.ArrayMap;
@@ -1554,15 +1556,300 @@
         }
     }
 
-    private void dumpHelp(PrintWriter pw) {
-        pw.println("AppOps service (appops) dump options:");
-        pw.println("  [-h] [CMD]");
-        pw.println("  -h: print this help text.");
-        pw.println("Commands:");
+    static class Shell extends ShellCommand {
+        final IAppOpsService mInterface;
+        final AppOpsService mInternal;
+
+        int userId = UserHandle.USER_SYSTEM;
+        String packageName;
+        String opStr;
+        int op;
+        int packageUid;
+
+        Shell(IAppOpsService iface, AppOpsService internal) {
+            mInterface = iface;
+            mInternal = internal;
+        }
+
+        @Override
+        public int onCommand(String cmd) {
+            return onShellCommand(this, cmd);
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            dumpCommandHelp(pw);
+        }
+
+        private int strOpToOp(String op, PrintWriter err) {
+            try {
+                return AppOpsManager.strOpToOp(op);
+            } catch (IllegalArgumentException e) {
+            }
+            try {
+                return Integer.parseInt(op);
+            } catch (NumberFormatException e) {
+            }
+            try {
+                return AppOpsManager.strDebugOpToOp(op);
+            } catch (IllegalArgumentException e) {
+                err.println("Error: " + e.getMessage());
+                return -1;
+            }
+        }
+
+        int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException {
+            userId = UserHandle.USER_CURRENT;
+            packageName = null;
+            opStr = null;
+            for (String argument; (argument = getNextArg()) != null;) {
+                if ("--user".equals(argument)) {
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                } else {
+                    if (packageName == null) {
+                        packageName = argument;
+                    } else if (opStr == null) {
+                        opStr = argument;
+                        break;
+                    }
+                }
+            }
+            if (packageName == null) {
+                err.println("Error: Package name not specified.");
+                return -1;
+            } else if (opStr == null && reqOp) {
+                err.println("Error: Operation not specified.");
+                return -1;
+            }
+            if (opStr != null) {
+                op = strOpToOp(opStr, err);
+                if (op < 0) {
+                    return -1;
+                }
+            } else {
+                op = AppOpsManager.OP_NONE;
+            }
+            if (userId == UserHandle.USER_CURRENT) {
+                userId = ActivityManager.getCurrentUser();
+            }
+            if ("root".equals(packageName)) {
+                packageUid = 0;
+            } else {
+                packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, userId);
+            }
+            if (packageUid < 0) {
+                err.println("Error: No UID for " + packageName + " in user " + userId);
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+        (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver);
+    }
+
+    static void dumpCommandHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println("  set [--user <USER_ID>] <PACKAGE> <OP> <MODE>");
+        pw.println("    Set the mode for a particular application and operation.");
+        pw.println("  get [--user <USER_ID>] <PACKAGE> [<OP>]");
+        pw.println("    Return the mode for a particular application and optional operation.");
+        pw.println("  reset [--user <USER_ID>] [<PACKAGE>]");
+        pw.println("    Reset the given application or all applications to default modes.");
         pw.println("  write-settings");
         pw.println("    Immediately write pending changes to storage.");
         pw.println("  read-settings");
         pw.println("    Read the last written settings, replacing current state in RAM.");
+        pw.println("  options:");
+        pw.println("    <PACKAGE> an Android package name.");
+        pw.println("    <OP>      an AppOps operation.");
+        pw.println("    <MODE>    one of allow, ignore, deny, or default");
+        pw.println("    <USER_ID> the user id under which the package is installed. If --user is not");
+        pw.println("              specified, the current user is assumed.");
+    }
+
+    static int onShellCommand(Shell shell, String cmd) {
+        if (cmd == null) {
+            return shell.handleDefaultCommands(cmd);
+        }
+        PrintWriter pw = shell.getOutPrintWriter();
+        PrintWriter err = shell.getErrPrintWriter();
+        try {
+            switch (cmd) {
+                case "set": {
+                    int res = shell.parseUserPackageOp(true, err);
+                    if (res < 0) {
+                        return res;
+                    }
+                    String modeStr = shell.getNextArg();
+                    if (modeStr == null) {
+                        err.println("Error: Mode not specified.");
+                        return -1;
+                    }
+
+                    final int mode;
+                    switch (modeStr) {
+                        case "allow":
+                            mode = AppOpsManager.MODE_ALLOWED;
+                            break;
+                        case "deny":
+                            mode = AppOpsManager.MODE_ERRORED;
+                            break;
+                        case "ignore":
+                            mode = AppOpsManager.MODE_IGNORED;
+                            break;
+                        case "default":
+                            mode = AppOpsManager.MODE_DEFAULT;
+                            break;
+                        default:
+                            err.println("Error: Mode " + modeStr + " is not valid,");
+                            return -1;
+                    }
+
+                    shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode);
+                    return 0;
+                }
+                case "get": {
+                    int res = shell.parseUserPackageOp(false, err);
+                    if (res < 0) {
+                        return res;
+                    }
+
+                    List<AppOpsManager.PackageOps> ops = shell.mInterface.getOpsForPackage(
+                            shell.packageUid, shell.packageName,
+                            shell.op != AppOpsManager.OP_NONE ? new int[] {shell.op} : null);
+                    if (ops == null || ops.size() <= 0) {
+                        pw.println("No operations.");
+                        return 0;
+                    }
+                    final long now = System.currentTimeMillis();
+                    for (int i=0; i<ops.size(); i++) {
+                        List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+                        for (int j=0; j<entries.size(); j++) {
+                            AppOpsManager.OpEntry ent = entries.get(j);
+                            pw.print(AppOpsManager.opToName(ent.getOp()));
+                            pw.print(": ");
+                            switch (ent.getMode()) {
+                                case AppOpsManager.MODE_ALLOWED:
+                                    pw.print("allow");
+                                    break;
+                                case AppOpsManager.MODE_IGNORED:
+                                    pw.print("ignore");
+                                    break;
+                                case AppOpsManager.MODE_ERRORED:
+                                    pw.print("deny");
+                                    break;
+                                case AppOpsManager.MODE_DEFAULT:
+                                    pw.print("default");
+                                    break;
+                                default:
+                                    pw.print("mode=");
+                                    pw.print(ent.getMode());
+                                    break;
+                            }
+                            if (ent.getTime() != 0) {
+                                pw.print("; time=");
+                                TimeUtils.formatDuration(now - ent.getTime(), pw);
+                                pw.print(" ago");
+                            }
+                            if (ent.getRejectTime() != 0) {
+                                pw.print("; rejectTime=");
+                                TimeUtils.formatDuration(now - ent.getRejectTime(), pw);
+                                pw.print(" ago");
+                            }
+                            if (ent.getDuration() == -1) {
+                                pw.print(" (running)");
+                            } else if (ent.getDuration() != 0) {
+                                pw.print("; duration=");
+                                TimeUtils.formatDuration(ent.getDuration(), pw);
+                            }
+                            pw.println();
+                        }
+                    }
+                    return 0;
+                }
+                case "reset": {
+                    String packageName = null;
+                    int userId = UserHandle.USER_CURRENT;
+                    for (String argument; (argument = shell.getNextArg()) != null;) {
+                        if ("--user".equals(argument)) {
+                            String userStr = shell.getNextArgRequired();
+                            userId = UserHandle.parseUserArg(userStr);
+                        } else {
+                            if (packageName == null) {
+                                packageName = argument;
+                            } else {
+                                err.println("Error: Unsupported argument: " + argument);
+                                return -1;
+                            }
+                        }
+                    }
+
+                    if (userId == UserHandle.USER_CURRENT) {
+                        userId = ActivityManager.getCurrentUser();
+                    }
+
+                    shell.mInterface.resetAllModes(userId, packageName);
+                    pw.print("Reset all modes for: ");
+                    if (userId == UserHandle.USER_ALL) {
+                        pw.print("all users");
+                    } else {
+                        pw.print("user "); pw.print(userId);
+                    }
+                    pw.print(", ");
+                    if (packageName == null) {
+                        pw.println("all packages");
+                    } else {
+                        pw.print("package "); pw.println(packageName);
+                    }
+                    return 0;
+                }
+                case "write-settings": {
+                    shell.mInternal.mContext.enforcePermission(
+                            android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        synchronized (shell.mInternal) {
+                            shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
+                        }
+                        shell.mInternal.writeState();
+                        pw.println("Current settings written.");
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    return 0;
+                }
+                case "read-settings": {
+                    shell.mInternal.mContext.enforcePermission(
+                            android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                            Binder.getCallingPid(), Binder.getCallingUid(), null);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        shell.mInternal.readState();
+                        pw.println("Last settings read.");
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    return 0;
+                }
+                default:
+                    return shell.handleDefaultCommands(cmd);
+            }
+        } catch (RemoteException e) {
+            pw.println("Remote exception: " + e);
+        }
+        return -1;
+    }
+
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("AppOps service (appops) dump options:");
+        pw.println("  none");
     }
 
     @Override
@@ -1583,27 +1870,6 @@
                     return;
                 } else if ("-a".equals(arg)) {
                     // dump all data
-                } else if ("write-settings".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        synchronized (this) {
-                            mHandler.removeCallbacks(mWriteRunner);
-                        }
-                        writeState();
-                        pw.println("Current settings written.");
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
-                } else if ("read-settings".equals(arg)) {
-                    long token = Binder.clearCallingIdentity();
-                    try {
-                        readState();
-                        pw.println("Last settings read.");
-                    } finally {
-                        Binder.restoreCallingIdentity(token);
-                    }
-                    return;
                 } else if (arg.length() > 0 && arg.charAt(0) == '-'){
                     pw.println("Unknown option: " + arg);
                     return;
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 485e26b..f5ed83e 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -107,6 +107,8 @@
 
     private static final boolean COMPRESS_TIME = false;
 
+    private static final int EVENT_BUFFER_SIZE = 40;
+
     private static final String ACTION_STEP_IDLE_STATE =
             "com.android.server.device_idle.STEP_IDLE_STATE";
 
@@ -196,6 +198,8 @@
     private long mNextIdlePendingDelay;
     private long mNextIdleDelay;
     private long mNextLightAlarmTime;
+    private long mCurIdleBudget;
+    private long mMaintenanceStartTime;
 
     private int mActiveIdleOpCount;
     private IBinder mDownloadServiceActive;
@@ -274,6 +278,25 @@
      */
     private int[] mTempWhitelistAppIdArray = new int[0];
 
+    private static final int EVENT_NULL = 0;
+    private static final int EVENT_NORMAL = 1;
+    private static final int EVENT_LIGHT_IDLE = 2;
+    private static final int EVENT_LIGHT_MAINTENANCE = 3;
+    private static final int EVENT_FULL_IDLE = 4;
+    private static final int EVENT_FULL_MAINTENANCE = 5;
+
+    private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
+    private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
+
+    private void addEvent(int cmd) {
+        if (mEventCmds[0] != cmd) {
+            System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1);
+            System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1);
+            mEventCmds[0] = cmd;
+            mEventTimes[0] = SystemClock.elapsedRealtime();
+        }
+    }
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
@@ -424,7 +447,10 @@
     private final class Constants extends ContentObserver {
         // Key names stored in the settings value.
         private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
-        private static final String KEY_LIGHT_IDLE_PENDING_TIMEOUT = "light_idle_pending_to";
+        private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
+                = "light_idle_maintenance_min_budget";
+        private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+                = "light_idle_maintenance_max_budget";
         private static final String KEY_INACTIVE_TIMEOUT = "inactive_to";
         private static final String KEY_SENSING_TIMEOUT = "sensing_to";
         private static final String KEY_LOCATING_TIMEOUT = "locating_to";
@@ -454,12 +480,24 @@
         public long LIGHT_IDLE_TIMEOUT;
 
         /**
-         * This is the initial time, after light idle idle, that we will will sit in the
-         * LIGHT_IDLE_MAINTENANCE period for the system to run normally before returning to idle.
+         * This is the minimum amount of time we want to make available for maintenance mode
+         * when lightly idling.  That is, we will always have at least this amount of time
+         * available maintenance before timing out and cutting off maintenance mode.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
-         * @see #KEY_LIGHT_IDLE_PENDING_TIMEOUT
+         * @see #KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
          */
-        public long LIGHT_IDLE_PENDING_TIMEOUT;
+        public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+
+        /**
+         * This is the maximum amount of time we want to make available for maintenance mode
+         * when lightly idling.  That is, if the system isn't using up its minimum maintenance
+         * budget and this time is being added to the budget reserve, this is the maximum
+         * reserve size we will allow to grow and thus the maximum amount of time we will
+         * allow for the maintenance window.
+         * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+         * @see #KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+         */
+        public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
 
         /**
          * This is the time, after becoming inactive, at which we start looking at the
@@ -619,8 +657,12 @@
 
                 LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
                         !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
-                LIGHT_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_PENDING_TIMEOUT,
+                LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
+                        KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
                         !COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L);
+                LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getLong(
+                        KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
+                        !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -662,8 +704,12 @@
             TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
             pw.println();
 
-            pw.print("    "); pw.print(KEY_LIGHT_IDLE_PENDING_TIMEOUT); pw.print("=");
-            TimeUtils.formatDuration(LIGHT_IDLE_PENDING_TIMEOUT, pw);
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw);
+            pw.println();
+
+            pw.print("    "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET); pw.print("=");
+            TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, pw);
             pw.println();
 
             pw.print("    "); pw.print(KEY_INACTIVE_TIMEOUT); pw.print("=");
@@ -1435,8 +1481,11 @@
             mState = STATE_ACTIVE;
             mLightState = LIGHT_STATE_ACTIVE;
             mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+            mCurIdleBudget = 0;
+            mMaintenanceStartTime = 0;
             resetIdleManagementLocked();
             resetLightIdleManagementLocked();
+            addEvent(EVENT_NORMAL);
         }
     }
 
@@ -1496,21 +1545,43 @@
 
         switch (mLightState) {
             case LIGHT_STATE_INACTIVE:
+                mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                mMaintenanceStartTime = 0;
             case LIGHT_STATE_IDLE_MAINTENANCE:
+                if (mMaintenanceStartTime != 0) {
+                    long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
+                    if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                        // We didn't use up all of our minimum budget; add this to the reserve.
+                        mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
+                    } else {
+                        // We used more than our minimum budget; this comes out of the reserve.
+                        mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
+                    }
+                }
+                mMaintenanceStartTime = 0;
                 scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
                 if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                 mLightState = LIGHT_STATE_IDLE;
                 EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                addEvent(EVENT_LIGHT_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
                 break;
             case LIGHT_STATE_IDLE:
                 // We have been idling long enough, now it is time to do some work.
                 mActiveIdleOpCount = 1;
-                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
+                mMaintenanceStartTime = SystemClock.elapsedRealtime();
+                if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+                    mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+                } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
+                    mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
+                }
+                mMaintenanceStartTime = SystemClock.elapsedRealtime();
+                scheduleLightAlarmLocked(mCurIdleBudget);
                 if (DEBUG) Slog.d(TAG,
                         "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                 mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                 EventLogTags.writeDeviceIdleLight(mLightState, reason);
+                addEvent(EVENT_LIGHT_MAINTENANCE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
@@ -1600,6 +1671,7 @@
                     cancelLightAlarmLocked();
                 }
                 EventLogTags.writeDeviceIdle(mState, reason);
+                addEvent(EVENT_FULL_IDLE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                 break;
             case STATE_IDLE:
@@ -1612,6 +1684,7 @@
                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                 mState = STATE_IDLE_MAINTENANCE;
                 EventLogTags.writeDeviceIdle(mState, reason);
+                addEvent(EVENT_FULL_MAINTENANCE);
                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                 break;
         }
@@ -1709,7 +1782,10 @@
             scheduleReportActiveLocked(type, Process.myUid());
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
+            mCurIdleBudget = 0;
+            mMaintenanceStartTime = 0;
             EventLogTags.writeDeviceIdle(mState, type);
+            addEvent(EVENT_NORMAL);
             becomeInactive = true;
         }
         if (mLightState == LIGHT_STATE_OVERRIDE) {
@@ -2016,6 +2092,8 @@
         pw.println("    Print this help text.");
         pw.println("  step");
         pw.println("    Immediately step to next state, without waiting for alarm.");
+        pw.println("  light-step");
+        pw.println("    Immediately step to next light idle state, without waiting for alarm.");
         pw.println("  force-idle");
         pw.println("    Force directly into idle mode, regardless of other device state.");
         pw.println("    Use \"step\" to get out.");
@@ -2262,6 +2340,31 @@
         synchronized (this) {
             mConstants.dump(pw);
 
+            if (mEventCmds[0] != EVENT_NULL) {
+                pw.println("  Idling history:");
+                long now = SystemClock.elapsedRealtime();
+                for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) {
+                    int cmd = mEventCmds[i];
+                    if (cmd == EVENT_NULL) {
+                        continue;
+                    }
+                    String label;
+                    switch (mEventCmds[i]) {
+                        case EVENT_NORMAL:              label = "     normal"; break;
+                        case EVENT_LIGHT_IDLE:          label = " light-idle"; break;
+                        case EVENT_LIGHT_MAINTENANCE:   label = "light-maint"; break;
+                        case EVENT_FULL_IDLE:           label = "  full-idle"; break;
+                        case EVENT_FULL_MAINTENANCE:    label = " full-maint"; break;
+                        default:                        label = "         ??"; break;
+                    }
+                    pw.print("    ");
+                    pw.print(label);
+                    pw.print(": ");
+                    TimeUtils.formatDuration(mEventTimes[i], now, pw);;
+                    pw.println();
+                }
+            }
+
             int size = mPowerSaveWhitelistAppsExceptIdle.size();
             if (size > 0) {
                 pw.println("  Whitelist (except idle) system apps:");
@@ -2373,6 +2476,16 @@
                 TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
                 pw.println();
             }
+            if (mCurIdleBudget != 0) {
+                pw.print("  mCurIdleBudget=");
+                TimeUtils.formatDuration(mCurIdleBudget, pw);
+                pw.println();
+            }
+            if (mMaintenanceStartTime != 0) {
+                pw.print("  mMaintenanceStartTime=");
+                TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
+                pw.println();
+            }
             if (mSyncActive) {
                 pw.print("  mSyncActive="); pw.println(mSyncActive);
             }
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 6acec6b..eb49a78 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -389,7 +389,7 @@
 
     private int getUserParentOrSelfId(int userId) {
         // Device supports per user encryption, so lock is applied to the given user.
-        if (mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
             return userId;
         }
         // Device uses Block Based Encryption, and the parent user's lock is used for the whole
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index f89155d..a32bb2f 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1904,16 +1904,18 @@
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
-        synchronized (mLock) {
-            if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
-                mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
-            }
-            if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
-                // TODO: persist through vold and reboot
-            }
+        if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+            final boolean emulateFbe = (flags & StorageManager.DEBUG_EMULATE_FBE) != 0;
+            SystemProperties.set(StorageManager.PROP_EMULATE_FBE, Boolean.toString(emulateFbe));
+        }
 
-            writeSettingsLocked();
-            mHandler.obtainMessage(H_RESET).sendToTarget();
+        if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
+            synchronized (mLock) {
+                mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
+
+                writeSettingsLocked();
+                mHandler.obtainMessage(H_RESET).sendToTarget();
+            }
         }
     }
 
@@ -2738,7 +2740,7 @@
 
     @Override
     public boolean isUserKeyUnlocked(int userId) {
-        if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
             synchronized (mLock) {
                 return ArrayUtils.contains(mUnlockedUsers, userId);
             }
@@ -2761,14 +2763,6 @@
     }
 
     @Override
-    public boolean isPerUserEncryptionEnabled() {
-        // TODO: switch this over to a single property; currently using two to
-        // handle the emulated case
-        return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
-                || SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
-    }
-
-    @Override
     public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
         // TODO: Invoke vold to mount app fuse.
         throw new UnsupportedOperationException();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 92e16c7..3a0d80b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20220,6 +20220,11 @@
     }
 
     @Override
+    public boolean unlockUser(int userId, byte[] token) {
+        return mUserController.unlockUser(userId, token);
+    }
+
+    @Override
     public boolean switchUser(final int userId) {
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         String userName;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 79aa85f..124d2ef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1665,9 +1665,7 @@
 
         UserInfo user = getUserInfo(userId);
         // TODO: Timeout for work challenge
-        if (user.isManagedProfile()
-                && mService.mContext.getSystemService(StorageManager.class)
-                    .isPerUserEncryptionEnabled()) {
+        if (user.isManagedProfile() && StorageManager.isFileBasedEncryptionEnabled()) {
             KeyguardManager km = (KeyguardManager) mService.mContext
                     .getSystemService(Context.KEYGUARD_SERVICE);
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d6fced6..e04f138 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -66,6 +66,7 @@
 import android.util.SparseIntArray;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.UserManagerService;
 
@@ -99,7 +100,9 @@
     /**
      * Which users have been started, so are allowed to run code.
      */
+    @GuardedBy("mService")
     private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+
     /**
      * LRU list of history of current users.  Most recently current is at the end.
      */
@@ -415,7 +418,7 @@
 
     private void updateUserUnlockedState(UserState uss) {
         final IMountService mountService = IMountService.Stub
-                .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+                .asInterface(ServiceManager.getService("mount"));
         if (mountService != null) {
             try {
                 uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
@@ -424,7 +427,7 @@
             }
         } else {
             // System isn't fully booted yet, so guess based on property
-            uss.unlocked = !SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
+            uss.unlocked = !StorageManager.isFileBasedEncryptionEnabled();
         }
     }
 
@@ -606,6 +609,35 @@
         return result;
     }
 
+    boolean unlockUser(final int userId, byte[] token) {
+        if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: unlockUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final UserInfo userInfo = getUserInfo(userId);
+        final IMountService mountService = IMountService.Stub
+                .asInterface(ServiceManager.getService("mount"));
+        try {
+            mountService.unlockUserKey(userId, userInfo.serialNumber, token);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to unlock: " + e.getMessage());
+            throw e.rethrowAsRuntimeException();
+        }
+
+        synchronized (mService) {
+            final UserState uss = mStartedUsers.get(userId);
+            updateUserUnlockedState(uss);
+        }
+
+        return true;
+    }
+
     void showUserSwitchDialog(int userId, String userName) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 0420269..c9e1315 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -41,6 +41,9 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 
+import android.os.SystemClock;
+import com.android.internal.logging.MetricsLogger;
+
 /**
  * This {@link NotificationSignalExtractor} attempts to validate
  * people references. Also elevates the priority of real people.
@@ -218,6 +221,7 @@
 
     private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
             float[] affinityOut) {
+        long start = SystemClock.elapsedRealtime();
         float affinity = NONE;
         if (extras == null) {
             return null;
@@ -251,6 +255,9 @@
         // record the best available data, so far:
         affinityOut[0] = affinity;
 
+        MetricsLogger.histogram(mBaseContext, "validate_people_cache_latency",
+                (int) (SystemClock.elapsedRealtime() - start));
+
         if (pendingLookups.isEmpty()) {
             if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
             return null;
@@ -430,6 +437,7 @@
 
         @Override
         public void work() {
+            long start = SystemClock.elapsedRealtime();
             if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
             long timeStartMs = System.currentTimeMillis();
             for (final String handle: mPendingLookups) {
@@ -468,6 +476,9 @@
                 mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
                         mContactAffinity == STARRED_CONTACT, false /* cached */);
             }
+
+            MetricsLogger.histogram(mBaseContext, "validate_people_lookup_latency",
+                    (int) (SystemClock.elapsedRealtime() - start));
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 8fac9da..073b4f03 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -57,6 +57,8 @@
     private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
     private static final boolean DEBUG = false;
 
+    private static final int DEFAULT_FLAGS = PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS;
+
     private static final String AUDIO_MIME_TYPE = "audio/mpeg";
 
     private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
@@ -696,7 +698,7 @@
     private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
             Intent intent, int userId) {
         ResolveInfo handler = mService.resolveIntent(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
         if (handler == null || handler.activityInfo == null) {
             return null;
         }
@@ -711,7 +713,7 @@
     private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
             Intent intent, int userId) {
         List<ResolveInfo> handlers = mService.queryIntentServices(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
         if (handlers == null) {
             return null;
         }
@@ -738,7 +740,8 @@
             homeIntent.setPackage(syncAdapterPackageName);
 
             ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
-                    homeIntent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+                    homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
+                    userId);
             if (homeActivity != null) {
                 continue;
             }
@@ -754,7 +757,7 @@
 
     private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
             String authority, int userId) {
-        ProviderInfo provider = mService.resolveContentProvider(authority, 0, userId);
+        ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
         if (provider != null) {
             return getSystemPackageLPr(provider.packageName);
         }
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
new file mode 100644
index 0000000..628ad0e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimedRemoteCaller;
+
+import com.android.internal.app.EphemeralResolverService;
+import com.android.internal.app.EphemeralResolveInfo;
+import com.android.internal.app.IEphemeralResolver;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Represents a remote ephemeral resolver. It is responsible for binding to the remote
+ * service and handling all interactions in a timely manner.
+ * @hide
+ */
+final class EphemeralResolverConnection {
+    // This is running in a critical section and the timeout must be sufficiently low
+    private static final long BIND_SERVICE_TIMEOUT_MS =
+            ("eng".equals(Build.TYPE)) ? 300 : 200;
+
+    private final Object mLock = new Object();
+    private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
+            new GetEphemeralResolveInfoCaller();
+    private final ServiceConnection mServiceConnection = new MyServiceConnection();
+    private final Context mContext;
+    /** Intent used to bind to the service */
+    private final Intent mIntent;
+
+    private IEphemeralResolver mRemoteInstance;
+
+    public EphemeralResolverConnection(Context context, ComponentName componentName) {
+        mContext = context;
+        mIntent = new Intent().setComponent(componentName);
+    }
+
+    public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
+        throwIfCalledOnMainThread();
+        try {
+            return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
+                    getRemoteInstanceLazy(), hashPrefix);
+        } catch (RemoteException re) {
+        } catch (TimeoutException te) {
+        } finally {
+            synchronized (mLock) {
+                mLock.notifyAll();
+            }
+        }
+        return null;
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+        synchronized (mLock) {
+            pw.append(prefix).append("bound=")
+                    .append((mRemoteInstance != null) ? "true" : "false").println();
+
+            pw.flush();
+
+            try {
+                getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix });
+            } catch (TimeoutException te) {
+                /* ignore */
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+    }
+
+    private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException {
+        synchronized (mLock) {
+            if (mRemoteInstance != null) {
+                return mRemoteInstance;
+            }
+            bindLocked();
+            return mRemoteInstance;
+        }
+    }
+
+    private void bindLocked() throws TimeoutException {
+        if (mRemoteInstance != null) {
+            return;
+        }
+
+        mContext.bindServiceAsUser(mIntent, mServiceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM);
+
+        final long startMillis = SystemClock.uptimeMillis();
+        while (true) {
+            if (mRemoteInstance != null) {
+                break;
+            }
+            final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+            final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
+            if (remainingMillis <= 0) {
+                throw new TimeoutException("Didn't bind to resolver in time.");
+            }
+            try {
+                mLock.wait(remainingMillis);
+            } catch (InterruptedException ie) {
+                /* ignore */
+            }
+        }
+
+        mLock.notifyAll();
+    }
+
+    private void throwIfCalledOnMainThread() {
+        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+            throw new RuntimeException("Cannot invoke on the main thread");
+        }
+    }
+
+    private final class MyServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                mRemoteInstance = IEphemeralResolver.Stub.asInterface(service);
+                mLock.notifyAll();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                mRemoteInstance = null;
+            }
+        }
+    }
+
+    private static final class GetEphemeralResolveInfoCaller
+            extends TimedRemoteCaller<List<EphemeralResolveInfo>> {
+        private final IRemoteCallback mCallback;
+
+        public GetEphemeralResolveInfoCaller() {
+            super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+            mCallback = new IRemoteCallback.Stub() {
+                    @Override
+                    public void sendResult(Bundle data) throws RemoteException {
+                        final ArrayList<EphemeralResolveInfo> resolveList =
+                                data.getParcelableArrayList(
+                                        EphemeralResolverService.EXTRA_RESOLVE_INFO);
+                        int sequence =
+                                data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1);
+                        onRemoteMethodResult(resolveList, sequence);
+                    }
+            };
+        }
+
+        public List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+                IEphemeralResolver target, int hashPrefix)
+                        throws RemoteException, TimeoutException {
+            final int sequence = onBeforeRemoteCall();
+            target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
+            return getResultTimed(sequence);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 992919e..4f3544b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -209,6 +209,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.EphemeralResolveInfo;
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.NativeLibraryHelper;
@@ -252,6 +253,7 @@
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.cert.CertificateEncodingException;
@@ -301,6 +303,7 @@
     private static final boolean DEBUG_VERIFY = false;
     private static final boolean DEBUG_DEXOPT = false;
     private static final boolean DEBUG_ABI_SELECTION = false;
+    private static final boolean DEBUG_EPHEMERAL = false;
 
     static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
 
@@ -598,6 +601,16 @@
     private final ComponentName mIntentFilterVerifierComponent;
     private int mIntentFilterVerificationToken = 0;
 
+    /** Component that knows whether or not an ephemeral application exists */
+    final ComponentName mEphemeralResolverComponent;
+    /** The service connection to the ephemeral resolver */
+    final EphemeralResolverConnection mEphemeralResolverConnection;
+
+    /** Component used to install ephemeral applications */
+    final ComponentName mEphemeralInstallerComponent;
+    final ActivityInfo mEphemeralInstallerActivity = new ActivityInfo();
+    final ResolveInfo mEphemeralInstallerInfo = new ResolveInfo();
+
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
             = new SparseArray<IntentFilterVerificationState>();
 
@@ -2346,6 +2359,33 @@
             mIntentFilterVerifier = new IntentVerifierProxy(mContext,
                     mIntentFilterVerifierComponent);
 
+            final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
+            final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr();
+            // both the installer and resolver must be present to enable ephemeral
+            if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent
+                            + " installer:" + ephemeralInstallerComponent);
+                }
+                mEphemeralResolverComponent = ephemeralResolverComponent;
+                mEphemeralInstallerComponent = ephemeralInstallerComponent;
+                setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
+                mEphemeralResolverConnection =
+                        new EphemeralResolverConnection(mContext, mEphemeralResolverComponent);
+            } else {
+                if (DEBUG_EPHEMERAL) {
+                    final String missingComponent =
+                            (ephemeralResolverComponent == null)
+                            ? (ephemeralInstallerComponent == null)
+                                    ? "resolver and installer"
+                                    : "resolver"
+                            : "installer";
+                    Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent);
+                }
+                mEphemeralResolverComponent = null;
+                mEphemeralInstallerComponent = null;
+                mEphemeralResolverConnection = null;
+            }
         } // synchronized (mPackages)
         } // synchronized (mInstallLock)
 
@@ -2484,6 +2524,89 @@
         return verifierComponentName;
     }
 
+    private ComponentName getEphemeralResolverLPr() {
+        final String[] packageArray =
+                mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
+        if (packageArray.length == 0) {
+            if (DEBUG_EPHEMERAL) {
+                Slog.d(TAG, "Ephemeral resolver NOT found; empty package list");
+            }
+            return null;
+        }
+
+        Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
+        final List<ResolveInfo> resolvers = queryIntentServices(resolverIntent,
+                null /*resolvedType*/, 0 /*flags*/, UserHandle.USER_SYSTEM);
+
+        final int N = resolvers.size();
+        if (N == 0) {
+            if (DEBUG_EPHEMERAL) {
+                Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters");
+            }
+            return null;
+        }
+
+        final Set<String> possiblePackages = new ArraySet<>(Arrays.asList(packageArray));
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = resolvers.get(i);
+
+            if (info.serviceInfo == null) {
+                continue;
+            }
+
+            final String packageName = info.serviceInfo.packageName;
+            if (!possiblePackages.contains(packageName)) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.d(TAG, "Ephemeral resolver not in allowed package list;"
+                            + " pkg: " + packageName + ", info:" + info);
+                }
+                continue;
+            }
+
+            if (DEBUG_EPHEMERAL) {
+                Slog.v(TAG, "Ephemeral resolver found;"
+                        + " pkg: " + packageName + ", info:" + info);
+            }
+            return new ComponentName(packageName, info.serviceInfo.name);
+        }
+        if (DEBUG_EPHEMERAL) {
+            Slog.v(TAG, "Ephemeral resolver NOT found");
+        }
+        return null;
+    }
+
+    private ComponentName getEphemeralInstallerLPr() {
+        Intent installerIntent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+        installerIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
+        final List<ResolveInfo> installers = queryIntentActivities(installerIntent,
+                PACKAGE_MIME_TYPE, 0 /*flags*/, 0 /*userId*/);
+
+        ComponentName ephemeralInstaller = null;
+
+        final int N = installers.size();
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = installers.get(i);
+            final String packageName = info.activityInfo.packageName;
+
+            if (!info.activityInfo.applicationInfo.isSystemApp()) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.d(TAG, "Ephemeral installer is not system app;"
+                            + " pkg: " + packageName + ", info:" + info);
+                }
+                continue;
+            }
+
+            if (ephemeralInstaller != null) {
+                throw new RuntimeException("There must only be one ephemeral installer");
+            }
+
+            ephemeralInstaller = new ComponentName(packageName, info.activityInfo.name);
+        }
+
+        return ephemeralInstaller;
+    }
+
     private void primeDomainVerificationsLPw(int userId) {
         if (DEBUG_DOMAIN_VERIFICATION) {
             Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -3021,18 +3144,19 @@
      * purposefully done before acquiring {@link #mPackages} lock.
      */
     private int augmentFlagsForUser(int flags, int userId) {
-        if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+        if (StorageManager.isFileBasedEncryptionEnabled()) {
             final IMountService mount = IMountService.Stub
-                    .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+                    .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;
             }
             final long token = Binder.clearCallingIdentity();
             try {
                 if (!mount.isUserKeyUnlocked(userId)) {
-                    flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
+                    flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
                 }
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
@@ -4270,8 +4394,97 @@
                 false, false, false, userId);
     }
 
+    private boolean isEphemeralAvailable(Intent intent, String resolvedType, int userId) {
+        MessageDigest digest = null;
+        try {
+            digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            // If we can't create a digest, ignore ephemeral apps.
+            return false;
+        }
+
+        final byte[] hostBytes = intent.getData().getHost().getBytes();
+        final byte[] digestBytes = digest.digest(hostBytes);
+        int shaPrefix =
+                digestBytes[0] << 24
+                | digestBytes[1] << 16
+                | digestBytes[2] << 8
+                | digestBytes[3] << 0;
+        final List<EphemeralResolveInfo> ephemeralResolveInfoList =
+                mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix);
+        if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
+            // No hash prefix match; there are no ephemeral apps for this domain.
+            return false;
+        }
+        for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) {
+            EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i);
+            if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) {
+                continue;
+            }
+            final List<IntentFilter> filters = ephemeralApplication.getFilters();
+            // No filters; this should never happen.
+            if (filters.isEmpty()) {
+                continue;
+            }
+            // We have a domain match; resolve the filters to see if anything matches.
+            final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
+            for (int j = filters.size() - 1; j >= 0; --j) {
+                ephemeralResolver.addFilter(filters.get(j));
+            }
+            List<ResolveInfo> ephemeralResolveList = ephemeralResolver.queryIntent(
+                    intent, resolvedType, false /*defaultOnly*/, userId);
+            return !ephemeralResolveList.isEmpty();
+        }
+        // Hash or filter mis-match; no ephemeral apps for this domain.
+        return false;
+    }
+
     private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
             int flags, List<ResolveInfo> query, int userId) {
+        final boolean isWebUri = hasWebURI(intent);
+        // Check whether or not an ephemeral app exists to handle the URI.
+        if (isWebUri && mEphemeralResolverConnection != null) {
+            // Deny ephemeral apps if the user choose _ALWAYS or _ALWAYS_ASK for intent resolution.
+            boolean hasAlwaysHandler = false;
+            synchronized (mPackages) {
+                final int count = query.size();
+                for (int n=0; n<count; n++) {
+                    ResolveInfo info = query.get(n);
+                    String packageName = info.activityInfo.packageName;
+                    PackageSetting ps = mSettings.mPackages.get(packageName);
+                    if (ps != null) {
+                        // Try to get the status from User settings first
+                        long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+                        int status = (int) (packedStatus >> 32);
+                        if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+                                || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+                            hasAlwaysHandler = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // Only consider installing an ephemeral app if there isn't already a verified handler.
+            // We've determined that there's an ephemeral app available for the URI, ignore any
+            // ResolveInfo's and just return the ephemeral installer
+            if (!hasAlwaysHandler && isEphemeralAvailable(intent, resolvedType, userId)) {
+                if (DEBUG_EPHEMERAL) {
+                    Slog.v(TAG, "Resolving to the ephemeral installer");
+                }
+                // ditch the result and return a ResolveInfo to launch the ephemeral installer
+                ResolveInfo ri = new ResolveInfo(mEphemeralInstallerInfo);
+                ri.activityInfo = new ActivityInfo(ri.activityInfo);
+                // make a deep copy of the applicationInfo
+                ri.activityInfo.applicationInfo = new ApplicationInfo(
+                        ri.activityInfo.applicationInfo);
+                if (userId != 0) {
+                    ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+                            UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+                }
+                return ri;
+            }
+        }
         if (query != null) {
             final int N = query.size();
             if (N == 1) {
@@ -6302,22 +6515,25 @@
         return true;
     }
 
-    private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
-        int[] users = sUserManager.getUserIds();
+    private void createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo)
+            throws PackageManagerException {
         int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
-        if (res < 0) {
-            return res;
+        if (res != 0) {
+            throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                    "Failed to install " + packageName + ": " + res);
         }
+
+        final int[] users = sUserManager.getUserIds();
         for (int user : users) {
             if (user != 0) {
                 res = mInstaller.createUserData(volumeUuid, packageName,
                         UserHandle.getUid(user, uid), user, seinfo);
-                if (res < 0) {
-                    return res;
+                if (res != 0) {
+                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+                            "Failed to createUserData " + packageName + ": " + res);
                 }
             }
         }
-        return res;
     }
 
     private int removeDataDirsLI(String volumeUuid, String packageName) {
@@ -6887,18 +7103,6 @@
                                     + pkg.applicationInfo.uid + "; old data erased";
                             reportSettingsProblem(Log.WARN, msg);
                             recovered = true;
-
-                            // And now re-install the app.
-                            ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
-                                    pkg.applicationInfo.seinfo);
-                            if (ret == -1) {
-                                // Ack should not happen!
-                                msg = prefix + pkg.packageName
-                                        + " could not have data directory re-created after delete.";
-                                reportSettingsProblem(Log.WARN, msg);
-                                throw new PackageManagerException(
-                                        INSTALL_FAILED_INSUFFICIENT_STORAGE, msg);
-                            }
                         }
                         if (!recovered) {
                             mHasSystemUidErrors = true;
@@ -6931,6 +7135,10 @@
                     }
                 }
 
+                // Ensure that directories are prepared
+                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+                        pkg.applicationInfo.seinfo);
+
                 if (mShouldRestoreconData) {
                     Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
                     mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName,
@@ -6941,14 +7149,8 @@
                     if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
                         Log.v(TAG, "Want this data dir: " + dataPath);
                 }
-                //invoke installer to do the actual installation
-                int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+                createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
                         pkg.applicationInfo.seinfo);
-                if (ret < 0) {
-                    // Error from installer
-                    throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
-                            "Unable to create data dirs [errorCode=" + ret + "]");
-                }
             }
 
             // Get all of our default paths setup
@@ -7780,6 +7982,30 @@
         }
     }
 
+    private void setUpEphemeralInstallerActivityLP(ComponentName installerComponent) {
+        final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
+
+        // Set up information for ephemeral installer activity
+        mEphemeralInstallerActivity.applicationInfo = pkg.applicationInfo;
+        mEphemeralInstallerActivity.name = mEphemeralInstallerComponent.getClassName();
+        mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName;
+        mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName;
+        mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+        mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
+                ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+        mEphemeralInstallerActivity.theme = 0;
+        mEphemeralInstallerActivity.exported = true;
+        mEphemeralInstallerActivity.enabled = true;
+        mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity;
+        mEphemeralInstallerInfo.priority = 0;
+        mEphemeralInstallerInfo.preferredOrder = 0;
+        mEphemeralInstallerInfo.match = 0;
+
+        if (DEBUG_EPHEMERAL) {
+            Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent);
+        }
+    }
+
     private static String calculateBundledApkRoot(final String codePathString) {
         final File codePath = new File(codePathString);
         final File codeRoot;
@@ -9336,7 +9562,28 @@
         private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
                 = new ArrayMap<ComponentName, PackageParser.Provider>();
         private int mFlags;
-    };
+    }
+
+    private static final class EphemeralIntentResolver
+            extends IntentResolver<IntentFilter, ResolveInfo> {
+        @Override
+        protected IntentFilter[] newArray(int size) {
+            return new IntentFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, IntentFilter info) {
+            return true;
+        }
+
+        @Override
+        protected ResolveInfo newResult(IntentFilter info, int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            final ResolveInfo res = new ResolveInfo();
+            res.filter = info;
+            return res;
+        }
+    }
 
     private static final Comparator<ResolveInfo> mResolvePrioritySorter =
             new Comparator<ResolveInfo>() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2cedc9c..dbb5818 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1000,7 +1000,7 @@
         pw.println("    the text in FILTER.");
         pw.println("    Options:");
         pw.println("      -f: see their associated file");
-        pw.println("      -d: filter to only show disbled packages");
+        pw.println("      -d: filter to only show disabled packages");
         pw.println("      -e: filter to only show enabled packages");
         pw.println("      -s: filter to only show system packages");
         pw.println("      -3: filter to only show third party packages");
@@ -1055,4 +1055,3 @@
         }
     }
 }
-
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1d299d7..99aa30b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3802,8 +3802,7 @@
         if ((flags & PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS) != 0) {
             return true;
         }
-        if ((flags & PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA) != 0) {
-            // When running with amnesia, we can only run encryption-aware apps
+        if ((flags & PackageManager.MATCH_ENCRYPTION_AWARE_ONLY) != 0) {
             return componentInfo.encryptionAware;
         }
         return true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 53ce9e2..baeccb4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -392,7 +392,7 @@
     @Override
     public int getCredentialOwnerProfile(int userHandle) {
         checkManageUsersPermission("get the credential owner");
-        if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (!StorageManager.isFileBasedEncryptionEnabled()) {
             synchronized (mUsersLock) {
                 UserInfo profileParent = getProfileParentLU(userHandle);
                 if (profileParent != null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 943c9ed..8b1a830 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1480,7 +1480,9 @@
                                 mNextAppTransitionFutureCallback, null /* finishedCallback */,
                                 mNextAppTransitionScaleUp);
                         mNextAppTransitionFutureCallback = null;
-                        mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+                        if (specs != null) {
+                            mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+                        }
                     }
                     mService.requestTraversal();
                 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3ad2610..253fdad 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -55,6 +55,7 @@
 
 import android.Manifest;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
@@ -3700,14 +3701,10 @@
         }
     }
 
-    void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+    void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
         // This is used by freeform <-> recents windows transition. We need to synchronize
         // the animation with the appearance of the content of recents, so we will make
         // animation stay on the first or last frame a little longer.
-        if (specs == null) {
-            Slog.wtf(TAG, "prolongAnimationsFromSpecs: AppTransitionAnimationSpec is null!");
-            return;
-        }
         mTmpTaskIds.clear();
         for (int i = specs.length - 1; i >= 0; i--) {
             mTmpTaskIds.put(specs[i].taskId, 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 31c3670..844cca5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3852,7 +3852,7 @@
         }
         enforceCrossUserPermission(userHandle);
         // Managed Profile password can only be changed when per user encryption is present.
-        if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+        if (!StorageManager.isFileBasedEncryptionEnabled()) {
             enforceNotManagedProfile(userHandle, "set the active password");
         }
 
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e0f95cf..c734fab 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -772,7 +772,8 @@
         }
 
         private void updateUsbNotification() {
-            if (mNotificationManager == null || !mUseUsbNotification) return;
+            if (mNotificationManager == null || !mUseUsbNotification
+                    || ("0".equals(SystemProperties.get("persist.charging.notify")))) return;
             int id = 0;
             Resources r = mContext.getResources();
             if (mConnected || mHostConnected) {