Merge "Revert "Fix broken build""
diff --git a/Android.mk b/Android.mk
index 71bba0f..9d2ca0d 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 \
@@ -417,6 +418,8 @@
 	packages/services/PacProcessor/com/android/net/IProxyService.aidl \
 	packages/services/Proxy/com/android/net/IProxyCallback.aidl \
 	packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
+	core/java/android/service/quicksettings/IQSService.aidl \
+	core/java/android/service/quicksettings/IQSTileService.aidl \
 
 # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
 LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
@@ -625,6 +628,7 @@
 	frameworks/base/core/java/android/bluetooth/le/ScanResult.aidl \
 	frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
 	frameworks/base/core/java/android/database/CursorWindow.aidl \
+	frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
 
 gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
 $(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/api/current.txt b/api/current.txt
index 1a7139c..a5b7fa8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30,6 +30,7 @@
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -2732,6 +2733,7 @@
     method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
     method public final android.os.IBinder getIBinder();
     method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
   }
@@ -2796,6 +2798,7 @@
     method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
     method public void setPassword(android.accounts.Account, java.lang.String);
     method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
     field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2812,6 +2815,8 @@
     field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
     field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
     field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+    field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+    field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
     field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
     field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3495,6 +3500,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
@@ -3773,6 +3779,8 @@
   }
 
   public class ActivityOptions {
+    method public android.graphics.Rect getLaunchBounds();
+    method public boolean hasLaunchBounds();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3782,6 +3790,7 @@
     method public static android.app.ActivityOptions makeTaskLaunchBehind();
     method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
+    method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -29043,6 +29052,35 @@
 
 }
 
+package android.service.quicksettings {
+
+  public final class Tile implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getLabel();
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setIcon(android.graphics.drawable.Icon);
+    method public void setLabel(java.lang.CharSequence);
+    method public void updateTile();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+  }
+
+  public class TileService extends android.app.Service {
+    ctor public TileService();
+    method public final android.service.quicksettings.Tile getQsTile();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public void onClick();
+    method public void onStartListening();
+    method public void onStopListening();
+    method public void onTileAdded();
+    method public void onTileRemoved();
+    field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+  }
+
+}
+
 package android.service.restrictions {
 
   public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -36238,7 +36276,6 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
-    method public final void cancelDrag();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -36256,6 +36293,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);
@@ -36481,6 +36519,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();
@@ -37090,6 +37129,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);
@@ -37156,6 +37196,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();
@@ -37270,6 +37312,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);
@@ -37299,12 +37342,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 f2784410..ac13222 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43,6 +43,7 @@
     field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
     field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
     field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
     field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
     field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
     field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -2831,6 +2832,7 @@
     method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
     method public final android.os.IBinder getIBinder();
     method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+    method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
     method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
     field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
   }
@@ -2895,6 +2897,7 @@
     method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
     method public void setPassword(android.accounts.Account, java.lang.String);
     method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+    method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
     field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2911,6 +2914,8 @@
     field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
     field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
     field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+    field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+    field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
     field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
     field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3598,6 +3603,7 @@
     method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
     method public void openContextMenu(android.view.View);
     method public void openOptionsMenu();
+    method public void overlayWithDecorCaption(boolean);
     method public void overridePendingTransition(int, int);
     method public void postponeEnterTransition();
     method public void recreate();
@@ -3882,6 +3888,8 @@
   }
 
   public class ActivityOptions {
+    method public android.graphics.Rect getLaunchBounds();
+    method public boolean hasLaunchBounds();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3891,6 +3899,7 @@
     method public static android.app.ActivityOptions makeTaskLaunchBehind();
     method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
     method public void requestUsageTimeReport(android.app.PendingIntent);
+    method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -8515,6 +8524,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";
@@ -8562,6 +8572,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";
@@ -31189,6 +31200,35 @@
 
 }
 
+package android.service.quicksettings {
+
+  public final class Tile implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getLabel();
+    method public void setContentDescription(java.lang.CharSequence);
+    method public void setIcon(android.graphics.drawable.Icon);
+    method public void setLabel(java.lang.CharSequence);
+    method public void updateTile();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+  }
+
+  public class TileService extends android.app.Service {
+    ctor public TileService();
+    method public final android.service.quicksettings.Tile getQsTile();
+    method public android.os.IBinder onBind(android.content.Intent);
+    method public void onClick();
+    method public void onStartListening();
+    method public void onStopListening();
+    method public void onTileAdded();
+    method public void onTileRemoved();
+    field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+  }
+
+}
+
 package android.service.restrictions {
 
   public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -38559,7 +38599,6 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
-    method public final void cancelDrag();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -38577,6 +38616,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);
@@ -38802,6 +38842,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();
@@ -39411,6 +39452,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);
@@ -39477,6 +39519,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();
@@ -39591,6 +39635,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);
@@ -39620,12 +39665,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/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
index a675769..726167e 100644
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
@@ -291,7 +291,7 @@
         System.err.println("        settings [--user NUM] delete namespace key");
         System.err.println("        settings [--user NUM] list namespace");
         System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
-        System.err.println("If '--user NUM' is not given, the operations are performed on the"
+        System.err.println("If '--user NUM' is not given, the operations are performed on the "
                 + "system user.");
     }
 
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 9c401c7f..185ceb4 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -116,6 +116,25 @@
      */
     public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
 
+    /**
+     * Bundle key used for the {@link String} account type in session bundle.
+     * This is used in the default implementation of
+     * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession.
+     */
+    private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE";
+    /**
+     * Bundle key used for the {@link String} array of required features in
+     * session bundle. This is used in the default implementation of
+     * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession.
+     */
+    private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+    /**
+     * Bundle key used for the {@link Bundle} options in session bundle. This is
+     * used in default implementation of {@link #startAddAccountSession}. TODO:
+     * and startUpdateCredentialsSession.
+     */
+    private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+
     private final Context mContext;
 
     public AbstractAccountAuthenticator(Context context) {
@@ -336,6 +355,36 @@
                 handleException(response, "addAccountFromCredentials", account.toString(), e);
             }
         }
+
+        @Override
+        public void startAddAccountSession(IAccountAuthenticatorResponse response,
+                String accountType, String authTokenType, String[] features, Bundle options)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG,
+                        "startAddAccountSession: accountType " + accountType
+                        + ", authTokenType " + authTokenType
+                        + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+            }
+            checkBinderPermission();
+            try {
+                final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
+                        new AccountAuthenticatorResponse(response), accountType, authTokenType,
+                        features, options);
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    if (result != null) {
+                        result.keySet(); // force it to be unparcelled
+                    }
+                    Log.v(TAG, "startAddAccountSession: result "
+                            + AccountManager.sanitizeResult(result));
+                }
+                if (result != null) {
+                    response.onResult(result);
+                }
+            } catch (Exception e) {
+                handleException(response, "startAddAccountSession", accountType, e);
+            }
+        }
     }
 
     private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -603,4 +652,52 @@
         }).start();
         return null;
     }
+
+    /**
+     * Starts the add account session to authenticate user to an account of the
+     * specified accountType.
+     *
+     * @param response to send the result back to the AccountManager, will never
+     *            be null
+     * @param accountType the type of account to authenticate with, will never
+     *            be null
+     * @param authTokenType the type of auth token to retrieve after
+     *            authenticating with the account, may be null
+     * @param requiredFeatures a String array of authenticator-specific features
+     *            that the account authenticated with must support, may be null
+     * @param options a Bundle of authenticator-specific options, may be null
+     * @return a Bundle result or null if the result is to be returned via the
+     *         response. The result will contain either:
+     *         <ul>
+     *         <li>{@link AccountManager#KEY_INTENT}, or
+     *         <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
+     *         the account to device later, and if account is authenticated,
+     *         optional {@link AccountManager#KEY_PASSWORD} and
+     *         {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+     *         status of the account, or
+     *         <li>{@link AccountManager#KEY_ERROR_CODE} and
+     *         {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+     *         </ul>
+     * @throws NetworkErrorException if the authenticator could not honor the
+     *             request due to a network error
+     */
+    public Bundle startAddAccountSession(final AccountAuthenticatorResponse response,
+            final String accountType, final String authTokenType, final String[] requiredFeatures,
+            final Bundle options)
+            throws NetworkErrorException {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Bundle sessionBundle = new Bundle();
+                sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+                sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
+                sessionBundle.putBundle(KEY_OPTIONS, options);
+                Bundle result = new Bundle();
+                result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+                response.onResult(result);
+            }
+
+        }).start();
+        return null;
+    }
 }
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 0a7568a..42e5e2a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -240,6 +240,20 @@
      */
     public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
 
+    /**
+     * Bundle key used for a {@link Bundle} in result from
+     * {@link #startAddAccountSession} and friends which returns session data
+     * for installing an account later.
+     */
+    public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+
+    /**
+     * Bundle key used for the {@link String} account status token in result
+     * from {@link #startAddAccountSession} and friends which returns
+     * information about a particular account.
+     */
+    public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
+
     public static final String ACTION_AUTHENTICATOR_INTENT =
             "android.accounts.AccountAuthenticator";
     public static final String AUTHENTICATOR_META_DATA_NAME =
@@ -2590,4 +2604,84 @@
             }
         }
     }
+
+    /**
+     * Asks the user to authenticate with an account of a specified type. The
+     * authenticator for this account type processes this request with the
+     * appropriate user interface. If the user does elect to authenticate with a
+     * new account, a bundle of session data for installing the account later is
+     * returned with optional account password and account status token.
+     * <p>
+     * This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     * <p>
+     * <p>
+     * <b>NOTE:</b> The account will not be installed to the device by calling
+     * this api alone.
+     *
+     * @param accountType The type of account to add; must not be null
+     * @param authTokenType The type of auth token (see {@link #getAuthToken})
+     *            this account will need to be able to generate, null for none
+     * @param requiredFeatures The features (see {@link #hasFeatures}) this
+     *            account must have, null for none
+     * @param options Authenticator-specific options for the request, may be
+     *            null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *            authenticator-defined sub-Activity to prompt the user to
+     *            create an account; used only to call startActivity(); if null,
+     *            the prompt will not be launched directly, but the necessary
+     *            {@link Intent} will be returned to the caller instead
+     * @param callback Callback to invoke when the request completes, null for
+     *            no callback
+     * @param handler {@link Handler} identifying the callback thread, null for
+     *            the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *         these fields if activity was specified and user was authenticated
+     *         with an account:
+     *         <ul>
+     *         <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+     *         adding the the to the device later.
+     *         <li>{@link #KEY_PASSWORD} - optional, the password or password
+     *         hash of the account.
+     *         <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+     *         status of the account
+     *         </ul>
+     *         If no activity was specified, the returned Bundle contains only
+     *         {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     *         actual account creation process. If authenticator doesn't support
+     *         this method, the returned Bundle contains only
+     *         {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted
+     *         {@code options} needed to add account later. If an error
+     *         occurred, {@link AccountManagerFuture#getResult()} throws:
+     *         <ul>
+     *         <li>{@link AuthenticatorException} if no authenticator was
+     *         registered for this account type or the authenticator failed to
+     *         respond
+     *         <li>{@link OperationCanceledException} if the operation was
+     *         canceled for any reason, including the user canceling the
+     *         creation process or adding accounts (of this type) has been
+     *         disabled by policy
+     *         <li>{@link IOException} if the authenticator experienced an I/O
+     *         problem creating a new account, usually because of network
+     *         trouble
+     *         </ul>
+     */
+    public AccountManagerFuture<Bundle> startAddAccountSession(final String accountType,
+            final String authTokenType, final String[] requiredFeatures, final Bundle options,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        final Bundle optionsIn = new Bundle();
+        if (options != null) {
+            optionsIn.putAll(options);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.startAddAccountSession(mResponse, accountType, authTokenType,
+                        requiredFeatures, activity != null, optionsIn);
+            }
+        }.start();
+    }
 }
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 58612da..b326070 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -83,4 +83,11 @@
      */
     void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
             in Bundle accountCredentials);
+
+    /**
+     * Starts the add account session by prompting the user for account information
+     * and return a Bundle containing data to finish the session later.
+     */
+    void startAddAccountSession(in IAccountAuthenticatorResponse response, String accountType,
+        String authTokenType, in String[] requiredFeatures, in Bundle options);
 }
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0d95db1..5de311e 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -83,4 +83,9 @@
     String getPreviousName(in Account account);
     boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
 
+    /* Add account in two steps. */
+    void startAddAccountSession(in IAccountManagerResponse response, String accountType,
+        String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+        in Bundle options);
+
 }
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index d8d2737..20d71a6 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -250,50 +250,19 @@
     /**
      * PathDataEvaluator is used to interpolate between two paths which are
      * represented in the same format but different control points' values.
-     * The path is represented as an array of PathDataNode here, which is
-     * fundamentally an array of floating point numbers.
+     * The path is represented as verbs and points for each of the verbs.
      */
-    private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> {
-        private PathParser.PathDataNode[] mNodeArray;
-
-        /**
-         * Create a PathParser.PathDataNode[] that does not reuse the animated value.
-         * Care must be taken when using this option because on every evaluation
-         * a new <code>PathParser.PathDataNode[]</code> will be allocated.
-         */
-        private PathDataEvaluator() {}
-
-        /**
-         * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call.
-         * Caution must be taken to ensure that the value returned from
-         * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
-         * used across threads. The value will be modified on each <code>evaluate()</code> call.
-         *
-         * @param nodeArray The array to modify and return from <code>evaluate</code>.
-         */
-        public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) {
-            mNodeArray = nodeArray;
-        }
+    private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
+        private final PathParser.PathData mPathData = new PathParser.PathData();
 
         @Override
-        public PathParser.PathDataNode[] evaluate(float fraction,
-                PathParser.PathDataNode[] startPathData,
-                PathParser.PathDataNode[] endPathData) {
-            if (!PathParser.canMorph(startPathData, endPathData)) {
+        public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
+                    PathParser.PathData endPathData) {
+            if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
                 throw new IllegalArgumentException("Can't interpolate between"
                         + " two incompatible pathData");
             }
-
-            if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) {
-                mNodeArray = PathParser.deepCopyNodes(startPathData);
-            }
-
-            for (int i = 0; i < startPathData.length; i++) {
-                mNodeArray[i].interpolatePathDataNode(startPathData[i],
-                        endPathData[i], fraction);
-            }
-
-            return mNodeArray;
+            return mPathData;
         }
     }
 
@@ -323,13 +292,14 @@
         if (valueType == VALUE_TYPE_PATH) {
             String fromString = styledAttributes.getString(valueFromId);
             String toString = styledAttributes.getString(valueToId);
-            PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
-            PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+            PathParser.PathData nodesFrom = fromString == null
+                    ? null : new PathParser.PathData(fromString);
+            PathParser.PathData nodesTo = toString == null
+                    ? null : new PathParser.PathData(toString);
 
             if (nodesFrom != null || nodesTo != null) {
                 if (nodesFrom != null) {
-                    TypeEvaluator evaluator =
-                            new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+                    TypeEvaluator evaluator = new PathDataEvaluator();
                     if (nodesTo != null) {
                         if (!PathParser.canMorph(nodesFrom, nodesTo)) {
                             throw new InflateException(" Can't morph from " + fromString + " to " +
@@ -342,8 +312,7 @@
                                 (Object) nodesFrom);
                     }
                 } else if (nodesTo != null) {
-                    TypeEvaluator evaluator =
-                            new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+                    TypeEvaluator evaluator = new PathDataEvaluator();
                     returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
                             (Object) nodesTo);
                 }
@@ -484,23 +453,25 @@
         TypeEvaluator evaluator = null;
         String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
         String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
-        PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
-        PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+        PathParser.PathData pathDataFrom = fromString == null
+                ? null : new PathParser.PathData(fromString);
+        PathParser.PathData pathDataTo = toString == null
+                ? null : new PathParser.PathData(toString);
 
-        if (nodesFrom != null) {
-            if (nodesTo != null) {
-                anim.setObjectValues(nodesFrom, nodesTo);
-                if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+        if (pathDataFrom != null) {
+            if (pathDataTo != null) {
+                anim.setObjectValues(pathDataFrom, pathDataTo);
+                if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
                     throw new InflateException(arrayAnimator.getPositionDescription()
                             + " Can't morph from " + fromString + " to " + toString);
                 }
             } else {
-                anim.setObjectValues((Object)nodesFrom);
+                anim.setObjectValues((Object)pathDataFrom);
             }
-            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
-        } else if (nodesTo != null) {
-            anim.setObjectValues((Object)nodesTo);
-            evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+            evaluator = new PathDataEvaluator();
+        } else if (pathDataTo != null) {
+            anim.setObjectValues((Object)pathDataTo);
+            evaluator = new PathDataEvaluator();
         }
 
         if (DBG_ANIMATOR_INFLATER && evaluator != null) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 472d97f..8bb0ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6618,6 +6618,17 @@
     }
 
     /**
+     * Set whether the caption should displayed directly on the content rather than push it down.
+     *
+     * This affects only freeform windows since they display the caption and only the main
+     * window of the activity. The caption is used to drag the window around and also shows
+     * maximize and close action buttons.
+     */
+    public void overlayWithDecorCaption(boolean overlay) {
+        mWindow.setOverlayDecorCaption(overlay);
+    }
+
+    /**
      * Interface for informing a translucent {@link Activity} once all visible activities below it
      * have completed drawing. This is necessary only after an {@link Activity} has been made
      * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 00bba2d..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;
+        }
     }
 
     /**
@@ -884,7 +893,7 @@
             if (mIcon != null) {
                 return mIcon;
             }
-            return loadTaskDescriptionIcon(mIconFilename);
+            return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
         }
 
         /** @hide */
@@ -898,11 +907,11 @@
         }
 
         /** @hide */
-        public static Bitmap loadTaskDescriptionIcon(String iconFilename) {
+        public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) {
             if (iconFilename != null) {
                 try {
                     return ActivityManagerNative.getDefault().
-                            getTaskDescriptionIcon(iconFilename);
+                            getTaskDescriptionIcon(iconFilename, userId);
                 } catch (RemoteException e) {
                 }
             }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e246e62..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();
@@ -2490,7 +2500,8 @@
         case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String filename = data.readString();
-            Bitmap icon = getTaskDescriptionIcon(filename);
+            int userId = data.readInt();
+            Bitmap icon = getTaskDescriptionIcon(filename, userId);
             reply.writeNoException();
             if (icon == null) {
                 reply.writeInt(0);
@@ -5249,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();
@@ -5998,11 +6023,12 @@
     }
 
     @Override
-    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
+    public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeString(filename);
+        data.writeInt(userId);
         mRemote.transact(GET_TASK_DESCRIPTION_ICON_TRANSACTION, data, reply, 0);
         reply.readException();
         final Bitmap icon = reply.readInt() == 0 ? null : Bitmap.CREATOR.createFromParcel(reply);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 57900aa..cee1aa5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -64,11 +64,13 @@
     public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
 
     /**
-     * The bounds that the activity should be started in. Set to null explicitly
-     * for full screen. If the key is not found, previous bounds will be preserved.
+     * The bounds (window size) that the activity should be launched in. Set to null explicitly for
+     * full screen. If the key is not found, previous bounds will be preserved.
+     * NOTE: This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
      * @hide
      */
-    public static final String KEY_BOUNDS = "android:activity.bounds";
+    public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
 
     /**
      * Type of animation that arguments specify.
@@ -193,8 +195,8 @@
     public static final int ANIM_CLIP_REVEAL = 11;
 
     private String mPackageName;
-    private boolean mHasBounds;
-    private Rect mBounds;
+    private boolean mHasLaunchBounds;
+    private Rect mLaunchBounds;
     private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
     private int mCustomExitResId;
@@ -705,9 +707,9 @@
         } catch (RuntimeException e) {
             Slog.w(TAG, e);
         }
-        mHasBounds = opts.containsKey(KEY_BOUNDS);
-        if (mHasBounds) {
-            mBounds = opts.getParcelable(KEY_BOUNDS);
+        mHasLaunchBounds = opts.containsKey(KEY_LAUNCH_BOUNDS);
+        if (mHasLaunchBounds) {
+            mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS);
         }
         mAnimationType = opts.getInt(KEY_ANIM_TYPE);
         switch (mAnimationType) {
@@ -766,10 +768,15 @@
         }
     }
 
-    /** @hide */
-    public ActivityOptions setBounds(Rect bounds) {
-        mHasBounds = true;
-        mBounds = bounds;
+    /**
+     * Sets the bounds (window size) that the activity should be launched in. Set to null explicitly
+     * for full screen.
+     * NOTE: This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
+     */
+    public ActivityOptions setLaunchBounds(Rect launchBounds) {
+        mHasLaunchBounds = true;
+        mLaunchBounds = launchBounds;
         return this;
     }
 
@@ -778,14 +785,12 @@
         return mPackageName;
     }
 
-    /** @hide */
-    public boolean hasBounds() {
-        return mHasBounds;
+    public boolean hasLaunchBounds() {
+        return mHasLaunchBounds;
     }
 
-    /** @hide */
-    public Rect getBounds(){
-        return mBounds;
+    public Rect getLaunchBounds(){
+        return mLaunchBounds;
     }
 
     /** @hide */
@@ -997,8 +1002,8 @@
         if (mPackageName != null) {
             b.putString(KEY_PACKAGE_NAME, mPackageName);
         }
-        if (mHasBounds) {
-            b.putParcelable(KEY_BOUNDS, mBounds);
+        if (mHasLaunchBounds) {
+            b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
         }
         b.putInt(KEY_ANIM_TYPE, mAnimationType);
         if (mUsageTimeReport != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b3d6382..802880d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -78,6 +78,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.security.NetworkSecurityPolicy;
+import android.security.net.config.NetworkSecurityConfigProvider;
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -4831,6 +4832,11 @@
             }
         }
 
+        // Install the Network Security Config Provider. This must happen before the application
+        // code is loaded to prevent issues with instances of TLS objects being created before
+        // the provider is installed.
+        NetworkSecurityConfigProvider.install(appContext);
+
         // Continue loading instrumentation.
         if (ii != null) {
             final ApplicationInfo instrApp = new ApplicationInfo();
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c0cc566..20f3495 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -575,14 +575,20 @@
         setGhostVisibility(View.INVISIBLE);
         mHasStopped = true;
         mIsCanceled = true;
+        clearState();
+        return super.cancelPendingTransitions();
+    }
+
+    @Override
+    protected void clearState() {
+        mSharedElementsBundle = null;
+        mEnterViewsTransition = null;
         mResultReceiver = null;
         if (mBackgroundAnimator != null) {
             mBackgroundAnimator.cancel();
             mBackgroundAnimator = null;
         }
-        mActivity = null;
-        clearState();
-        return super.cancelPendingTransitions();
+        super.clearState();
     }
 
     private void makeOpaque() {
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 4b670cd..e93b40e 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -470,6 +470,11 @@
             mActivity = null;
         }
         // Clear the state so that we can't hold any references accidentally and leak memory.
+        clearState();
+    }
+
+    @Override
+    protected void clearState() {
         mHandler = null;
         mSharedElementBundle = null;
         if (mBackgroundAnimator != null) {
@@ -477,7 +482,7 @@
             mBackgroundAnimator = null;
         }
         mExitSharedElementBundle = null;
-        clearState();
+        super.clearState();
     }
 
     @Override
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 3d0fc92..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;
@@ -497,7 +498,7 @@
     public void resizeTask(int taskId, Rect bounds, int resizeMode) throws RemoteException;
 
     public Rect getTaskBounds(int taskId) throws RemoteException;
-    public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
+    public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException;
 
     public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
             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/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 30232da..84ddd9f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -50,9 +50,6 @@
     void setPackagePriority(String pkg, int uid, int priority);
     int getPackagePriority(String pkg, int uid);
 
-    void setPackagePeekable(String pkg, int uid, boolean peekable);
-    boolean getPackagePeekable(String pkg, int uid);
-
     void setPackageVisibilityOverride(String pkg, int uid, int visibility);
     int getPackageVisibilityOverride(String pkg, int uid);
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 390c280..4e6548b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -838,13 +838,6 @@
     public static final String EXTRA_PEOPLE = "android.people";
 
     /**
-     * {@link #extras} key: used to provide hints about the appropriateness of
-     * displaying this notification as a heads-up notification.
-     * @hide
-     */
-    public static final String EXTRA_AS_HEADS_UP = "headsup";
-
-    /**
      * Allow certain system-generated notifications to appear before the device is provisioned.
      * Only available to notifications coming from the android package.
      * @hide
@@ -887,32 +880,6 @@
      */
     public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
 
-    /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
-     * displayed in the heads up space.
-     *
-     * <p>
-     * If this notification has a {@link #fullScreenIntent}, then it will always launch the
-     * full-screen intent when posted.
-     * </p>
-     * @hide
-     */
-    public static final int HEADS_UP_NEVER = 0;
-
-    /**
-     * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be
-     * displayed as a heads up.
-     * @hide
-     */
-    public static final int HEADS_UP_ALLOWED = 1;
-
-    /**
-     * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a
-     * good candidate for display as a heads up.
-     * @hide
-     */
-    public static final int HEADS_UP_REQUESTED = 2;
-
     private Icon mSmallIcon;
     private Icon mLargeIcon;
 
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/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index c368e5a..5997515 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -435,7 +435,7 @@
             return null;
         }
 
-        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
+        final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
         intent.setData(uri);
         intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 159ca01..af7f472 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -125,8 +125,7 @@
     public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
 
     /** {@hide} */
-    public static final String
-            ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT";
+    public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
 
     /** {@hide} */
     public static final String
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
new file mode 100644
index 0000000..087eb61
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+/**
+ * @hide
+ */
+interface IQSService {
+    void updateQsTile(in Tile tile);
+}
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
new file mode 100644
index 0000000..6b46bee5
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.IQSService;
+
+/**
+ * @hide
+ */
+oneway interface IQSTileService {
+    void setQSTile(in Tile tile);
+    void onTileAdded();
+    void onTileRemoved();
+    void onStartListening();
+    void onStopListening();
+    void onClick();
+}
diff --git a/core/java/android/service/quicksettings/Tile.aidl b/core/java/android/service/quicksettings/Tile.aidl
new file mode 100644
index 0000000..0373326
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quicksettings;
+
+parcelable Tile;
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
new file mode 100644
index 0000000..c8ae171
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A Tile holds the state of a tile that will be displayed
+ * in Quick Settings.
+ *
+ * A tile in Quick Settings exists as an icon with an accompanied label.
+ * It also may have content description for accessibility usability.
+ * The style and layout of the tile may change to match a given
+ * device.
+ */
+public final class Tile implements Parcelable {
+
+    private static final String TAG = "Tile";
+
+    private ComponentName mComponentName;
+    private IQSService mService;
+    private Icon mIcon;
+    private CharSequence mLabel;
+    private CharSequence mContentDescription;
+
+    /**
+     * @hide
+     */
+    public Tile(Parcel source) {
+        readFromParcel(source);
+    }
+
+    /**
+     * @hide
+     */
+    public Tile(ComponentName componentName, IQSService service) {
+        mComponentName = componentName;
+        mService = service;
+    }
+
+    /**
+     * @hide
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * Gets the current icon for the tile.
+     */
+    public Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Sets the current icon for the tile.
+     *
+     * This icon is expected to be white on alpha, and may be
+     * tinted by the system to match it's theme.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param icon New icon to show.
+     */
+    public void setIcon(Icon icon) {
+        this.mIcon = icon;
+    }
+
+    /**
+     * Gets the current label for the tile.
+     */
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Sets the current label for the tile.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param label New label to show.
+     */
+    public void setLabel(CharSequence label) {
+        this.mLabel = label;
+    }
+
+    /**
+     * Gets the current content description for the tile.
+     */
+    public CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sets the current content description for the tile.
+     *
+     * Does not take effect until {@link #updateTile()} is called.
+     *
+     * @param contentDescription New content description to use.
+     */
+    public void setContentDescription(CharSequence contentDescription) {
+        this.mContentDescription = contentDescription;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Pushes the state of the Tile to Quick Settings to be displayed.
+     */
+    public void updateTile() {
+        try {
+            mService.updateQsTile(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't update tile");
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mService);
+        if (mComponentName != null) {
+            dest.writeByte((byte) 1);
+            mComponentName.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        if (mIcon != null) {
+            dest.writeByte((byte) 1);
+            mIcon.writeToParcel(dest, flags);
+        } else {
+            dest.writeByte((byte) 0);
+        }
+        TextUtils.writeToParcel(mLabel, dest, flags);
+        TextUtils.writeToParcel(mContentDescription, dest, flags);
+    }
+
+    private void readFromParcel(Parcel source) {
+        mService = IQSService.Stub.asInterface(source.readStrongBinder());
+        if (source.readByte() != 0) {
+            mComponentName = ComponentName.CREATOR.createFromParcel(source);
+        } else {
+            mComponentName = null;
+        }
+        if (source.readByte() != 0) {
+            mIcon = Icon.CREATOR.createFromParcel(source);
+        } else {
+            mIcon = null;
+        }
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    public static final Creator<Tile> CREATOR = new Creator<Tile>() {
+        @Override
+        public Tile createFromParcel(Parcel source) {
+            return new Tile(source);
+        }
+
+        @Override
+        public Tile[] newArray(int size) {
+            return new Tile[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
new file mode 100644
index 0000000..eba4c6f
--- /dev/null
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.service.quicksettings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A QSTileService provides the user a tile that can be added to Quick Settings.
+ * Quick Settings is a space provided that allows the user to change settings and
+ * take quick actions without leaving the context of their current app.
+ *
+ * <p>The lifecycle of a QSTileService is different from some other services in
+ * that it may be unbound during parts of its lifecycle.  Any of the following
+ * lifecycle events can happen indepently in a separate binding/creation of the
+ * service.</p>
+ *
+ * <ul>
+ * <li>When a tile is added by the user its QSTileService will be bound to and
+ * {@link #onTileAdded()} will be called.</li>
+ *
+ * <li>When a tile should be up to date and listing will be indicated by
+ * {@link #onStartListening()} and {@link #onStopListening()}.</li>
+ *
+ * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
+ * will be called.</li>
+ * </ul>
+ * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
+ * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
+ * The label and icon for the service will be used as the default label and
+ * icon for the tile. Here is an example QSTileService declaration.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ *     android:name=".MyQSTileService"
+ *     android:label="@string/my_default_tile_label"
+ *     android:icon="@drawable/my_default_icon_label"
+ *     android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ *     <intent-filter>
+ *         <action android:name="android.intent.action.QS_TILE" />
+ *     </intent-filter>
+ * </service>}
+ * </pre>
+ *
+ * @see Tile Tile for details about the UI of a Quick Settings Tile.
+ */
+public class TileService extends Service {
+
+    /**
+     * Action that identifies a Service as being a QSTileService.
+     */
+    public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+
+    private final H mHandler = new H(Looper.getMainLooper());
+
+    private boolean mListening = false;
+    private Tile mTile;
+
+    /**
+     * Called when the user adds this tile to Quick Settings.
+     * <p/>
+     * Note that this is not guaranteed to be called between {@link #onCreate()}
+     * and {@link #onStartListening()}, it will only be called when the tile is added
+     * and not on subsequent binds.
+     */
+    public void onTileAdded() {
+    }
+
+    /**
+     * Called when the user removes this tile from Quick Settings.
+     */
+    public void onTileRemoved() {
+    }
+
+    /**
+     * Called when this tile moves into a listening state.
+     * <p/>
+     * When this tile is in a listening state it is expected to keep the
+     * UI up to date.  Any listeners or callbacks needed to keep this tile
+     * up to date should be registered here and unregistered in {@link #onStopListening()}.
+     *
+     * @see #getQsTile()
+     * @see Tile#updateTile()
+     */
+    public void onStartListening() {
+    }
+
+    /**
+     * Called when this tile moves out of the listening state.
+     */
+    public void onStopListening() {
+    }
+
+    /**
+     * Called when the user clicks on this tile.
+     */
+    public void onClick() {
+    }
+
+    /**
+     * Gets the {@link Tile} for this service.
+     * <p/>
+     * This tile may be used to get or set the current state for this
+     * tile. This tile is only valid for updates between {@link #onStartListening()}
+     * and {@link #onStopListening()}.
+     */
+    public final Tile getQsTile() {
+        return mTile;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IQSTileService.Stub() {
+            @Override
+            public void setQSTile(Tile tile) throws RemoteException {
+                mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
+            }
+
+            @Override
+            public void onTileRemoved() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
+            }
+
+            @Override
+            public void onTileAdded() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
+            }
+
+            @Override
+            public void onStopListening() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
+            }
+
+            @Override
+            public void onStartListening() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
+            }
+
+            @Override
+            public void onClick() throws RemoteException {
+                mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED);
+            }
+        };
+    }
+
+    private class H extends Handler {
+        private static final int MSG_SET_TILE = 1;
+        private static final int MSG_START_LISTENING = 2;
+        private static final int MSG_STOP_LISTENING = 3;
+        private static final int MSG_TILE_ADDED = 4;
+        private static final int MSG_TILE_REMOVED = 5;
+        private static final int MSG_TILE_CLICKED = 6;
+
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SET_TILE:
+                    mTile = (Tile) msg.obj;
+                    break;
+                case MSG_TILE_ADDED:
+                    TileService.this.onTileRemoved();
+                    break;
+                case MSG_TILE_REMOVED:
+                    TileService.this.onTileAdded();
+                    break;
+                case MSG_START_LISTENING:
+                    if (mListening) {
+                        mListening = false;
+                        TileService.this.onStopListening();
+                    }
+                    break;
+                case MSG_STOP_LISTENING:
+                    if (!mListening) {
+                        mListening = true;
+                        TileService.this.onStartListening();
+                    }
+                    break;
+                case MSG_TILE_CLICKED:
+                    TileService.this.onClick();
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index f099479..f17a16c 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -15,10 +15,6 @@
 package android.util;
 
 import android.graphics.Path;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * @hide
@@ -45,663 +41,94 @@
     }
 
     /**
-     * @param pathData The string representing a path, the same as "d" string in svg file.
-     * @return an array of the PathDataNode.
+     * Interpret PathData as path commands and insert the commands to the given path.
+     *
+     * @param data The source PathData to be converted.
+     * @param outPath The Path object where path commands will be inserted.
      */
-    public static PathDataNode[] createNodesFromPathData(String pathData) {
-        if (pathData == null) {
-            return null;
-        }
-        int start = 0;
-        int end = 1;
-
-        ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
-        while (end < pathData.length()) {
-            end = nextStart(pathData, end);
-            String s = pathData.substring(start, end).trim();
-            if (s.length() > 0) {
-                float[] val = getFloats(s);
-                addNode(list, s.charAt(0), val);
-            }
-
-            start = end;
-            end++;
-        }
-        if ((end - start) == 1 && start < pathData.length()) {
-            addNode(list, pathData.charAt(start), new float[0]);
-        }
-        return list.toArray(new PathDataNode[list.size()]);
+    public static void createPathFromPathData(Path outPath, PathData data) {
+        nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData);
     }
 
     /**
-     * @param source The array of PathDataNode to be duplicated.
-     * @return a deep copy of the <code>source</code>.
-     */
-    public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
-        if (source == null) {
-            return null;
-        }
-        PathDataNode[] copy = new PathParser.PathDataNode[source.length];
-        for (int i = 0; i < source.length; i ++) {
-            copy[i] = new PathDataNode(source[i]);
-        }
-        return copy;
-    }
-
-    /**
-     * @param nodesFrom The source path represented in an array of PathDataNode
-     * @param nodesTo The target path represented in an array of PathDataNode
+     * @param pathDataFrom The source path represented in PathData
+     * @param pathDataTo The target path represented in PathData
      * @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
      */
-    public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
-        if (nodesFrom == null || nodesTo == null) {
-            return false;
-        }
-
-        if (nodesFrom.length != nodesTo.length) {
-            return false;
-        }
-
-        for (int i = 0; i < nodesFrom.length; i ++) {
-            if (nodesFrom[i].mType != nodesTo[i].mType
-                    || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
-                return false;
-            }
-        }
-        return true;
+    public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) {
+        return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData);
     }
 
     /**
-     * Update the target's data to match the source.
-     * Before calling this, make sure canMorph(target, source) is true.
+     * PathData class is a wrapper around the native PathData object, which contains
+     * the result of parsing a path string. Specifically, there are verbs and points
+     * associated with each verb stored in PathData. This data can then be used to
+     * generate commands to manipulate a Path.
+     */
+    public static class PathData {
+        long mNativePathData = 0;
+        public PathData() {
+            mNativePathData = nCreateEmptyPathData();
+        }
+
+        public PathData(PathData data) {
+            mNativePathData = nCreatePathData(data.mNativePathData);
+        }
+
+        public PathData(String pathString) {
+            mNativePathData = nCreatePathDataFromString(pathString, pathString.length());
+            if (mNativePathData == 0) {
+                throw new IllegalArgumentException("Invalid pathData: " + pathString);
+            }
+        }
+
+        /**
+         * Update the path data to match the source.
+         * Before calling this, make sure canMorph(target, source) is true.
+         *
+         * @param source The source path represented in PathData
+         */
+        public void setPathData(PathData source) {
+            nSetPathData(mNativePathData, source.mNativePathData);
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mNativePathData != 0) {
+                nFinalize(mNativePathData);
+                mNativePathData = 0;
+            }
+            super.finalize();
+        }
+    }
+
+    /**
+     * Interpolate between the <code>fromData</code> and <code>toData</code> according to the
+     * <code>fraction</code>, and put the resulting path data into <code>outData</code>.
      *
-     * @param target The target path represented in an array of PathDataNode
-     * @param source The source path represented in an array of PathDataNode
+     * @param outData The resulting PathData of the interpolation
+     * @param fromData The start value as a PathData.
+     * @param toData The end value as a PathData
+     * @param fraction The fraction to interpolate.
      */
-    public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
-        for (int i = 0; i < source.length; i ++) {
-            target[i].mType = source[i].mType;
-            for (int j = 0; j < source[i].mParams.length; j ++) {
-                target[i].mParams[j] = source[i].mParams[j];
-            }
-        }
+    public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData,
+            float fraction) {
+        return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData,
+                toData.mNativePathData, fraction);
     }
 
-    private static int nextStart(String s, int end) {
-        char c;
-
-        while (end < s.length()) {
-            c = s.charAt(end);
-            // Note that 'e' or 'E' are not valid path commands, but could be
-            // used for floating point numbers' scientific notation.
-            // Therefore, when searching for next command, we should ignore 'e'
-            // and 'E'.
-            if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
-                    && c != 'e' && c != 'E') {
-                return end;
-            }
-            end++;
-        }
-        return end;
-    }
-
-    private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
-        list.add(new PathDataNode(cmd, val));
-    }
-
-    private static class ExtractFloatResult {
-        // We need to return the position of the next separator and whether the
-        // next float starts with a '-' or a '.'.
-        int mEndPosition;
-        boolean mEndWithNegOrDot;
-    }
-
-    /**
-     * Parse the floats in the string.
-     * This is an optimized version of parseFloat(s.split(",|\\s"));
-     *
-     * @param s the string containing a command and list of floats
-     * @return array of floats
-     */
-    private static float[] getFloats(String s) {
-        if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
-            return new float[0];
-        }
-        try {
-            float[] results = new float[s.length()];
-            int count = 0;
-            int startPosition = 1;
-            int endPosition = 0;
-
-            ExtractFloatResult result = new ExtractFloatResult();
-            int totalLength = s.length();
-
-            // The startPosition should always be the first character of the
-            // current number, and endPosition is the character after the current
-            // number.
-            while (startPosition < totalLength) {
-                extract(s, startPosition, result);
-                endPosition = result.mEndPosition;
-
-                if (startPosition < endPosition) {
-                    results[count++] = Float.parseFloat(
-                            s.substring(startPosition, endPosition));
-                }
-
-                if (result.mEndWithNegOrDot) {
-                    // Keep the '-' or '.' sign with next number.
-                    startPosition = endPosition;
-                } else {
-                    startPosition = endPosition + 1;
-                }
-            }
-            return Arrays.copyOf(results, count);
-        } catch (NumberFormatException e) {
-            throw new RuntimeException("error in parsing \"" + s + "\"", e);
-        }
-    }
-
-    /**
-     * Calculate the position of the next comma or space or negative sign
-     * @param s the string to search
-     * @param start the position to start searching
-     * @param result the result of the extraction, including the position of the
-     * the starting position of next number, whether it is ending with a '-'.
-     */
-    private static void extract(String s, int start, ExtractFloatResult result) {
-        // Now looking for ' ', ',', '.' or '-' from the start.
-        int currentIndex = start;
-        boolean foundSeparator = false;
-        result.mEndWithNegOrDot = false;
-        boolean secondDot = false;
-        boolean isExponential = false;
-        for (; currentIndex < s.length(); currentIndex++) {
-            boolean isPrevExponential = isExponential;
-            isExponential = false;
-            char currentChar = s.charAt(currentIndex);
-            switch (currentChar) {
-                case ' ':
-                case ',':
-                    foundSeparator = true;
-                    break;
-                case '-':
-                    // The negative sign following a 'e' or 'E' is not a separator.
-                    if (currentIndex != start && !isPrevExponential) {
-                        foundSeparator = true;
-                        result.mEndWithNegOrDot = true;
-                    }
-                    break;
-                case '.':
-                    if (!secondDot) {
-                        secondDot = true;
-                    } else {
-                        // This is the second dot, and it is considered as a separator.
-                        foundSeparator = true;
-                        result.mEndWithNegOrDot = true;
-                    }
-                    break;
-                case 'e':
-                case 'E':
-                    isExponential = true;
-                    break;
-            }
-            if (foundSeparator) {
-                break;
-            }
-        }
-        // When there is nothing found, then we put the end position to the end
-        // of the string.
-        result.mEndPosition = currentIndex;
-    }
-
-    /**
-     * Each PathDataNode represents one command in the "d" attribute of the svg
-     * file.
-     * An array of PathDataNode can represent the whole "d" attribute.
-     */
-    public static class PathDataNode {
-        private char mType;
-        private float[] mParams;
-
-        private PathDataNode(char type, float[] params) {
-            mType = type;
-            mParams = params;
-        }
-
-        private PathDataNode(PathDataNode n) {
-            mType = n.mType;
-            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
-        }
-
-        /**
-         * Convert an array of PathDataNode to Path.
-         *
-         * @param node The source array of PathDataNode.
-         * @param path The target Path object.
-         */
-        public static void nodesToPath(PathDataNode[] node, Path path) {
-            float[] current = new float[6];
-            char previousCommand = 'm';
-            for (int i = 0; i < node.length; i++) {
-                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
-                previousCommand = node[i].mType;
-            }
-        }
-
-        /**
-         * The current PathDataNode will be interpolated between the
-         * <code>nodeFrom</code> and <code>nodeTo</code> according to the
-         * <code>fraction</code>.
-         *
-         * @param nodeFrom The start value as a PathDataNode.
-         * @param nodeTo The end value as a PathDataNode
-         * @param fraction The fraction to interpolate.
-         */
-        public void interpolatePathDataNode(PathDataNode nodeFrom,
-                PathDataNode nodeTo, float fraction) {
-            for (int i = 0; i < nodeFrom.mParams.length; i++) {
-                mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
-                        + nodeTo.mParams[i] * fraction;
-            }
-        }
-
-        private static void addCommand(Path path, float[] current,
-                char previousCmd, char cmd, float[] val) {
-
-            int incr = 2;
-            float currentX = current[0];
-            float currentY = current[1];
-            float ctrlPointX = current[2];
-            float ctrlPointY = current[3];
-            float currentSegmentStartX = current[4];
-            float currentSegmentStartY = current[5];
-            float reflectiveCtrlPointX;
-            float reflectiveCtrlPointY;
-
-            switch (cmd) {
-                case 'z':
-                case 'Z':
-                    path.close();
-                    // Path is closed here, but we need to move the pen to the
-                    // closed position. So we cache the segment's starting position,
-                    // and restore it here.
-                    currentX = currentSegmentStartX;
-                    currentY = currentSegmentStartY;
-                    ctrlPointX = currentSegmentStartX;
-                    ctrlPointY = currentSegmentStartY;
-                    path.moveTo(currentX, currentY);
-                    break;
-                case 'm':
-                case 'M':
-                case 'l':
-                case 'L':
-                case 't':
-                case 'T':
-                    incr = 2;
-                    break;
-                case 'h':
-                case 'H':
-                case 'v':
-                case 'V':
-                    incr = 1;
-                    break;
-                case 'c':
-                case 'C':
-                    incr = 6;
-                    break;
-                case 's':
-                case 'S':
-                case 'q':
-                case 'Q':
-                    incr = 4;
-                    break;
-                case 'a':
-                case 'A':
-                    incr = 7;
-                    break;
-            }
-
-            for (int k = 0; k < val.length; k += incr) {
-                switch (cmd) {
-                    case 'm': // moveto - Start a new sub-path (relative)
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        if (k > 0) {
-                            // According to the spec, if a moveto is followed by multiple
-                            // pairs of coordinates, the subsequent pairs are treated as
-                            // implicit lineto commands.
-                            path.rLineTo(val[k + 0], val[k + 1]);
-                        } else {
-                            path.rMoveTo(val[k + 0], val[k + 1]);
-                            currentSegmentStartX = currentX;
-                            currentSegmentStartY = currentY;
-                        }
-                        break;
-                    case 'M': // moveto - Start a new sub-path
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        if (k > 0) {
-                            // According to the spec, if a moveto is followed by multiple
-                            // pairs of coordinates, the subsequent pairs are treated as
-                            // implicit lineto commands.
-                            path.lineTo(val[k + 0], val[k + 1]);
-                        } else {
-                            path.moveTo(val[k + 0], val[k + 1]);
-                            currentSegmentStartX = currentX;
-                            currentSegmentStartY = currentY;
-                        }
-                        break;
-                    case 'l': // lineto - Draw a line from the current point (relative)
-                        path.rLineTo(val[k + 0], val[k + 1]);
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'L': // lineto - Draw a line from the current point
-                        path.lineTo(val[k + 0], val[k + 1]);
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
-                        path.rLineTo(val[k + 0], 0);
-                        currentX += val[k + 0];
-                        break;
-                    case 'H': // horizontal lineto - Draws a horizontal line
-                        path.lineTo(val[k + 0], currentY);
-                        currentX = val[k + 0];
-                        break;
-                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
-                        path.rLineTo(0, val[k + 0]);
-                        currentY += val[k + 0];
-                        break;
-                    case 'V': // vertical lineto - Draws a vertical line from the current point
-                        path.lineTo(currentX, val[k + 0]);
-                        currentY = val[k + 0];
-                        break;
-                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
-                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-
-                        ctrlPointX = currentX + val[k + 2];
-                        ctrlPointY = currentY + val[k + 3];
-                        currentX += val[k + 4];
-                        currentY += val[k + 5];
-
-                        break;
-                    case 'C': // curveto - Draws a cubic Bézier curve
-                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
-                                val[k + 4], val[k + 5]);
-                        currentX = val[k + 4];
-                        currentY = val[k + 5];
-                        ctrlPointX = val[k + 2];
-                        ctrlPointY = val[k + 3];
-                        break;
-                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1],
-                                val[k + 2], val[k + 3]);
-
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'c' || previousCmd == 's'
-                                || previousCmd == 'C' || previousCmd == 'S') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 'q': // Draws a quadratic Bézier (relative)
-                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = currentX + val[k + 0];
-                        ctrlPointY = currentY + val[k + 1];
-                        currentX += val[k + 2];
-                        currentY += val[k + 3];
-                        break;
-                    case 'Q': // Draws a quadratic Bézier
-                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
-                        ctrlPointX = val[k + 0];
-                        ctrlPointY = val[k + 1];
-                        currentX = val[k + 2];
-                        currentY = val[k + 3];
-                        break;
-                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
-                        reflectiveCtrlPointX = 0;
-                        reflectiveCtrlPointY = 0;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = currentX - ctrlPointX;
-                            reflectiveCtrlPointY = currentY - ctrlPointY;
-                        }
-                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = currentX + reflectiveCtrlPointX;
-                        ctrlPointY = currentY + reflectiveCtrlPointY;
-                        currentX += val[k + 0];
-                        currentY += val[k + 1];
-                        break;
-                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
-                        reflectiveCtrlPointX = currentX;
-                        reflectiveCtrlPointY = currentY;
-                        if (previousCmd == 'q' || previousCmd == 't'
-                                || previousCmd == 'Q' || previousCmd == 'T') {
-                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-                        }
-                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                                val[k + 0], val[k + 1]);
-                        ctrlPointX = reflectiveCtrlPointX;
-                        ctrlPointY = reflectiveCtrlPointY;
-                        currentX = val[k + 0];
-                        currentY = val[k + 1];
-                        break;
-                    case 'a': // Draws an elliptical arc
-                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5] + currentX,
-                                val[k + 6] + currentY,
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX += val[k + 5];
-                        currentY += val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                    case 'A': // Draws an elliptical arc
-                        drawArc(path,
-                                currentX,
-                                currentY,
-                                val[k + 5],
-                                val[k + 6],
-                                val[k + 0],
-                                val[k + 1],
-                                val[k + 2],
-                                val[k + 3] != 0,
-                                val[k + 4] != 0);
-                        currentX = val[k + 5];
-                        currentY = val[k + 6];
-                        ctrlPointX = currentX;
-                        ctrlPointY = currentY;
-                        break;
-                }
-                previousCmd = cmd;
-            }
-            current[0] = currentX;
-            current[1] = currentY;
-            current[2] = ctrlPointX;
-            current[3] = ctrlPointY;
-            current[4] = currentSegmentStartX;
-            current[5] = currentSegmentStartY;
-        }
-
-        private static void drawArc(Path p,
-                float x0,
-                float y0,
-                float x1,
-                float y1,
-                float a,
-                float b,
-                float theta,
-                boolean isMoreThanHalf,
-                boolean isPositiveArc) {
-
-            /* Convert rotation angle from degrees to radians */
-            double thetaD = Math.toRadians(theta);
-            /* Pre-compute rotation matrix entries */
-            double cosTheta = Math.cos(thetaD);
-            double sinTheta = Math.sin(thetaD);
-            /* Transform (x0, y0) and (x1, y1) into unit space */
-            /* using (inverse) rotation, followed by (inverse) scale */
-            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
-            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
-            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
-            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
-            /* Compute differences and averages */
-            double dx = x0p - x1p;
-            double dy = y0p - y1p;
-            double xm = (x0p + x1p) / 2;
-            double ym = (y0p + y1p) / 2;
-            /* Solve for intersecting unit circles */
-            double dsq = dx * dx + dy * dy;
-            if (dsq == 0.0) {
-                Log.w(LOGTAG, " Points are coincident");
-                return; /* Points are coincident */
-            }
-            double disc = 1.0 / dsq - 1.0 / 4.0;
-            if (disc < 0.0) {
-                Log.w(LOGTAG, "Points are too far apart " + dsq);
-                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
-                drawArc(p, x0, y0, x1, y1, a * adjust,
-                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
-                return; /* Points are too far apart */
-            }
-            double s = Math.sqrt(disc);
-            double sdx = s * dx;
-            double sdy = s * dy;
-            double cx;
-            double cy;
-            if (isMoreThanHalf == isPositiveArc) {
-                cx = xm - sdy;
-                cy = ym + sdx;
-            } else {
-                cx = xm + sdy;
-                cy = ym - sdx;
-            }
-
-            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
-            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
-            double sweep = (eta1 - eta0);
-            if (isPositiveArc != (sweep >= 0)) {
-                if (sweep > 0) {
-                    sweep -= 2 * Math.PI;
-                } else {
-                    sweep += 2 * Math.PI;
-                }
-            }
-
-            cx *= a;
-            cy *= b;
-            double tcx = cx;
-            cx = cx * cosTheta - cy * sinTheta;
-            cy = tcx * sinTheta + cy * cosTheta;
-
-            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-        }
-
-        /**
-         * Converts an arc to cubic Bezier segments and records them in p.
-         *
-         * @param p The target for the cubic Bezier segments
-         * @param cx The x coordinate center of the ellipse
-         * @param cy The y coordinate center of the ellipse
-         * @param a The radius of the ellipse in the horizontal direction
-         * @param b The radius of the ellipse in the vertical direction
-         * @param e1x E(eta1) x coordinate of the starting point of the arc
-         * @param e1y E(eta2) y coordinate of the starting point of the arc
-         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
-         * @param start The start angle of the arc on the ellipse
-         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
-         */
-        private static void arcToBezier(Path p,
-                double cx,
-                double cy,
-                double a,
-                double b,
-                double e1x,
-                double e1y,
-                double theta,
-                double start,
-                double sweep) {
-            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
-            // and http://www.spaceroots.org/documents/ellipse/node22.html
-
-            // Maximum of 45 degrees per cubic Bezier segment
-            int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
-
-            double eta1 = start;
-            double cosTheta = Math.cos(theta);
-            double sinTheta = Math.sin(theta);
-            double cosEta1 = Math.cos(eta1);
-            double sinEta1 = Math.sin(eta1);
-            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
-            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
-            double anglePerSegment = sweep / numSegments;
-            for (int i = 0; i < numSegments; i++) {
-                double eta2 = eta1 + anglePerSegment;
-                double sinEta2 = Math.sin(eta2);
-                double cosEta2 = Math.cos(eta2);
-                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
-                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
-                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
-                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
-                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
-                double alpha =
-                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
-                double q1x = e1x + alpha * ep1x;
-                double q1y = e1y + alpha * ep1y;
-                double q2x = e2x - alpha * ep2x;
-                double q2y = e2y - alpha * ep2y;
-
-                p.cubicTo((float) q1x,
-                        (float) q1y,
-                        (float) q2x,
-                        (float) q2y,
-                        (float) e2x,
-                        (float) e2y);
-                eta1 = eta2;
-                e1x = e2x;
-                e1y = e2y;
-                ep1x = ep2x;
-                ep1y = ep2y;
-            }
-        }
-    }
-
+    // Native functions are defined below.
     private static native boolean nParseStringForPath(long pathPtr, String pathString,
             int stringLength);
+    private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+    private static native long nCreateEmptyPathData();
+    private static native long nCreatePathData(long nativePtr);
+    private static native long nCreatePathDataFromString(String pathString, int stringLength);
+    private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+            long toDataPtr, float fraction);
+    private static native void nFinalize(long nativePtr);
+    private static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+    private static native void nSetPathData(long outDataPtr, long fromDataPtr);
 }
+
+
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index f81b5d0..3fc70cc 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,11 +186,6 @@
 	void reportDropResult(IWindow window, boolean consumed);
 
     /**
-     * Cancel a drag operation.
-     */
-    void cancelDrag(IBinder dragToken);
-
-    /**
      * Tell the OS that we've just dragged into a View that is willing to accept the drop
      */
     void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 30408c6..227d8f2 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
@@ -19905,11 +19970,11 @@
         }
         Surface surface = new Surface();
         try {
-            mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
                     flags, shadowSize.x, shadowSize.y, surface);
-            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
-                    + mAttachInfo.mDragToken + " surface=" + surface);
-            if (mAttachInfo.mDragToken != null) {
+            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+                    + " surface=" + surface);
+            if (token != null) {
                 Canvas canvas = surface.lockCanvas(null);
                 try {
                     canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19926,7 +19991,7 @@
                 // repurpose 'shadowSize' for the last touch point
                 root.getLastTouchPoint(shadowSize);
 
-                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
                         shadowSize.x, shadowSize.y,
                         shadowTouchPoint.x, shadowTouchPoint.y, data);
                 if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -19943,22 +20008,6 @@
         return okay;
     }
 
-    public final void cancelDrag() {
-        if (ViewDebug.DEBUG_DRAG) {
-            Log.d(VIEW_LOG_TAG, "cancelDrag");
-        }
-        if (mAttachInfo.mDragToken != null) {
-            try {
-                mAttachInfo.mSession.cancelDrag(mAttachInfo.mDragToken);
-            } catch (Exception e) {
-                Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
-            }
-            mAttachInfo.mDragToken = null;
-        } else {
-            Log.e(VIEW_LOG_TAG, "No active drag to cancel");
-        }
-    }
-
     /**
      * Starts a move from {startX, startY}, the amount of the movement will be the offset
      * between {startX, startY} and the new cursor positon.
@@ -21867,6 +21916,7 @@
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
+            void schedulePartialLayout();
         }
 
         /**
@@ -22237,9 +22287,10 @@
         View mViewRequestingLayout;
 
         /**
-         * Used to track the identity of the current drag operation.
+         * Used to track views that need (at least) a partial relayout at their current size
+         * during the next traversal.
          */
-        IBinder mDragToken;
+        final List<View> mPartialLayoutViews = new ArrayList<View>();
 
         /**
          * Creates a new set of attachment information with the specified
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..5bbfc3f 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();
             }
         }
@@ -5307,10 +5372,10 @@
                     }
                 }
 
-                // When the drag operation ends, reset drag-related state
+                // When the drag operation ends, release any local state object
+                // that may have been in use
                 if (what == DragEvent.ACTION_DRAG_ENDED) {
                     setLocalDragState(null);
-                    mAttachInfo.mDragToken = null;
                 }
             }
         }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8259372..3c4d45a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -276,6 +276,8 @@
 
     private boolean mDestroyed;
 
+    private boolean mOverlayWithDecorCaption = false;
+
     // The current window attributes.
     private final WindowManager.LayoutParams mWindowAttributes =
         new WindowManager.LayoutParams();
@@ -2044,4 +2046,18 @@
     /** @hide */
     public void setTheme(int resId) {
     }
+
+    /**
+     * Whether the caption should be displayed directly on the content rather than push the content
+     * down. This affects only freeform windows since they display the caption.
+     * @hide
+     */
+    public void setOverlayDecorCaption(boolean overlayCaption) {
+        mOverlayWithDecorCaption = overlayCaption;
+    }
+
+    /** @hide */
+    public boolean getOverlayDecorCaption() {
+        return mOverlayWithDecorCaption;
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9428f44..19a98f3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -540,6 +540,13 @@
         void deactivate() {
             mActive = false;
         }
+
+        @Override
+        public String toString() {
+            return "ControlledInputConnectionWrapper{mActive=" + mActive
+                    + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
+                    + "}";
+        }
     }
     
     final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
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/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index a018d26..9f94005 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -28,6 +28,8 @@
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
@@ -447,6 +449,68 @@
         return CheckedTextView.class.getName();
     }
 
+    static class SavedState extends BaseSavedState {
+        boolean checked;
+
+        /**
+         * Constructor called from {@link CheckedTextView#onSaveInstanceState()}
+         */
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        /**
+         * Constructor called from {@link #CREATOR}
+         */
+        private SavedState(Parcel in) {
+            super(in);
+            checked = (Boolean)in.readValue(null);
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeValue(checked);
+        }
+
+        @Override
+        public String toString() {
+            return "CheckedTextView.SavedState{"
+                    + Integer.toHexString(System.identityHashCode(this))
+                    + " checked=" + checked + "}";
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        Parcelable superState = super.onSaveInstanceState();
+
+        SavedState ss = new SavedState(superState);
+
+        ss.checked = isChecked();
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        SavedState ss = (SavedState) state;
+
+        super.onRestoreInstanceState(ss.getSuperState());
+        setChecked(ss.checked);
+        requestLayout();
+    }
+
     /** @hide */
     @Override
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
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/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 077cebc..27fe03c 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -25,8 +25,8 @@
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorCaptionView;
 import com.android.internal.widget.FloatingToolbar;
-import com.android.internal.widget.NonClientDecorView;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -88,6 +88,18 @@
 
     private static final boolean SWEEP_OPEN_MENU = false;
 
+    // The height of a window which has focus in DIP.
+    private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
+    // The height of a window which has not in DIP.
+    private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
+
+    // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
+    // size calculation takes the shadow size into account. We set the elevation currently
+    // to max until the first layout command has been executed.
+    private boolean mAllowUpdateElevation = false;
+
+    private boolean mElevationAdjustedForStack = false;
+
     int mDefaultOpacity = PixelFormat.OPAQUE;
 
     /** The feature ID of the panel, or -1 if this is the application's DecorView */
@@ -101,8 +113,7 @@
 
     private final Rect mFrameOffsets = new Rect();
 
-    // True if a non client area decor exists.
-    private boolean mHasNonClientDecor = false;
+    private boolean mHasCaption = false;
 
     private boolean mChanging;
 
@@ -161,18 +172,18 @@
     private Rect mTempRect;
     private Rect mOutsets = new Rect();
 
-    // This is the non client decor view for the window, containing the caption and window control
+    // This is the caption view for the window, containing the caption and window control
     // buttons. The visibility of this decor depends on the workspace and the window type.
     // If the window type does not require such a view, this member might be null.
-    NonClientDecorView mNonClientDecorView;
+    DecorCaptionView mDecorCaptionView;
 
-    // The non client decor needs to adapt to the used workspace. Since querying and changing the
-    // workspace is expensive, this is the workspace value the window is currently set up for.
-    int mWorkspaceId;
+    // Stack window is currently in. Since querying and changing the stack is expensive,
+    // this is the stack value the window is currently set up for.
+    int mStackId;
 
     private boolean mWindowResizeCallbacksAdded = false;
 
-    public BackdropFrameRenderer mBackdropFrameRenderer = null;
+    BackdropFrameRenderer mBackdropFrameRenderer = null;
     private Drawable mResizingBackgroundDrawable;
     private Drawable mCaptionBackgroundDrawable;
 
@@ -191,7 +202,7 @@
         setWindow(window);
     }
 
-    public void setBackgroundFallback(int resId) {
+    void setBackgroundFallback(int resId) {
         mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
         setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
     }
@@ -351,10 +362,10 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         int action = event.getAction();
-        if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
-            // Don't dispatch ACTION_DOWN to the non client decor if the window is
-            // resizable and the event was (starting) outside the window.
-            // Window resizing events should be handled by WindowManager.
+        if (mHasCaption && isShowingCaption()) {
+            // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
+            // was (starting) outside the window. Window resizing events should be handled by
+            // WindowManager.
             // TODO: Investigate how to handle the outside touch in window manager
             //       without generating these events.
             //       Currently we receive these because we need to enlarge the window's
@@ -630,6 +641,11 @@
         if (mOutsets.top > 0) {
             offsetTopAndBottom(-mOutsets.top);
         }
+
+        // If the application changed its SystemUI metrics, we might also have to adapt
+        // our shadow elevation.
+        updateElevation();
+        mAllowUpdateElevation = true;
     }
 
     @Override
@@ -781,11 +797,11 @@
         }
     }
 
-    public void startChanging() {
+    void startChanging() {
         mChanging = true;
     }
 
-    public void finishChanging() {
+    void finishChanging() {
         mChanging = false;
         drawableChanged();
     }
@@ -1138,7 +1154,7 @@
         invalidate();
 
         int opacity = PixelFormat.OPAQUE;
-        if (windowHasShadow()) {
+        if (ActivityManager.StackId.hasWindowShadow(mStackId)) {
             // If the window has a shadow, it must be translucent.
             opacity = PixelFormat.TRANSLUCENT;
         } else{
@@ -1213,6 +1229,8 @@
         if (mFloatingActionMode != null) {
             mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
         }
+
+        updateElevation();
     }
 
     @Override
@@ -1495,38 +1513,19 @@
     }
 
     /**
-     * Informs the decor if a non client decor is attached and visible.
+     * Informs the decor if the caption is attached and visible.
      * @param attachedAndVisible true when the decor is visible.
-     * Note that this will even be called if there is no non client decor.
+     * Note that this will even be called if there is no caption.
      **/
-    void enableNonClientDecor(boolean attachedAndVisible) {
-        if (mHasNonClientDecor != attachedAndVisible) {
-            mHasNonClientDecor = attachedAndVisible;
+    void enableCaption(boolean attachedAndVisible) {
+        if (mHasCaption != attachedAndVisible) {
+            mHasCaption = attachedAndVisible;
             if (getForeground() != null) {
                 drawableChanged();
             }
         }
     }
 
-    /**
-     * Returns true if the window has a non client decor.
-     * @return If there is a non client decor - even if it is not visible.
-     **/
-    private boolean windowHasNonClientDecor() {
-        return mHasNonClientDecor;
-    }
-
-    /**
-     * Returns true if the Window is free floating and has a shadow (although at some times
-     * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
-     * do not have a shadow since it could not be seen anyways (a small screen / tablet
-     * "tiles" the windows side by side but does not overlap them).
-     * @return Returns true when the window has a shadow created by the non client decor.
-     **/
-    private boolean windowHasShadow() {
-        return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
-    }
-
     void setWindow(PhoneWindow phoneWindow) {
         mWindow = phoneWindow;
         Context context = getContext();
@@ -1537,91 +1536,89 @@
     }
 
     void onConfigurationChanged() {
-        if (mNonClientDecorView != null) {
-            int workspaceId = getWorkspaceId();
-            if (mWorkspaceId != workspaceId) {
-                mWorkspaceId = workspaceId;
+        int workspaceId = getStackId();
+        if (mDecorCaptionView != null) {
+            if (mStackId != workspaceId) {
+                mStackId = workspaceId;
                 // We might have to change the kind of surface before we do anything else.
-                mNonClientDecorView.onConfigurationChanged(
-                        ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
-                        ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
-                enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
+                mDecorCaptionView.onConfigurationChanged(
+                        ActivityManager.StackId.hasWindowDecor(mStackId));
+                enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId));
             }
         }
+        initializeElevation();
     }
 
     View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
-        mWorkspaceId = getWorkspaceId();
+        mStackId = getStackId();
 
         mResizingBackgroundDrawable = getResizingBackgroundDrawable(
                 mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
         mCaptionBackgroundDrawable =
-                getContext().getDrawable(R.drawable.non_client_decor_title_focused);
+                getContext().getDrawable(R.drawable.decor_caption_title_focused);
 
         if (mBackdropFrameRenderer != null) {
             mBackdropFrameRenderer.onResourcesLoaded(
                     this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable);
         }
 
-        mNonClientDecorView = createNonClientDecorView(inflater);
+        mDecorCaptionView = createDecorCaptionView(inflater);
         final View root = inflater.inflate(layoutResource, null);
-        if (mNonClientDecorView != null) {
-            if (mNonClientDecorView.getParent() == null) {
-                addView(mNonClientDecorView,
+        if (mDecorCaptionView != null) {
+            if (mDecorCaptionView.getParent() == null) {
+                addView(mDecorCaptionView,
                         new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             }
-            mNonClientDecorView.addView(root,
-                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            mDecorCaptionView.addView(root,
+                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
         } else {
             addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
         mContentRoot = (ViewGroup) root;
+        initializeElevation();
         return root;
     }
 
-    // Free floating overlapping windows require a non client decor with a caption and shadow..
-    private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
-        NonClientDecorView nonClientDecorView = null;
-        for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
+    // Free floating overlapping windows require a caption.
+    private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
+        DecorCaptionView DecorCaptionView = null;
+        for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) {
             View view = getChildAt(i);
-            if (view instanceof NonClientDecorView) {
+            if (view instanceof DecorCaptionView) {
                 // The decor was most likely saved from a relaunch - so reuse it.
-                nonClientDecorView = (NonClientDecorView) view;
+                DecorCaptionView = (DecorCaptionView) view;
                 removeViewAt(i);
             }
         }
         final WindowManager.LayoutParams attrs = mWindow.getAttributes();
-        boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+        final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
                 attrs.type == TYPE_APPLICATION;
-        // Only a non floating application window on one of the allowed workspaces can get a non
-        // client decor.
-        final boolean hasNonClientDecor = ActivityManager.StackId.hasWindowDecor(mWorkspaceId);
-        if (!mWindow.isFloating() && isApplication && hasNonClientDecor) {
+        // Only a non floating application window on one of the allowed workspaces can get a caption
+        if (!mWindow.isFloating() && isApplication
+                && ActivityManager.StackId.hasWindowDecor(mStackId)) {
             // Dependent on the brightness of the used title we either use the
             // dark or the light button frame.
-            if (nonClientDecorView == null) {
+            if (DecorCaptionView == null) {
                 Context context = getContext();
                 TypedValue value = new TypedValue();
                 context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
                 inflater = inflater.from(context);
                 if (Color.luminance(value.data) < 0.5) {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_dark, null);
+                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
+                            R.layout.decor_caption_dark, null);
                 } else {
-                    nonClientDecorView = (NonClientDecorView) inflater.inflate(
-                            R.layout.non_client_decor_light, null);
+                    DecorCaptionView = (DecorCaptionView) inflater.inflate(
+                            R.layout.decor_caption_light, null);
                 }
             }
-            nonClientDecorView.setPhoneWindow(mWindow,
-                    ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
-                    ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
+            DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
         } else {
-            nonClientDecorView = null;
+            DecorCaptionView = null;
         }
 
-        // Tell the decor if it has a visible non client decor.
-        enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor);
-        return nonClientDecorView;
+        // Tell the decor if it has a visible caption.
+        enableCaption(DecorCaptionView != null);
+        return DecorCaptionView;
     }
 
     /**
@@ -1652,12 +1649,12 @@
     }
 
     /**
-     * Returns the Id of the workspace which contains this window.
-     * Note that if no workspace can be determined - which usually means that it was not
-     * created for an activity - the fullscreen workspace ID will be returned.
-     * @return Returns the workspace stack id which contains this window.
+     * Returns the Id of the stack which contains this window.
+     * Note that if no stack can be determined - which usually means that it was not
+     * created for an activity - the fullscreen stack ID will be returned.
+     * @return Returns the stack id which contains this window.
      **/
-    private int getWorkspaceId() {
+    private int getStackId() {
         int workspaceId = INVALID_STACK_ID;
         final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
         if (callback != null) {
@@ -1674,12 +1671,10 @@
     }
 
     void clearContentView() {
-        if (mNonClientDecorView != null) {
-            if (mNonClientDecorView.getChildCount() > 1) {
-                mNonClientDecorView.removeViewAt(1);
-            }
+        if (mDecorCaptionView != null) {
+            mDecorCaptionView.removeContentView();
         } else {
-            // This window doesn't have non client decor, so we need to just remove the
+            // This window doesn't have caption, so we need to just remove the
             // children of the decor view.
             removeAllViews();
         }
@@ -1749,18 +1744,60 @@
         }
     }
 
+    /**
+     * The elevation gets set for the first time and the framework needs to be informed that
+     * the surface layer gets created with the shadow size in mind.
+     */
+    private void initializeElevation() {
+        // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
+        mAllowUpdateElevation = false;
+        updateElevation();
+    }
+
     private void updateElevation() {
-        if (mNonClientDecorView != null) {
-            mNonClientDecorView.updateElevation();
+        float elevation = 0;
+        final boolean wasAdjustedForStack = mElevationAdjustedForStack;
+        // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
+        // since the shadow is bound to the content size and not the target size.
+        if (ActivityManager.StackId.hasWindowShadow(mStackId)
+                && mBackdropFrameRenderer == null) {
+            elevation = hasWindowFocus() ?
+                    DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
+            // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
+            if (!mAllowUpdateElevation) {
+                elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
+            }
+            // Convert the DP elevation into physical pixels.
+            elevation = dipToPx(elevation);
+            mElevationAdjustedForStack = true;
+        } else {
+            mElevationAdjustedForStack = false;
+        }
+
+        // Don't change the elevation if we didn't previously adjust it for the stack it was in
+        // or it didn't change.
+        if ((wasAdjustedForStack || mElevationAdjustedForStack)
+                && getElevation() != elevation) {
+            mWindow.setElevation(elevation);
         }
     }
 
     boolean isShowingCaption() {
-        return mNonClientDecorView != null && mNonClientDecorView.isShowingDecor();
+        return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing();
     }
 
     int getCaptionHeight() {
-        return isShowingCaption() ? mNonClientDecorView.getDecorCaptionHeight() : 0;
+        return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
+    }
+
+    /**
+     * Converts a DIP measure into physical pixels.
+     * @param dip The dip value.
+     * @return Returns the number of pixels.
+     */
+    private float dipToPx(float dip) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
+                getResources().getDisplayMetrics());
     }
 
     private static class ColorViewState {
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/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
new file mode 100644
index 0000000..16e8296
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.Window;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * This class represents the special screen elements to control a window on freeform
+ * environment.
+ * As such this class handles the following things:
+ * <ul>
+ * <li>The caption, containing the system buttons like maximize, close and such as well as
+ * allowing the user to drag the window around.</li>
+ * After creating the view, the function
+ * {@link #setPhoneWindow} needs to be called to make
+ * the connection to it's owning PhoneWindow.
+ * Note: At this time the application can change various attributes of the DecorView which
+ * will break things (in settle/unexpected ways):
+ * <ul>
+ * <li>setOutlineProvider</li>
+ * <li>setSurfaceFormat</li>
+ * <li>..</li>
+ * </ul>
+ */
+public class DecorCaptionView extends ViewGroup
+        implements View.OnClickListener, View.OnTouchListener {
+    private final static String TAG = "DecorCaptionView";
+    private PhoneWindow mOwner = null;
+    private boolean mShow = false;
+
+    // True if the window is being dragged.
+    private boolean mDragging = false;
+
+    // True when the left mouse button got released while dragging.
+    private boolean mLeftMouseButtonReleased;
+
+    private boolean mOverlayWithAppContent = false;
+
+    private View mCaption;
+    private View mContent;
+
+    public DecorCaptionView(Context context) {
+        super(context);
+    }
+
+    public DecorCaptionView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCaption = getChildAt(0);
+    }
+
+    public void setPhoneWindow(PhoneWindow owner, boolean show) {
+        mOwner = owner;
+        mShow = show;
+        mOverlayWithAppContent = owner.getOverlayDecorCaption();
+        updateCaptionVisibility();
+        // By changing the outline provider to BOUNDS, the window can remove its
+        // background without removing the shadow.
+        mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+
+        findViewById(R.id.maximize_window).setOnClickListener(this);
+        findViewById(R.id.close_window).setOnClickListener(this);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent e) {
+        // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+        // the old input device events get cancelled first. So no need to remember the kind of
+        // input device we are listening to.
+        switch (e.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                if (!mShow) {
+                    // When there is no caption we should not react to anything.
+                    return false;
+                }
+                // A drag action is started if we aren't dragging already and the starting event is
+                // either a left mouse button or any other input device.
+                if (!mDragging &&
+                        (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+                                (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
+                    mDragging = true;
+                    mLeftMouseButtonReleased = false;
+                    startMovingTask(e.getRawX(), e.getRawY());
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                if (mDragging && !mLeftMouseButtonReleased) {
+                    if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+                            (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+                        // There is no separate mouse button up call and if the user mixes mouse
+                        // button drag actions, we stop dragging once he releases the button.
+                        mLeftMouseButtonReleased = true;
+                        break;
+                    }
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                if (!mDragging) {
+                    break;
+                }
+                // Abort the ongoing dragging.
+                mDragging = false;
+                return true;
+        }
+        return mDragging;
+    }
+
+    /**
+     * The phone window configuration has changed and the caption needs to be updated.
+     * @param show True if the caption should be shown.
+     */
+    public void onConfigurationChanged(boolean show) {
+        mShow = show;
+        updateCaptionVisibility();
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view.getId() == R.id.maximize_window) {
+            maximizeWindow();
+        } else if (view.getId() == R.id.close_window) {
+            mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+        }
+    }
+
+    @Override
+    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+        if (!(params instanceof MarginLayoutParams)) {
+            throw new IllegalArgumentException(
+                    "params " + params + " must subclass MarginLayoutParams");
+        }
+        // Make sure that we never get more then one client area in our view.
+        if (index >= 2 || getChildCount() >= 2) {
+            throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
+        }
+        // To support the overlaying content in the caption, we need to put the content view as the
+        // first child to get the right Z-Ordering.
+        super.addView(child, 0, params);
+        mContent = child;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            captionHeight = mCaption.getMeasuredHeight();
+        } else {
+            captionHeight = 0;
+        }
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            } else {
+                measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
+                        captionHeight);
+            }
+        }
+
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+                MeasureSpec.getSize(heightMeasureSpec));
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int captionHeight;
+        if (mCaption.getVisibility() != View.GONE) {
+            mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
+            captionHeight = mCaption.getBottom() - mCaption.getTop();
+        } else {
+            captionHeight = 0;
+        }
+
+        if (mContent != null) {
+            if (mOverlayWithAppContent) {
+                mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
+            } else {
+                mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
+                        captionHeight + mContent.getMeasuredHeight());
+            }
+        }
+    }
+    /**
+     * Determine if the workspace is entirely covered by the window.
+     * @return Returns true when the window is filling the entire screen/workspace.
+     **/
+    private boolean isFillingScreen() {
+        return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
+                (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+                        View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
+    }
+
+    /**
+     * Updates the visibility of the caption.
+     **/
+    private void updateCaptionVisibility() {
+        // Don't show the caption if the window has e.g. entered full screen.
+        boolean invisible = isFillingScreen() || !mShow;
+        mCaption.setVisibility(invisible ? GONE : VISIBLE);
+        mCaption.setOnTouchListener(this);
+    }
+
+    /**
+     * Maximize the window by moving it to the maximized workspace stack.
+     **/
+    private void maximizeWindow() {
+        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+        if (callback != null) {
+            try {
+                callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Cannot change task workspace.");
+            }
+        }
+    }
+
+    public boolean isCaptionShowing() {
+        return mShow;
+    }
+
+    public int getCaptionHeight() {
+        return (mCaption != null) ? mCaption.getHeight() : 0;
+    }
+
+    public void removeContentView() {
+        if (mContent != null) {
+            removeView(mContent);
+            mContent = null;
+        }
+    }
+
+    public View getCaption() {
+        return mCaption;
+    }
+
+    @Override
+    public LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new MarginLayoutParams(getContext(), attrs);
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
+                MarginLayoutParams.MATCH_PARENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(LayoutParams p) {
+        return new MarginLayoutParams(p);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof MarginLayoutParams;
+    }
+}
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
deleted file mode 100644
index 33b8e05..0000000
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.Window;
-import android.util.Log;
-import android.util.TypedValue;
-
-import com.android.internal.R;
-import com.android.internal.policy.DecorView;
-import com.android.internal.policy.PhoneWindow;
-
-/**
- * This class represents the special screen elements to control a window on free form
- * environment. All thse screen elements are added in the "non client area" which is the area of
- * the window which is handled by the OS and not the application.
- * As such this class handles the following things:
- * <ul>
- * <li>The caption, containing the system buttons like maximize, close and such as well as
- * allowing the user to drag the window around.</li>
- * <li>The shadow - which is changing dependent on the window focus.</li>
- * <li>The border around the client area (if there is one).</li>
- * <li>The resize handles which allow to resize the window.</li>
- * </ul>
- * After creating the view, the function
- * {@link #setPhoneWindow} needs to be called to make
- * the connection to it's owning PhoneWindow.
- * Note: At this time the application can change various attributes of the DecorView which
- * will break things (in settle/unexpected ways):
- * <ul>
- * <li>setElevation</li>
- * <li>setOutlineProvider</li>
- * <li>setSurfaceFormat</li>
- * <li>..</li>
- * </ul>
- * This will be mitigated once b/22527834 will be addressed.
- */
-public class NonClientDecorView extends LinearLayout
-        implements View.OnClickListener, View.OnTouchListener {
-    private final static String TAG = "NonClientDecorView";
-    // The height of a window which has focus in DIP.
-    private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
-    // The height of a window which has not in DIP.
-    private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
-    private PhoneWindow mOwner = null;
-    private boolean mWindowHasShadow = false;
-    private boolean mShowDecor = false;
-
-    // True if the window is being dragged.
-    private boolean mDragging = false;
-
-    // True when the left mouse button got released while dragging.
-    private boolean mLeftMouseButtonReleased;
-
-    // True if this window is resizable (which is currently only true when the decor is shown).
-    public boolean mVisible = false;
-
-    // The current focus state of the window for updating the window elevation.
-    private boolean mWindowHasFocus = true;
-
-    // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
-    // size calculation takes the shadow size into account. We set the elevation currently
-    // to max until the first layout command has been executed.
-    private boolean mAllowUpdateElevation = false;
-
-    public NonClientDecorView(Context context) {
-        super(context);
-    }
-
-    public NonClientDecorView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
-        mOwner = owner;
-        mWindowHasShadow = windowHasShadow;
-        mShowDecor = showDecor;
-        updateCaptionVisibility();
-        if (mWindowHasShadow) {
-            initializeElevation();
-        }
-        // By changing the outline provider to BOUNDS, the window can remove its
-        // background without removing the shadow.
-        mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
-
-        findViewById(R.id.maximize_window).setOnClickListener(this);
-        findViewById(R.id.close_window).setOnClickListener(this);
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent e) {
-        // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
-        // the old input device events get cancelled first. So no need to remember the kind of
-        // input device we are listening to.
-        switch (e.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                if (!mShowDecor) {
-                    // When there is no decor we should not react to anything.
-                    return false;
-                }
-                // A drag action is started if we aren't dragging already and the starting event is
-                // either a left mouse button or any other input device.
-                if (!mDragging &&
-                        (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
-                                (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
-                    mDragging = true;
-                    mLeftMouseButtonReleased = false;
-                    startMovingTask(e.getRawX(), e.getRawY());
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                if (mDragging && !mLeftMouseButtonReleased) {
-                    if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
-                            (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
-                        // There is no separate mouse button up call and if the user mixes mouse
-                        // button drag actions, we stop dragging once he releases the button.
-                        mLeftMouseButtonReleased = true;
-                        break;
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                if (!mDragging) {
-                    break;
-                }
-                // Abort the ongoing dragging.
-                mDragging = false;
-                return true;
-        }
-        return mDragging;
-    }
-
-    /**
-     * The phone window configuration has changed and the decor needs to be updated.
-     * @param showDecor True if the decor should be shown.
-     * @param windowHasShadow True when the window should show a shadow.
-     **/
-    public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
-        mShowDecor = showDecor;
-        updateCaptionVisibility();
-        if (windowHasShadow != mWindowHasShadow) {
-            mWindowHasShadow = windowHasShadow;
-            initializeElevation();
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (view.getId() == R.id.maximize_window) {
-            maximizeWindow();
-        } else if (view.getId() == R.id.close_window) {
-            mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
-        }
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        mWindowHasFocus = hasWindowFocus;
-        updateElevation();
-        super.onWindowFocusChanged(hasWindowFocus);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        // If the application changed its SystemUI metrics, we might also have to adapt
-        // our shadow elevation.
-        updateElevation();
-        mAllowUpdateElevation = true;
-
-        super.onLayout(changed, left, top, right, bottom);
-    }
-
-    @Override
-    public void addView(View child, int index, ViewGroup.LayoutParams params) {
-        // Make sure that we never get more then one client area in our view.
-        if (index >= 2 || getChildCount() >= 2) {
-            throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
-        }
-        super.addView(child, index, params);
-    }
-
-    /**
-     * Determine if the workspace is entirely covered by the window.
-     * @return Returns true when the window is filling the entire screen/workspace.
-     **/
-    private boolean isFillingScreen() {
-        return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
-                (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
-                        View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
-    }
-
-    /**
-     * Updates the visibility of the caption.
-     **/
-    private void updateCaptionVisibility() {
-        // Don't show the decor if the window has e.g. entered full screen.
-        boolean invisible = isFillingScreen() || !mShowDecor;
-        View caption = getChildAt(0);
-        caption.setVisibility(invisible ? GONE : VISIBLE);
-        caption.setOnTouchListener(this);
-        mVisible = !invisible;
-    }
-
-    /**
-     * The elevation gets set for the first time and the framework needs to be informed that
-     * the surface layer gets created with the shadow size in mind.
-     **/
-    private void initializeElevation() {
-        // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
-        mAllowUpdateElevation = false;
-        if (mWindowHasShadow) {
-            updateElevation();
-        } else {
-            mOwner.setElevation(0);
-        }
-    }
-
-    /**
-     * The shadow height gets controlled by the focus to visualize highlighted windows.
-     * Note: This will overwrite application elevation properties.
-     * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
-     *       will get no shadow as they are expected to be "full screen".
-     **/
-    public void updateElevation() {
-        float elevation = 0;
-        // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
-        // is bound to the content size and not the target size.
-        if (mWindowHasShadow
-                && ((DecorView) mOwner.getDecorView()).mBackdropFrameRenderer == null) {
-            boolean fill = isFillingScreen();
-            elevation = fill ? 0 :
-                    (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
-                            DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
-            // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
-            if (!mAllowUpdateElevation && !fill) {
-                elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
-            }
-            // Convert the DP elevation into physical pixels.
-            elevation = dipToPx(elevation);
-        }
-        // Don't change the elevation if it didn't change since it can require some time.
-        if (mOwner.getDecorView().getElevation() != elevation) {
-            mOwner.setElevation(elevation);
-        }
-    }
-
-    /**
-     * Converts a DIP measure into physical pixels.
-     * @param dip The dip value.
-     * @return Returns the number of pixels.
-     */
-    private float dipToPx(float dip) {
-        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
-                getResources().getDisplayMetrics());
-    }
-
-    /**
-     * Maximize the window by moving it to the maximized workspace stack.
-     **/
-    private void maximizeWindow() {
-        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Cannot change task workspace.");
-            }
-        }
-    }
-
-    public boolean isShowingDecor() {
-        return mShowDecor;
-    }
-
-    public int getDecorCaptionHeight() {
-        final View caption = getChildAt(0);
-        return (caption != null) ? caption.getHeight() : 0;
-    }
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 65e8058..bd41c5d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -580,7 +580,8 @@
     char heapminfreeOptsBuf[sizeof("-XX:HeapMinFree=")-1 + PROPERTY_VALUE_MAX];
     char heapmaxfreeOptsBuf[sizeof("-XX:HeapMaxFree=")-1 + PROPERTY_VALUE_MAX];
     char usejitOptsBuf[sizeof("-Xusejit:")-1 + PROPERTY_VALUE_MAX];
-    char jitcodecachesizeOptsBuf[sizeof("-Xjitcodecachesize:")-1 + PROPERTY_VALUE_MAX];
+    char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX];
+    char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX];
     char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX];
     char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
     char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
@@ -684,7 +685,8 @@
      * JIT related options.
      */
     parseRuntimeOption("dalvik.vm.usejit", usejitOptsBuf, "-Xusejit:");
-    parseRuntimeOption("dalvik.vm.jitcodecachesize", jitcodecachesizeOptsBuf, "-Xjitcodecachesize:");
+    parseRuntimeOption("dalvik.vm.jitmaxsize", jitmaxsizeOptsBuf, "-Xjitmaxsize:");
+    parseRuntimeOption("dalvik.vm.jitinitialsize", jitinitialsizeOptsBuf, "-Xjitinitialsize:");
     parseRuntimeOption("dalvik.vm.jitthreshold", jitthresholdOptsBuf, "-Xjitthreshold:");
 
     property_get("ro.config.low_ram", propBuf, "");
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
index 245aa0f..0927120 100644
--- a/core/jni/android_util_PathParser.cpp
+++ b/core/jni/android_util_PathParser.cpp
@@ -18,19 +18,22 @@
 
 #include <PathParser.h>
 #include <SkPath.h>
+#include <utils/VectorDrawableUtils.h>
 
 #include <android/log.h>
 #include "core_jni_helpers.h"
 
 namespace android {
 
+using namespace uirenderer;
+
 static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr,
         jint strLength) {
     const char* pathString = env->GetStringUTFChars(inputPathStr, NULL);
     SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle);
 
-    android::uirenderer::PathParser::ParseResult result;
-    android::uirenderer::PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
+    PathParser::ParseResult result;
+    PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
     env->ReleaseStringUTFChars(inputPathStr, pathString);
     if (result.failureOccurred) {
         ALOGE(result.failureMessage.c_str());
@@ -38,8 +41,74 @@
     return !result.failureOccurred;
 }
 
+static long createEmptyPathData(JNIEnv*, jobject) {
+    PathData* pathData = new PathData();
+    return reinterpret_cast<jlong>(pathData);
+}
+
+static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+    PathData* newPathData = new PathData(*pathData);
+    return reinterpret_cast<jlong>(newPathData);
+}
+
+static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) {
+    const char* pathString = env->GetStringUTFChars(inputStr, NULL);
+    PathData* pathData = new PathData();
+    PathParser::ParseResult result;
+    PathParser::getPathDataFromString(pathData, &result, pathString, strLength);
+    env->ReleaseStringUTFChars(inputStr, pathString);
+    if (!result.failureOccurred) {
+        return reinterpret_cast<jlong>(pathData);
+    } else {
+        delete pathData;
+        ALOGE(result.failureMessage.c_str());
+        return NULL;
+    }
+}
+
+static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr,
+        jlong toPathDataPtr, jfloat fraction) {
+    PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+    return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData,
+            *toPathData, fraction);
+}
+
+static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle);
+    delete pathData;
+}
+
+static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) {
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+    return VectorDrawableUtils::canMorph(*fromPathData, *toPathData);
+}
+
+static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) {
+    PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+    PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+    *outPathData = *fromPathData;
+}
+
+static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) {
+    PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+    SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr);
+    VectorDrawableUtils::verbsToPath(skPath, *pathData);
+}
+
 static const JNINativeMethod gMethods[] = {
-    {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}
+    {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath},
+    {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData},
+    {"nCreatePathData", "!(J)J", (void*)createPathData},
+    {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath},
+    {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData},
+    {"nFinalize", "!(J)V", (void*)deletePathData},
+    {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData},
+    {"nSetPathData", "!(JJ)V", (void*)setPathData},
+    {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData},
 };
 
 int register_android_util_PathParser(JNIEnv* env) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3f1be456..73c7487 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -21,6 +21,7 @@
 #include <linux/fs.h>
 
 #include <list>
+#include <sstream>
 #include <string>
 
 #include <fcntl.h>
@@ -74,8 +75,10 @@
   MOUNT_EXTERNAL_WRITE = 3,
 };
 
-static void RuntimeAbort(JNIEnv* env) {
-  env->FatalError("RuntimeAbort");
+static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
+  std::ostringstream oss;
+  oss << __FILE__ << ":" << line << ": " << msg;
+  env->FatalError(oss.str().c_str());
 }
 
 // This signal handler is for zygote mode, since the zygote must reap its children
@@ -169,12 +172,11 @@
 
   ScopedIntArrayRO gids(env, javaGids);
   if (gids.get() == NULL) {
-      RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "Getting gids int array failed");
   }
   int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
   if (rc == -1) {
-    ALOGE("setgroups failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "setgroups failed");
   }
 }
 
@@ -194,8 +196,7 @@
     ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
     ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
     if (javaRlimit.size() != 3) {
-      ALOGE("rlimits array must have a second dimension of size 3");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "rlimits array must have a second dimension of size 3");
     }
 
     rlim.rlim_cur = javaRlimit[1];
@@ -205,7 +206,7 @@
     if (rc == -1) {
       ALOGE("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
             rlim.rlim_max);
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setrlimit failed");
     }
   }
 }
@@ -216,8 +217,7 @@
 static void EnableKeepCapabilities(JNIEnv* env) {
   int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
   if (rc == -1) {
-    ALOGE("prctl(PR_SET_KEEPCAPS) failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "prctl(PR_SET_KEEPCAPS) failed");
   }
 }
 
@@ -229,8 +229,7 @@
         ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
               "your kernel is compiled with file capabilities support");
       } else {
-        ALOGE("prctl(PR_CAPBSET_DROP) failed");
-        RuntimeAbort(env);
+        RuntimeAbort(env, __LINE__, "prctl(PR_CAPBSET_DROP) failed");
       }
     }
   }
@@ -251,7 +250,7 @@
 
   if (capset(&capheader, &capdata[0]) == -1) {
     ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective);
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "capset failed");
   }
 }
 
@@ -259,7 +258,7 @@
   errno = -set_sched_policy(0, SP_DEFAULT);
   if (errno != 0) {
     ALOGE("set_sched_policy(0, SP_DEFAULT) failed");
-    RuntimeAbort(env);
+    RuntimeAbort(env, __LINE__, "set_sched_policy(0, SP_DEFAULT) failed");
   }
 }
 
@@ -370,8 +369,7 @@
   jsize count = env->GetArrayLength(fdsToClose);
   ScopedIntArrayRO ar(env, fdsToClose);
   if (ar.get() == NULL) {
-      ALOGE("Bad fd array");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Bad fd array");
   }
   jsize i;
   int devnull;
@@ -379,13 +377,13 @@
     devnull = open("/dev/null", O_RDWR);
     if (devnull < 0) {
       ALOGE("Failed to open /dev/null: %s", strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Failed to open /dev/null");
       continue;
     }
     ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
     if (dup2(devnull, ar[i]) < 0) {
       ALOGE("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Failed dup2()");
     }
     close(devnull);
   }
@@ -493,8 +491,7 @@
         // FUSE hasn't been created yet by init.
         // In either case, continue without external storage.
       } else {
-        ALOGE("Cannot continue without emulated storage");
-        RuntimeAbort(env);
+        RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage");
       }
     }
 
@@ -522,13 +519,13 @@
     int rc = setresgid(gid, gid, gid);
     if (rc == -1) {
       ALOGE("setresgid(%d) failed: %s", gid, strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setresgid failed");
     }
 
     rc = setresuid(uid, uid, uid);
     if (rc == -1) {
       ALOGE("setresuid(%d) failed: %s", uid, strerror(errno));
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "setresuid failed");
     }
 
     if (NeedsNoRandomizeWorkaround()) {
@@ -550,8 +547,7 @@
         se_info = new ScopedUtfChars(env, java_se_info);
         se_info_c_str = se_info->c_str();
         if (se_info_c_str == NULL) {
-          ALOGE("se_info_c_str == NULL");
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "se_info_c_str == NULL");
         }
     }
     const char* se_name_c_str = NULL;
@@ -560,15 +556,14 @@
         se_name = new ScopedUtfChars(env, java_se_name);
         se_name_c_str = se_name->c_str();
         if (se_name_c_str == NULL) {
-          ALOGE("se_name_c_str == NULL");
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "se_name_c_str == NULL");
         }
     }
     rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
     if (rc == -1) {
       ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
             is_system_server, se_info_c_str, se_name_c_str);
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed");
     }
 
     // Make it easier to debug audit logs by setting the main thread's name to the
@@ -588,8 +583,7 @@
     env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
                               is_system_server ? NULL : instructionSet);
     if (env->ExceptionCheck()) {
-      ALOGE("Error calling post fork hooks.");
-      RuntimeAbort(env);
+      RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
     }
   } else if (pid > 0) {
     // the parent process
@@ -641,7 +635,7 @@
       int status;
       if (waitpid(pid, &status, WNOHANG) == pid) {
           ALOGE("System server process %d has died. Restarting Zygote!", pid);
-          RuntimeAbort(env);
+          RuntimeAbort(env, __LINE__, "System server process has died. Restarting Zygote!");
       }
   }
   return pid;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6bdf71b..42ede31 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1831,6 +1831,12 @@
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to bind to third party quick settings tiles.
+         <p>Should only be requested by the System, should be required by
+         QSTileService declarations.-->
+    <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
+        android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to force a BACK operation on whatever is the
          top activity.
          <p>Not for use by third-party applications.
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/decor_caption_title.xml
similarity index 84%
rename from core/res/res/drawable/non_client_decor_title.xml
rename to core/res/res/drawable/decor_caption_title.xml
index e50daea..591605d3 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/core/res/res/drawable/decor_caption_title.xml
@@ -16,6 +16,6 @@
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_window_focused="true"
-          android:drawable="@drawable/non_client_decor_title_focused" />
-    <item android:drawable="@drawable/non_client_decor_title_unfocused" />
+          android:drawable="@drawable/decor_caption_title_focused" />
+    <item android:drawable="@drawable/decor_caption_title_unfocused" />
 </selector>
diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_focused.xml
rename to core/res/res/drawable/decor_caption_title_focused.xml
diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_unfocused.xml
rename to core/res/res/drawable/decor_caption_title_unfocused.xml
diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/decor_caption_dark.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_dark.xml
rename to core/res/res/layout/decor_caption_dark.xml
index 40b8960..273264d 100644
--- a/core/res/res/layout/non_client_decor_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -17,16 +17,17 @@
 */
 -->
 
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"
-        android:background="@drawable/non_client_decor_title"
+        android:background="@drawable/decor_caption_title"
         android:focusable="false"
         android:descendantFocusability="blocksDescendants" >
         <LinearLayout
@@ -53,4 +54,4 @@
             android:contentDescription="@string/close_button_text"
             android:background="@drawable/decor_close_button_dark" />
     </LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/decor_caption_light.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_light.xml
rename to core/res/res/layout/decor_caption_light.xml
index c75d526..fd9198e 100644
--- a/core/res/res/layout/non_client_decor_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -17,16 +17,17 @@
 */
 -->
 
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:descendantFocusability="beforeDescendants" >
     <LinearLayout
+        android:id="@+id/caption"
         android:layout_width="match_parent"
         android:layout_gravity="end"
         android:layout_height="wrap_content"
-        android:background="@drawable/non_client_decor_title"
+        android:background="@drawable/decor_caption_title"
         android:focusable="false"
         android:descendantFocusability="blocksDescendants" >
         <LinearLayout
@@ -53,4 +54,4 @@
             android:contentDescription="@string/close_button_text"
             android:background="@drawable/decor_close_button_light" />
     </LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
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 556467a..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" />
@@ -1957,9 +1958,9 @@
   <java-symbol type="id" name="maximize_window" />
   <java-symbol type="id" name="close_window" />
   <java-symbol type="id" name="client_decor_placeholder" />
-  <java-symbol type="layout" name="non_client_decor_light" />
-  <java-symbol type="layout" name="non_client_decor_dark" />
-  <java-symbol type="drawable" name="non_client_decor_title_focused" />
+  <java-symbol type="layout" name="decor_caption_light" />
+  <java-symbol type="layout" name="decor_caption_dark" />
+  <java-symbol type="drawable" name="decor_caption_title_focused" />
 
   <!-- From TelephonyProvider -->
   <java-symbol type="xml" name="apns" />
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/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index a50c945..0ee877e 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1424,7 +1424,7 @@
         }
     }
 
-    static int resolveDensity(@NonNull Resources r, int parentDensity) {
+    static int resolveDensity(@Nullable Resources r, int parentDensity) {
         final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
         return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
     }
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index eee9b24..f630055e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -231,6 +231,9 @@
     private int mDpiScaledHeight = 0;
     private Insets mDpiScaledInsets = Insets.NONE;
 
+    /** Whether DPI-scaled width, height, and insets need to be updated. */
+    private boolean mDpiScaledDirty = true;
+
     // Temp variable, only for saving "new" operation at the draw() time.
     private final float[] mTmpFloats = new float[9];
     private final Matrix mTmpMatrix = new Matrix();
@@ -259,9 +262,13 @@
      *            displayed, or {@code null} to use the constant state defaults
      */
     private void updateLocalState(Resources res) {
-        mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity);
+        final int density = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mDensity);
+        if (mTargetDensity != density) {
+            mTargetDensity = density;
+            mDpiScaledDirty = true;
+        }
+
         mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
-        computeVectorSize();
     }
 
     @Override
@@ -422,17 +429,26 @@
 
     @Override
     public int getIntrinsicWidth() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledWidth;
     }
 
     @Override
     public int getIntrinsicHeight() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledHeight;
     }
 
     /** @hide */
     @Override
     public Insets getOpticalInsets() {
+        if (mDpiScaledDirty) {
+            computeVectorSize();
+        }
         return mDpiScaledInsets;
     }
 
@@ -444,7 +460,7 @@
         final VPathRenderer pathRenderer = mVectorState.mVPathRenderer;
         final Insets opticalInsets = pathRenderer.mOpticalInsets;
 
-        final int sourceDensity = pathRenderer.mSourceDensity;
+        final int sourceDensity = pathRenderer.mDensity;
         final int targetDensity = mTargetDensity;
         if (targetDensity != sourceDensity) {
             mDpiScaledWidth = Drawable.scaleFromDensity(
@@ -465,6 +481,8 @@
             mDpiScaledHeight = (int) pathRenderer.mBaseHeight;
             mDpiScaledInsets = opticalInsets;
         }
+
+        mDpiScaledDirty = false;
     }
 
     @Override
@@ -481,6 +499,11 @@
             return;
         }
 
+        final VPathRenderer path = state.mVPathRenderer;
+        final boolean changedDensity = path.setDensity(
+                Drawable.resolveDensity(t.getResources(), 0));
+        mDpiScaledDirty |= changedDensity;
+
         if (state.mThemeAttrs != null) {
             final TypedArray a = t.resolveAttributes(
                     state.mThemeAttrs, R.styleable.VectorDrawable);
@@ -492,6 +515,9 @@
             } finally {
                 a.recycle();
             }
+
+            // May have changed size.
+            mDpiScaledDirty = true;
         }
 
         // Apply theme to contained color state list.
@@ -499,7 +525,6 @@
             state.mTint = state.mTint.obtainForTheme(t);
         }
 
-        final VPathRenderer path = state.mVPathRenderer;
         if (path != null && path.canApplyTheme()) {
             path.applyTheme(t);
         }
@@ -565,21 +590,24 @@
     }
 
     @Override
-    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+    public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+            @NonNull AttributeSet attrs, @Nullable Theme theme)
             throws XmlPullParserException, IOException {
         final VectorDrawableState state = mVectorState;
-        final VPathRenderer pathRenderer = new VPathRenderer();
-        state.mVPathRenderer = pathRenderer;
+        state.mVPathRenderer = new VPathRenderer();
+        state.mVPathRenderer.setDensity(Drawable.resolveDensity(r, 0));
 
-        final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
+        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
         updateStateFromTypedArray(a);
         a.recycle();
 
+        mDpiScaledDirty = true;
+
         state.mCacheDirty = true;
-        inflateInternal(res, parser, attrs, theme);
+        inflateChildElements(r, parser, attrs, theme);
 
         // Update local properties.
-        updateLocalState(res);
+        updateLocalState(r);
     }
 
     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
@@ -592,13 +620,6 @@
         // Extract the theme attributes, if any.
         state.mThemeAttrs = a.extractThemeAttrs();
 
-        // The density may have changed since the last update (if any). Any
-        // dimension-type attributes will need their default values scaled.
-        final int targetDensity = Drawable.resolveDensity(a.getResources(), 0);
-        final int sourceDensity = pathRenderer.mSourceDensity;
-        final float densityScale = targetDensity / (float) sourceDensity;
-        pathRenderer.mSourceDensity = targetDensity;
-
         final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
         if (tintMode != -1) {
             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
@@ -626,11 +647,9 @@
         }
 
         pathRenderer.mBaseWidth = a.getDimension(
-                R.styleable.VectorDrawable_width,
-                pathRenderer.mBaseWidth * densityScale);
+                R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
         pathRenderer.mBaseHeight = a.getDimension(
-                R.styleable.VectorDrawable_height,
-                pathRenderer.mBaseHeight * densityScale);
+                R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
 
         if (pathRenderer.mBaseWidth <= 0) {
             throw new XmlPullParserException(a.getPositionDescription() +
@@ -641,21 +660,17 @@
         }
 
         final int insetLeft = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetLeft,
-                (int) (pathRenderer.mOpticalInsets.left * densityScale));
+                R.styleable.VectorDrawable_opticalInsetLeft, pathRenderer.mOpticalInsets.left);
         final int insetTop = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetTop,
-                (int) (pathRenderer.mOpticalInsets.top * densityScale));
+                R.styleable.VectorDrawable_opticalInsetTop, pathRenderer.mOpticalInsets.top);
         final int insetRight = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetRight,
-                (int) (pathRenderer.mOpticalInsets.right * densityScale));
+                R.styleable.VectorDrawable_opticalInsetRight, pathRenderer.mOpticalInsets.right);
         final int insetBottom = a.getDimensionPixelOffset(
-                R.styleable.VectorDrawable_opticalInsetBottom,
-                (int) (pathRenderer.mOpticalInsets.bottom * densityScale));
+                R.styleable.VectorDrawable_opticalInsetBottom, pathRenderer.mOpticalInsets.bottom);
         pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
 
-        final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
-                pathRenderer.getAlpha());
+        final float alphaInFloat = a.getFloat(
+                R.styleable.VectorDrawable_alpha, pathRenderer.getAlpha());
         pathRenderer.setAlpha(alphaInFloat);
 
         final String name = a.getString(R.styleable.VectorDrawable_name);
@@ -665,7 +680,7 @@
         }
     }
 
-    private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
+    private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
             Theme theme) throws XmlPullParserException, IOException {
         final VectorDrawableState state = mVectorState;
         final VPathRenderer pathRenderer = state.mVPathRenderer;
@@ -929,7 +944,7 @@
         int mRootAlpha = 0xFF;
         String mRootName = null;
 
-        int mSourceDensity = DisplayMetrics.DENSITY_DEFAULT;
+        int mDensity = DisplayMetrics.DENSITY_DEFAULT;
 
         final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
 
@@ -966,12 +981,37 @@
             mChangingConfigurations = copy.mChangingConfigurations;
             mRootAlpha = copy.mRootAlpha;
             mRootName = copy.mRootName;
-            mSourceDensity = copy.mSourceDensity;
+            mDensity = copy.mDensity;
             if (copy.mRootName != null) {
                 mVGTargetsMap.put(copy.mRootName, this);
             }
         }
 
+        public final boolean setDensity(int targetDensity) {
+            if (mDensity != targetDensity) {
+                final int sourceDensity = mDensity;
+                mDensity = targetDensity;
+                applyDensityScaling(sourceDensity, targetDensity);
+                return true;
+            }
+            return false;
+        }
+
+        private void applyDensityScaling(int sourceDensity, int targetDensity) {
+            mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
+            mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
+
+            final int insetLeft = Drawable.scaleFromDensity(
+                    mOpticalInsets.left, sourceDensity, targetDensity, false);
+            final int insetTop = Drawable.scaleFromDensity(
+                    mOpticalInsets.top, sourceDensity, targetDensity, false);
+            final int insetRight = Drawable.scaleFromDensity(
+                    mOpticalInsets.right, sourceDensity, targetDensity, false);
+            final int insetBottom = Drawable.scaleFromDensity(
+                    mOpticalInsets.bottom, sourceDensity, targetDensity, false);
+            mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+        }
+
         public boolean canApplyTheme() {
             return mRootGroup.canApplyTheme();
         }
@@ -1321,7 +1361,7 @@
      * Common Path information for clip path and normal path.
      */
     private static abstract class VPath implements VObject {
-        protected PathParser.PathDataNode[] mNodes = null;
+        protected PathParser.PathData mPathData = null;
         String mPathName;
         int mChangingConfigurations;
 
@@ -1332,7 +1372,7 @@
         public VPath(VPath copy) {
             mPathName = copy.mPathName;
             mChangingConfigurations = copy.mChangingConfigurations;
-            mNodes = PathParser.deepCopyNodes(copy.mNodes);
+            mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
         }
 
         public String getPathName() {
@@ -1345,18 +1385,14 @@
 
         /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
         @SuppressWarnings("unused")
-        public PathParser.PathDataNode[] getPathData() {
-            return mNodes;
+        public PathParser.PathData getPathData() {
+            return mPathData;
         }
 
+        // TODO: Move the PathEvaluator and this setter and the getter above into native.
         @SuppressWarnings("unused")
-        public void setPathData(PathParser.PathDataNode[] nodes) {
-            if (!PathParser.canMorph(mNodes, nodes)) {
-                // This should not happen in the middle of animation.
-                mNodes = PathParser.deepCopyNodes(nodes);
-            } else {
-                PathParser.updateNodes(mNodes, nodes);
-            }
+        public void setPathData(PathParser.PathData pathData) {
+            mPathData.setPathData(pathData);
         }
 
         @Override
@@ -1392,8 +1428,8 @@
          * @param outPath the output path
          */
         protected void toPath(TempState temp, Path outPath) {
-            if (mNodes != null) {
-                PathParser.PathDataNode.nodesToPath(mNodes, outPath);
+            if (mPathData != null) {
+                PathParser.createPathFromPathData(outPath, mPathData);
             }
         }
 
@@ -1488,9 +1524,9 @@
                 mPathName = pathName;
             }
 
-            final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData);
-            if (pathData != null) {
-                mNodes = PathParser.createNodesFromPathData(pathData);
+            final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
+            if (pathDataString != null) {
+                mPathData = new PathParser.PathData(pathDataString);
             }
         }
 
@@ -1719,9 +1755,9 @@
                 mPathName = pathName;
             }
 
-            final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData);
-            if (pathData != null) {
-                mNodes = PathParser.createNodesFromPathData(pathData);
+            final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
+            if (pathString != null) {
+                mPathData = new PathParser.PathData(pathString);
             }
 
             final ColorStateList fillColors = a.getColorStateList(
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8565372..0a57d50 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -29,6 +29,8 @@
     utils/NinePatchImpl.cpp \
     utils/StringUtils.cpp \
     utils/TestWindowContext.cpp \
+    utils/VectorDrawableUtils.cpp \
+    utils/TestUtils.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
@@ -218,7 +220,7 @@
     unit_tests/FatVectorTests.cpp \
     unit_tests/LayerUpdateQueueTests.cpp \
     unit_tests/LinearAllocatorTests.cpp \
-    unit_tests/PathParserTests.cpp \
+    unit_tests/VectorDrawableTests.cpp \
     unit_tests/OffscreenBufferPoolTests.cpp \
     unit_tests/StringUtilsTests.cpp
 
@@ -252,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/PathParser.cpp b/libs/hwui/PathParser.cpp
index 35230ff..4e9ac9c 100644
--- a/libs/hwui/PathParser.cpp
+++ b/libs/hwui/PathParser.cpp
@@ -221,7 +221,7 @@
         result->failureMessage = "No verbs found in the string for pathData";
         return;
     }
-    VectorDrawablePath::verbsToPath(skPath, &pathData);
+    VectorDrawableUtils::verbsToPath(skPath, pathData);
     return;
 }
 
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
index a9c1e60..4c87b18 100644
--- a/libs/hwui/PathParser.h
+++ b/libs/hwui/PathParser.h
@@ -18,6 +18,7 @@
 #define ANDROID_HWUI_PATHPARSER_H
 
 #include "VectorDrawablePath.h"
+#include "utils/VectorDrawableUtils.h"
 
 #include <jni.h>
 #include <android/log.h>
@@ -40,7 +41,7 @@
      */
     ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result,
             const char* pathStr, size_t strLength);
-    static void getPathDataFromString(PathData* outData, ParseResult* result,
+    ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result,
             const char* pathStr, size_t strLength);
     static void dump(const PathData& data);
 };
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 716d536..3f24f44 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -288,6 +288,9 @@
     bool transformUpdateNeeded = false;
     if (!mLayer) {
         mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+#if !HWUI_NEW_OPS
+        applyLayerPropertiesToLayer(info);
+#endif
         damageSelf(info);
         transformUpdateNeeded = true;
     } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp
index 05ea2da..c9a54ca 100644
--- a/libs/hwui/VectorDrawablePath.cpp
+++ b/libs/hwui/VectorDrawablePath.cpp
@@ -17,6 +17,7 @@
 #include "VectorDrawablePath.h"
 
 #include "PathParser.h"
+#include "utils/VectorDrawableUtils.h"
 
 #include <math.h>
 #include <utils/Log.h>
@@ -24,476 +25,35 @@
 namespace android {
 namespace uirenderer {
 
-class PathResolver {
-public:
-    float currentX = 0;
-    float currentY = 0;
-    float ctrlPointX = 0;
-    float ctrlPointY = 0;
-    float currentSegmentStartX = 0;
-    float currentSegmentStartY = 0;
-    void addCommand(SkPath* outPath, char previousCmd,
-            char cmd, const std::vector<float>* points, size_t start, size_t end);
-};
 
 VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) {
     PathParser::ParseResult result;
     PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
     if (!result.failureOccurred) {
-        verbsToPath(&mSkPath, &mData);
+        VectorDrawableUtils::verbsToPath(&mSkPath, mData);
     }
 }
 
 VectorDrawablePath::VectorDrawablePath(const PathData& data) {
     mData = data;
     // Now we need to construct a path
-    verbsToPath(&mSkPath, &data);
+    VectorDrawableUtils::verbsToPath(&mSkPath, data);
 }
 
 VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) {
     mData = path.mData;
-    verbsToPath(&mSkPath, &mData);
+    VectorDrawableUtils::verbsToPath(&mSkPath, mData);
 }
 
-bool VectorDrawablePath::canMorph(const PathData& morphTo) {
-    if (mData.verbs.size() != morphTo.verbs.size()) {
-        return false;
-    }
 
-    for (unsigned int i = 0; i < mData.verbs.size(); i++) {
-        if (mData.verbs[i] != morphTo.verbs[i]
-                || mData.verbSizes[i] != morphTo.verbSizes[i]) {
-            return false;
-        }
-    }
-    return true;
+bool VectorDrawablePath::canMorph(const PathData& morphTo) {
+    return VectorDrawableUtils::canMorph(mData, morphTo);
 }
 
 bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) {
     return canMorph(path.mData);
 }
- /**
- * Convert an array of PathVerb to Path.
- */
-void VectorDrawablePath::verbsToPath(SkPath* outPath, const PathData* data) {
-    PathResolver resolver;
-    char previousCommand = 'm';
-    size_t start = 0;
-    outPath->reset();
-    for (unsigned int i = 0; i < data->verbs.size(); i++) {
-        size_t verbSize = data->verbSizes[i];
-        resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start,
-                start + verbSize);
-        previousCommand = data->verbs[i];
-        start += verbSize;
-    }
-}
 
-/**
- * The current PathVerb will be interpolated between the
- * <code>nodeFrom</code> and <code>nodeTo</code> according to the
- * <code>fraction</code>.
- *
- * @param nodeFrom The start value as a PathVerb.
- * @param nodeTo The end value as a PathVerb
- * @param fraction The fraction to interpolate.
- */
-void VectorDrawablePath::interpolatePaths(PathData* outData,
-        const PathData* from, const PathData* to, float fraction) {
-    outData->points.resize(from->points.size());
-    outData->verbSizes = from->verbSizes;
-    outData->verbs = from->verbs;
-
-    for (size_t i = 0; i < from->points.size(); i++) {
-        outData->points[i] = from->points[i] * (1 - fraction) + to->points[i] * fraction;
-    }
-}
-
-/**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
-static void arcToBezier(SkPath* p,
-        double cx,
-        double cy,
-        double a,
-        double b,
-        double e1x,
-        double e1y,
-        double theta,
-        double start,
-        double sweep) {
-    // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
-    // and http://www.spaceroots.org/documents/ellipse/node22.html
-
-    // Maximum of 45 degrees per cubic Bezier segment
-    int numSegments = ceil(fabs(sweep * 4 / M_PI));
-
-    double eta1 = start;
-    double cosTheta = cos(theta);
-    double sinTheta = sin(theta);
-    double cosEta1 = cos(eta1);
-    double sinEta1 = sin(eta1);
-    double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
-    double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
-    double anglePerSegment = sweep / numSegments;
-    for (int i = 0; i < numSegments; i++) {
-        double eta2 = eta1 + anglePerSegment;
-        double sinEta2 = sin(eta2);
-        double cosEta2 = cos(eta2);
-        double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
-        double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
-        double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
-        double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
-        double tanDiff2 = tan((eta2 - eta1) / 2);
-        double alpha =
-                sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
-        double q1x = e1x + alpha * ep1x;
-        double q1y = e1y + alpha * ep1y;
-        double q2x = e2x - alpha * ep2x;
-        double q2y = e2y - alpha * ep2y;
-
-        p->cubicTo((float) q1x,
-                (float) q1y,
-                (float) q2x,
-                (float) q2y,
-                (float) e2x,
-                (float) e2y);
-        eta1 = eta2;
-        e1x = e2x;
-        e1y = e2y;
-        ep1x = ep2x;
-        ep1y = ep2y;
-    }
-}
-
-inline double toRadians(float theta) { return theta * M_PI / 180;}
-
-static void drawArc(SkPath* p,
-        float x0,
-        float y0,
-        float x1,
-        float y1,
-        float a,
-        float b,
-        float theta,
-        bool isMoreThanHalf,
-        bool isPositiveArc) {
-
-    /* Convert rotation angle from degrees to radians */
-    double thetaD = toRadians(theta);
-    /* Pre-compute rotation matrix entries */
-    double cosTheta = cos(thetaD);
-    double sinTheta = sin(thetaD);
-    /* Transform (x0, y0) and (x1, y1) into unit space */
-    /* using (inverse) rotation, followed by (inverse) scale */
-    double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
-    double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
-    double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
-    double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
-    /* Compute differences and averages */
-    double dx = x0p - x1p;
-    double dy = y0p - y1p;
-    double xm = (x0p + x1p) / 2;
-    double ym = (y0p + y1p) / 2;
-    /* Solve for intersecting unit circles */
-    double dsq = dx * dx + dy * dy;
-    if (dsq == 0.0) {
-        ALOGW("Points are coincident");
-        return; /* Points are coincident */
-    }
-    double disc = 1.0 / dsq - 1.0 / 4.0;
-    if (disc < 0.0) {
-        ALOGW("Points are too far apart %f", dsq);
-        float adjust = (float) (sqrt(dsq) / 1.99999);
-        drawArc(p, x0, y0, x1, y1, a * adjust,
-                b * adjust, theta, isMoreThanHalf, isPositiveArc);
-        return; /* Points are too far apart */
-    }
-    double s = sqrt(disc);
-    double sdx = s * dx;
-    double sdy = s * dy;
-    double cx;
-    double cy;
-    if (isMoreThanHalf == isPositiveArc) {
-        cx = xm - sdy;
-        cy = ym + sdx;
-    } else {
-        cx = xm + sdy;
-        cy = ym - sdx;
-    }
-
-    double eta0 = atan2((y0p - cy), (x0p - cx));
-
-    double eta1 = atan2((y1p - cy), (x1p - cx));
-
-    double sweep = (eta1 - eta0);
-    if (isPositiveArc != (sweep >= 0)) {
-        if (sweep > 0) {
-            sweep -= 2 * M_PI;
-        } else {
-            sweep += 2 * M_PI;
-        }
-    }
-
-    cx *= a;
-    cy *= b;
-    double tcx = cx;
-    cx = cx * cosTheta - cy * sinTheta;
-    cy = tcx * sinTheta + cy * cosTheta;
-
-    arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-}
-
-// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
-void PathResolver::addCommand(SkPath* outPath, char previousCmd,
-        char cmd, const std::vector<float>* points, size_t start, size_t end) {
-
-    int incr = 2;
-    float reflectiveCtrlPointX;
-    float reflectiveCtrlPointY;
-
-    switch (cmd) {
-    case 'z':
-    case 'Z':
-        outPath->close();
-        // Path is closed here, but we need to move the pen to the
-        // closed position. So we cache the segment's starting position,
-        // and restore it here.
-        currentX = currentSegmentStartX;
-        currentY = currentSegmentStartY;
-        ctrlPointX = currentSegmentStartX;
-        ctrlPointY = currentSegmentStartY;
-        outPath->moveTo(currentX, currentY);
-        break;
-    case 'm':
-    case 'M':
-    case 'l':
-    case 'L':
-    case 't':
-    case 'T':
-        incr = 2;
-        break;
-    case 'h':
-    case 'H':
-    case 'v':
-    case 'V':
-        incr = 1;
-        break;
-    case 'c':
-    case 'C':
-        incr = 6;
-        break;
-    case 's':
-    case 'S':
-    case 'q':
-    case 'Q':
-        incr = 4;
-        break;
-    case 'a':
-    case 'A':
-        incr = 7;
-        break;
-    }
-
-    for (unsigned int k = start; k < end; k += incr) {
-        switch (cmd) {
-        case 'm': // moveto - Start a new sub-path (relative)
-            currentX += points->at(k + 0);
-            currentY += points->at(k + 1);
-            if (k > start) {
-                // According to the spec, if a moveto is followed by multiple
-                // pairs of coordinates, the subsequent pairs are treated as
-                // implicit lineto commands.
-                outPath->rLineTo(points->at(k + 0), points->at(k + 1));
-            } else {
-                outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
-                currentSegmentStartX = currentX;
-                currentSegmentStartY = currentY;
-            }
-            break;
-        case 'M': // moveto - Start a new sub-path
-            currentX = points->at(k + 0);
-            currentY = points->at(k + 1);
-            if (k > start) {
-                // According to the spec, if a moveto is followed by multiple
-                // pairs of coordinates, the subsequent pairs are treated as
-                // implicit lineto commands.
-                outPath->lineTo(points->at(k + 0), points->at(k + 1));
-            } else {
-                outPath->moveTo(points->at(k + 0), points->at(k + 1));
-                currentSegmentStartX = currentX;
-                currentSegmentStartY = currentY;
-            }
-            break;
-        case 'l': // lineto - Draw a line from the current point (relative)
-            outPath->rLineTo(points->at(k + 0), points->at(k + 1));
-            currentX += points->at(k + 0);
-            currentY += points->at(k + 1);
-            break;
-        case 'L': // lineto - Draw a line from the current point
-            outPath->lineTo(points->at(k + 0), points->at(k + 1));
-            currentX = points->at(k + 0);
-            currentY = points->at(k + 1);
-            break;
-        case 'h': // horizontal lineto - Draws a horizontal line (relative)
-            outPath->rLineTo(points->at(k + 0), 0);
-            currentX += points->at(k + 0);
-            break;
-        case 'H': // horizontal lineto - Draws a horizontal line
-            outPath->lineTo(points->at(k + 0), currentY);
-            currentX = points->at(k + 0);
-            break;
-        case 'v': // vertical lineto - Draws a vertical line from the current point (r)
-            outPath->rLineTo(0, points->at(k + 0));
-            currentY += points->at(k + 0);
-            break;
-        case 'V': // vertical lineto - Draws a vertical line from the current point
-            outPath->lineTo(currentX, points->at(k + 0));
-            currentY = points->at(k + 0);
-            break;
-        case 'c': // curveto - Draws a cubic Bézier curve (relative)
-            outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
-                    points->at(k + 4), points->at(k + 5));
-
-            ctrlPointX = currentX + points->at(k + 2);
-            ctrlPointY = currentY + points->at(k + 3);
-            currentX += points->at(k + 4);
-            currentY += points->at(k + 5);
-
-            break;
-        case 'C': // curveto - Draws a cubic Bézier curve
-            outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
-                    points->at(k + 4), points->at(k + 5));
-            currentX = points->at(k + 4);
-            currentY = points->at(k + 5);
-            ctrlPointX = points->at(k + 2);
-            ctrlPointY = points->at(k + 3);
-            break;
-        case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
-            reflectiveCtrlPointX = 0;
-            reflectiveCtrlPointY = 0;
-            if (previousCmd == 'c' || previousCmd == 's'
-                    || previousCmd == 'C' || previousCmd == 'S') {
-                reflectiveCtrlPointX = currentX - ctrlPointX;
-                reflectiveCtrlPointY = currentY - ctrlPointY;
-            }
-            outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                    points->at(k + 0), points->at(k + 1),
-                    points->at(k + 2), points->at(k + 3));
-            ctrlPointX = currentX + points->at(k + 0);
-            ctrlPointY = currentY + points->at(k + 1);
-            currentX += points->at(k + 2);
-            currentY += points->at(k + 3);
-            break;
-        case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
-            reflectiveCtrlPointX = currentX;
-            reflectiveCtrlPointY = currentY;
-            if (previousCmd == 'c' || previousCmd == 's'
-                    || previousCmd == 'C' || previousCmd == 'S') {
-                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-            }
-            outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                    points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
-            ctrlPointX = points->at(k + 0);
-            ctrlPointY = points->at(k + 1);
-            currentX = points->at(k + 2);
-            currentY = points->at(k + 3);
-            break;
-        case 'q': // Draws a quadratic Bézier (relative)
-            outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
-            ctrlPointX = currentX + points->at(k + 0);
-            ctrlPointY = currentY + points->at(k + 1);
-            currentX += points->at(k + 2);
-            currentY += points->at(k + 3);
-            break;
-        case 'Q': // Draws a quadratic Bézier
-            outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
-            ctrlPointX = points->at(k + 0);
-            ctrlPointY = points->at(k + 1);
-            currentX = points->at(k + 2);
-            currentY = points->at(k + 3);
-            break;
-        case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
-            reflectiveCtrlPointX = 0;
-            reflectiveCtrlPointY = 0;
-            if (previousCmd == 'q' || previousCmd == 't'
-                    || previousCmd == 'Q' || previousCmd == 'T') {
-                reflectiveCtrlPointX = currentX - ctrlPointX;
-                reflectiveCtrlPointY = currentY - ctrlPointY;
-            }
-            outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                    points->at(k + 0), points->at(k + 1));
-            ctrlPointX = currentX + reflectiveCtrlPointX;
-            ctrlPointY = currentY + reflectiveCtrlPointY;
-            currentX += points->at(k + 0);
-            currentY += points->at(k + 1);
-            break;
-        case 'T': // Draws a quadratic Bézier curve (reflective control point)
-            reflectiveCtrlPointX = currentX;
-            reflectiveCtrlPointY = currentY;
-            if (previousCmd == 'q' || previousCmd == 't'
-                    || previousCmd == 'Q' || previousCmd == 'T') {
-                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
-                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
-            }
-            outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
-                    points->at(k + 0), points->at(k + 1));
-            ctrlPointX = reflectiveCtrlPointX;
-            ctrlPointY = reflectiveCtrlPointY;
-            currentX = points->at(k + 0);
-            currentY = points->at(k + 1);
-            break;
-        case 'a': // Draws an elliptical arc
-            // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
-            drawArc(outPath,
-                    currentX,
-                    currentY,
-                    points->at(k + 5) + currentX,
-                    points->at(k + 6) + currentY,
-                    points->at(k + 0),
-                    points->at(k + 1),
-                    points->at(k + 2),
-                    points->at(k + 3) != 0,
-                    points->at(k + 4) != 0);
-            currentX += points->at(k + 5);
-            currentY += points->at(k + 6);
-            ctrlPointX = currentX;
-            ctrlPointY = currentY;
-            break;
-        case 'A': // Draws an elliptical arc
-            drawArc(outPath,
-                    currentX,
-                    currentY,
-                    points->at(k + 5),
-                    points->at(k + 6),
-                    points->at(k + 0),
-                    points->at(k + 1),
-                    points->at(k + 2),
-                    points->at(k + 3) != 0,
-                    points->at(k + 4) != 0);
-            currentX = points->at(k + 5);
-            currentY = points->at(k + 6);
-            ctrlPointX = currentX;
-            ctrlPointY = currentY;
-            break;
-        }
-        previousCmd = cmd;
-    }
-}
 
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/VectorDrawablePath.h b/libs/hwui/VectorDrawablePath.h
index 40ce986..2e56349 100644
--- a/libs/hwui/VectorDrawablePath.h
+++ b/libs/hwui/VectorDrawablePath.h
@@ -17,15 +17,14 @@
 #ifndef ANDROID_HWUI_VPATH_H
 #define ANDROID_HWUI_VPATH_H
 
+#include <cutils/compiler.h>
 #include "SkPath.h"
 #include <vector>
 
 namespace android {
 namespace uirenderer {
 
-
-
-struct PathData {
+struct ANDROID_API PathData {
     // TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance
     // difference.
     std::vector<char> verbs;
@@ -44,9 +43,7 @@
     VectorDrawablePath(const char* path, size_t strLength);
     bool canMorph(const PathData& path);
     bool canMorph(const VectorDrawablePath& path);
-    static void verbsToPath(SkPath* outPath, const PathData* data);
-    static void interpolatePaths(PathData* outPathData, const PathData* from, const PathData* to,
-            float fraction);
+
 private:
     PathData mData;
     SkPath mSkPath;
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/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp
index 171078d..3d9fafa 100644
--- a/libs/hwui/microbench/PathParserBench.cpp
+++ b/libs/hwui/microbench/PathParserBench.cpp
@@ -17,21 +17,35 @@
 #include <benchmark/Benchmark.h>
 
 #include "PathParser.h"
+#include "VectorDrawablePath.h"
 
 #include <SkPath.h>
 
 using namespace android;
 using namespace android::uirenderer;
 
-BENCHMARK_NO_ARG(BM_PathParser_parseStringPath);
-void BM_PathParser_parseStringPath::Run(int iter) {
-    const char* pathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath);
+void BM_PathParser_parseStringPathForSkPath::Run(int iter) {
     SkPath skPath;
-    size_t length = strlen(pathString);
+    size_t length = strlen(sPathString);
     PathParser::ParseResult result;
     StartBenchmarkTiming();
     for (int i = 0; i < iter; i++) {
-        PathParser::parseStringForSkPath(&skPath, &result, pathString, length);
+        PathParser::parseStringForSkPath(&skPath, &result, sPathString, length);
+    }
+    StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData);
+void BM_PathParser_parseStringPathForPathData::Run(int iter) {
+    size_t length = strlen(sPathString);
+    PathData outData;
+    PathParser::ParseResult result;
+    StartBenchmarkTiming();
+    for (int i = 0; i < iter; i++) {
+        PathParser::getPathDataFromString(&outData, &result, sPathString, length);
     }
     StopBenchmarkTiming();
 }
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/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp
similarity index 79%
rename from libs/hwui/unit_tests/PathParserTests.cpp
rename to libs/hwui/unit_tests/VectorDrawableTests.cpp
index c99d7b0..77dd73a 100644
--- a/libs/hwui/unit_tests/PathParserTests.cpp
+++ b/libs/hwui/unit_tests/VectorDrawableTests.cpp
@@ -17,7 +17,8 @@
 #include <gtest/gtest.h>
 
 #include "PathParser.h"
-#include "VectorDrawablePath.h"
+#include "utils/MathUtils.h"
+#include "utils/VectorDrawableUtils.h"
 
 #include <functional>
 
@@ -177,6 +178,10 @@
     {"1-2e34567", false}
 };
 
+static bool hasSameVerbs(const PathData& from, const PathData& to) {
+    return from.verbs == to.verbs && from.verbSizes == to.verbSizes;
+}
+
 TEST(PathParser, parseStringForData) {
     for (TestData testData: sTestDataSet) {
         PathParser::ParseResult result;
@@ -197,12 +202,12 @@
     }
 }
 
-TEST(PathParser, createSkPathFromPathData) {
+TEST(VectorDrawableUtils, createSkPathFromPathData) {
     for (TestData testData: sTestDataSet) {
         SkPath expectedPath;
         testData.skPathLamda(&expectedPath);
         SkPath actualPath;
-        VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData);
+        VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData);
         EXPECT_EQ(expectedPath, actualPath);
     }
 }
@@ -230,5 +235,55 @@
     }
 }
 
+TEST(VectorDrawableUtils, morphPathData) {
+    for (TestData fromData: sTestDataSet) {
+        for (TestData toData: sTestDataSet) {
+            bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
+            if (fromData.pathData == toData.pathData) {
+                EXPECT_TRUE(canMorph);
+            } else {
+                bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+                EXPECT_EQ(expectedToMorph, canMorph);
+            }
+        }
+    }
+}
+
+TEST(VectorDrawableUtils, interpolatePathData) {
+    // Interpolate path data with itself and every other path data
+    for (TestData fromData: sTestDataSet) {
+        for (TestData toData: sTestDataSet) {
+            PathData outData;
+            bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
+                    toData.pathData, 0.5);
+            bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+            EXPECT_EQ(expectedToMorph, success);
+        }
+    }
+
+    float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
+    // Now try to interpolate with a slightly modified version of self and expect success
+    for (TestData fromData : sTestDataSet) {
+        PathData toPathData = fromData.pathData;
+        for (size_t i = 0; i < toPathData.points.size(); i++) {
+            toPathData.points[i]++;
+        }
+        const PathData& fromPathData = fromData.pathData;
+        PathData outData;
+        // Interpolate the two path data with different fractions
+        for (float fraction : fractions) {
+            bool success = VectorDrawableUtils::interpolatePathData(
+                    &outData, fromPathData, toPathData, fraction);
+            EXPECT_TRUE(success);
+            for (size_t i = 0; i < outData.points.size(); i++) {
+                float expectedResult = fromPathData.points[i] * (1.0 - fraction) +
+                        toPathData.points[i] * fraction;
+                EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i]));
+            }
+        }
+    }
+}
+
+
 }; // namespace uirenderer
 }; // namespace android
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/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp
new file mode 100644
index 0000000..ca75c59
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.cpp
@@ -0,0 +1,491 @@
+/*
+ * 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 "VectorDrawableUtils.h"
+
+#include "PathParser.h"
+
+#include <math.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+class PathResolver {
+public:
+    float currentX = 0;
+    float currentY = 0;
+    float ctrlPointX = 0;
+    float ctrlPointY = 0;
+    float currentSegmentStartX = 0;
+    float currentSegmentStartY = 0;
+    void addCommand(SkPath* outPath, char previousCmd,
+            char cmd, const std::vector<float>* points, size_t start, size_t end);
+};
+
+bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) {
+    if (morphFrom.verbs.size() != morphTo.verbs.size()) {
+        return false;
+    }
+
+    for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) {
+        if (morphFrom.verbs[i] != morphTo.verbs[i]
+                ||  morphFrom.verbSizes[i] != morphTo.verbSizes[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom,
+        const PathData& morphTo, float fraction) {
+    if (!canMorph(morphFrom, morphTo)) {
+        return false;
+    }
+    interpolatePaths(outData, morphFrom, morphTo, fraction);
+    return true;
+}
+
+ /**
+ * Convert an array of PathVerb to Path.
+ */
+void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) {
+    PathResolver resolver;
+    char previousCommand = 'm';
+    size_t start = 0;
+    outPath->reset();
+    for (unsigned int i = 0; i < data.verbs.size(); i++) {
+        size_t verbSize = data.verbSizes[i];
+        resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start,
+                start + verbSize);
+        previousCommand = data.verbs[i];
+        start += verbSize;
+    }
+}
+
+/**
+ * The current PathVerb will be interpolated between the
+ * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+ * <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathVerb.
+ * @param nodeTo The end value as a PathVerb
+ * @param fraction The fraction to interpolate.
+ */
+void VectorDrawableUtils::interpolatePaths(PathData* outData,
+        const PathData& from, const PathData& to, float fraction) {
+    outData->points.resize(from.points.size());
+    outData->verbSizes = from.verbSizes;
+    outData->verbs = from.verbs;
+
+    for (size_t i = 0; i < from.points.size(); i++) {
+        outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction;
+    }
+}
+
+/**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+static void arcToBezier(SkPath* p,
+        double cx,
+        double cy,
+        double a,
+        double b,
+        double e1x,
+        double e1y,
+        double theta,
+        double start,
+        double sweep) {
+    // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+    // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+    // Maximum of 45 degrees per cubic Bezier segment
+    int numSegments = ceil(fabs(sweep * 4 / M_PI));
+
+    double eta1 = start;
+    double cosTheta = cos(theta);
+    double sinTheta = sin(theta);
+    double cosEta1 = cos(eta1);
+    double sinEta1 = sin(eta1);
+    double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+    double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+    double anglePerSegment = sweep / numSegments;
+    for (int i = 0; i < numSegments; i++) {
+        double eta2 = eta1 + anglePerSegment;
+        double sinEta2 = sin(eta2);
+        double cosEta2 = cos(eta2);
+        double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+        double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+        double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+        double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+        double tanDiff2 = tan((eta2 - eta1) / 2);
+        double alpha =
+                sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+        double q1x = e1x + alpha * ep1x;
+        double q1y = e1y + alpha * ep1y;
+        double q2x = e2x - alpha * ep2x;
+        double q2y = e2y - alpha * ep2y;
+
+        p->cubicTo((float) q1x,
+                (float) q1y,
+                (float) q2x,
+                (float) q2y,
+                (float) e2x,
+                (float) e2y);
+        eta1 = eta2;
+        e1x = e2x;
+        e1y = e2y;
+        ep1x = ep2x;
+        ep1y = ep2y;
+    }
+}
+
+inline double toRadians(float theta) { return theta * M_PI / 180;}
+
+static void drawArc(SkPath* p,
+        float x0,
+        float y0,
+        float x1,
+        float y1,
+        float a,
+        float b,
+        float theta,
+        bool isMoreThanHalf,
+        bool isPositiveArc) {
+
+    /* Convert rotation angle from degrees to radians */
+    double thetaD = toRadians(theta);
+    /* Pre-compute rotation matrix entries */
+    double cosTheta = cos(thetaD);
+    double sinTheta = sin(thetaD);
+    /* Transform (x0, y0) and (x1, y1) into unit space */
+    /* using (inverse) rotation, followed by (inverse) scale */
+    double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+    double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+    double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+    double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+    /* Compute differences and averages */
+    double dx = x0p - x1p;
+    double dy = y0p - y1p;
+    double xm = (x0p + x1p) / 2;
+    double ym = (y0p + y1p) / 2;
+    /* Solve for intersecting unit circles */
+    double dsq = dx * dx + dy * dy;
+    if (dsq == 0.0) {
+        ALOGW("Points are coincident");
+        return; /* Points are coincident */
+    }
+    double disc = 1.0 / dsq - 1.0 / 4.0;
+    if (disc < 0.0) {
+        ALOGW("Points are too far apart %f", dsq);
+        float adjust = (float) (sqrt(dsq) / 1.99999);
+        drawArc(p, x0, y0, x1, y1, a * adjust,
+                b * adjust, theta, isMoreThanHalf, isPositiveArc);
+        return; /* Points are too far apart */
+    }
+    double s = sqrt(disc);
+    double sdx = s * dx;
+    double sdy = s * dy;
+    double cx;
+    double cy;
+    if (isMoreThanHalf == isPositiveArc) {
+        cx = xm - sdy;
+        cy = ym + sdx;
+    } else {
+        cx = xm + sdy;
+        cy = ym - sdx;
+    }
+
+    double eta0 = atan2((y0p - cy), (x0p - cx));
+
+    double eta1 = atan2((y1p - cy), (x1p - cx));
+
+    double sweep = (eta1 - eta0);
+    if (isPositiveArc != (sweep >= 0)) {
+        if (sweep > 0) {
+            sweep -= 2 * M_PI;
+        } else {
+            sweep += 2 * M_PI;
+        }
+    }
+
+    cx *= a;
+    cy *= b;
+    double tcx = cx;
+    cx = cx * cosTheta - cy * sinTheta;
+    cy = tcx * sinTheta + cy * cosTheta;
+
+    arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+}
+
+
+
+// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
+void PathResolver::addCommand(SkPath* outPath, char previousCmd,
+        char cmd, const std::vector<float>* points, size_t start, size_t end) {
+
+    int incr = 2;
+    float reflectiveCtrlPointX;
+    float reflectiveCtrlPointY;
+
+    switch (cmd) {
+    case 'z':
+    case 'Z':
+        outPath->close();
+        // Path is closed here, but we need to move the pen to the
+        // closed position. So we cache the segment's starting position,
+        // and restore it here.
+        currentX = currentSegmentStartX;
+        currentY = currentSegmentStartY;
+        ctrlPointX = currentSegmentStartX;
+        ctrlPointY = currentSegmentStartY;
+        outPath->moveTo(currentX, currentY);
+        break;
+    case 'm':
+    case 'M':
+    case 'l':
+    case 'L':
+    case 't':
+    case 'T':
+        incr = 2;
+        break;
+    case 'h':
+    case 'H':
+    case 'v':
+    case 'V':
+        incr = 1;
+        break;
+    case 'c':
+    case 'C':
+        incr = 6;
+        break;
+    case 's':
+    case 'S':
+    case 'q':
+    case 'Q':
+        incr = 4;
+        break;
+    case 'a':
+    case 'A':
+        incr = 7;
+        break;
+    }
+
+    for (unsigned int k = start; k < end; k += incr) {
+        switch (cmd) {
+        case 'm': // moveto - Start a new sub-path (relative)
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            if (k > start) {
+                // According to the spec, if a moveto is followed by multiple
+                // pairs of coordinates, the subsequent pairs are treated as
+                // implicit lineto commands.
+                outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+            } else {
+                outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
+                currentSegmentStartX = currentX;
+                currentSegmentStartY = currentY;
+            }
+            break;
+        case 'M': // moveto - Start a new sub-path
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            if (k > start) {
+                // According to the spec, if a moveto is followed by multiple
+                // pairs of coordinates, the subsequent pairs are treated as
+                // implicit lineto commands.
+                outPath->lineTo(points->at(k + 0), points->at(k + 1));
+            } else {
+                outPath->moveTo(points->at(k + 0), points->at(k + 1));
+                currentSegmentStartX = currentX;
+                currentSegmentStartY = currentY;
+            }
+            break;
+        case 'l': // lineto - Draw a line from the current point (relative)
+            outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            break;
+        case 'L': // lineto - Draw a line from the current point
+            outPath->lineTo(points->at(k + 0), points->at(k + 1));
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            break;
+        case 'h': // horizontal lineto - Draws a horizontal line (relative)
+            outPath->rLineTo(points->at(k + 0), 0);
+            currentX += points->at(k + 0);
+            break;
+        case 'H': // horizontal lineto - Draws a horizontal line
+            outPath->lineTo(points->at(k + 0), currentY);
+            currentX = points->at(k + 0);
+            break;
+        case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+            outPath->rLineTo(0, points->at(k + 0));
+            currentY += points->at(k + 0);
+            break;
+        case 'V': // vertical lineto - Draws a vertical line from the current point
+            outPath->lineTo(currentX, points->at(k + 0));
+            currentY = points->at(k + 0);
+            break;
+        case 'c': // curveto - Draws a cubic Bézier curve (relative)
+            outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+                    points->at(k + 4), points->at(k + 5));
+
+            ctrlPointX = currentX + points->at(k + 2);
+            ctrlPointY = currentY + points->at(k + 3);
+            currentX += points->at(k + 4);
+            currentY += points->at(k + 5);
+
+            break;
+        case 'C': // curveto - Draws a cubic Bézier curve
+            outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+                    points->at(k + 4), points->at(k + 5));
+            currentX = points->at(k + 4);
+            currentY = points->at(k + 5);
+            ctrlPointX = points->at(k + 2);
+            ctrlPointY = points->at(k + 3);
+            break;
+        case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+            reflectiveCtrlPointX = 0;
+            reflectiveCtrlPointY = 0;
+            if (previousCmd == 'c' || previousCmd == 's'
+                    || previousCmd == 'C' || previousCmd == 'S') {
+                reflectiveCtrlPointX = currentX - ctrlPointX;
+                reflectiveCtrlPointY = currentY - ctrlPointY;
+            }
+            outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1),
+                    points->at(k + 2), points->at(k + 3));
+            ctrlPointX = currentX + points->at(k + 0);
+            ctrlPointY = currentY + points->at(k + 1);
+            currentX += points->at(k + 2);
+            currentY += points->at(k + 3);
+            break;
+        case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+            reflectiveCtrlPointX = currentX;
+            reflectiveCtrlPointY = currentY;
+            if (previousCmd == 'c' || previousCmd == 's'
+                    || previousCmd == 'C' || previousCmd == 'S') {
+                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+            }
+            outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = points->at(k + 0);
+            ctrlPointY = points->at(k + 1);
+            currentX = points->at(k + 2);
+            currentY = points->at(k + 3);
+            break;
+        case 'q': // Draws a quadratic Bézier (relative)
+            outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = currentX + points->at(k + 0);
+            ctrlPointY = currentY + points->at(k + 1);
+            currentX += points->at(k + 2);
+            currentY += points->at(k + 3);
+            break;
+        case 'Q': // Draws a quadratic Bézier
+            outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+            ctrlPointX = points->at(k + 0);
+            ctrlPointY = points->at(k + 1);
+            currentX = points->at(k + 2);
+            currentY = points->at(k + 3);
+            break;
+        case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+            reflectiveCtrlPointX = 0;
+            reflectiveCtrlPointY = 0;
+            if (previousCmd == 'q' || previousCmd == 't'
+                    || previousCmd == 'Q' || previousCmd == 'T') {
+                reflectiveCtrlPointX = currentX - ctrlPointX;
+                reflectiveCtrlPointY = currentY - ctrlPointY;
+            }
+            outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1));
+            ctrlPointX = currentX + reflectiveCtrlPointX;
+            ctrlPointY = currentY + reflectiveCtrlPointY;
+            currentX += points->at(k + 0);
+            currentY += points->at(k + 1);
+            break;
+        case 'T': // Draws a quadratic Bézier curve (reflective control point)
+            reflectiveCtrlPointX = currentX;
+            reflectiveCtrlPointY = currentY;
+            if (previousCmd == 'q' || previousCmd == 't'
+                    || previousCmd == 'Q' || previousCmd == 'T') {
+                reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+                reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+            }
+            outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+                    points->at(k + 0), points->at(k + 1));
+            ctrlPointX = reflectiveCtrlPointX;
+            ctrlPointY = reflectiveCtrlPointY;
+            currentX = points->at(k + 0);
+            currentY = points->at(k + 1);
+            break;
+        case 'a': // Draws an elliptical arc
+            // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+            drawArc(outPath,
+                    currentX,
+                    currentY,
+                    points->at(k + 5) + currentX,
+                    points->at(k + 6) + currentY,
+                    points->at(k + 0),
+                    points->at(k + 1),
+                    points->at(k + 2),
+                    points->at(k + 3) != 0,
+                    points->at(k + 4) != 0);
+            currentX += points->at(k + 5);
+            currentY += points->at(k + 6);
+            ctrlPointX = currentX;
+            ctrlPointY = currentY;
+            break;
+        case 'A': // Draws an elliptical arc
+            drawArc(outPath,
+                    currentX,
+                    currentY,
+                    points->at(k + 5),
+                    points->at(k + 6),
+                    points->at(k + 0),
+                    points->at(k + 1),
+                    points->at(k + 2),
+                    points->at(k + 3) != 0,
+                    points->at(k + 4) != 0);
+            currentX = points->at(k + 5);
+            currentY = points->at(k + 6);
+            ctrlPointX = currentX;
+            ctrlPointY = currentY;
+            break;
+        default:
+            LOG_ALWAYS_FATAL("Unsupported command: %c", cmd);
+            break;
+        }
+        previousCmd = cmd;
+    }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h
new file mode 100644
index 0000000..21c1cdc
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.h
@@ -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.
+ */
+
+#ifndef ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+
+#include "VectorDrawablePath.h"
+
+#include <cutils/compiler.h>
+#include "SkPath.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class VectorDrawableUtils {
+public:
+    ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo);
+    ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom,
+            const PathData& morphTo, float fraction);
+    ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data);
+    static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to,
+            float fraction);
+};
+} // namespace uirenderer
+} // namespace android
+#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/
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/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0ee970d..91ac033 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -358,18 +358,6 @@
         return mState;
     }
 
-    public static abstract class DocumentsIntent {
-        /** Intent action name to open copy destination. */
-        public static String ACTION_OPEN_COPY_DESTINATION =
-                "com.android.documentsui.OPEN_COPY_DESTINATION";
-
-        /**
-         * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
-         * specifies if the destination directory needs to create new directory or not.
-         */
-        public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
-    }
-
     void setDisplayAdvancedDevices(boolean display) {
         LocalPreferences.setDisplayAdvancedDevices(this, display);
         mState.showAdvanced = mState.forceAdvanced | display;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55a123f..55e2f44 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -179,8 +179,7 @@
             if (mFailedFiles.size() > 0) {
                 Log.e(TAG, mFailedFiles.size() + " files failed to copy");
                 final Context context = getApplicationContext();
-                final Intent navigateIntent = new Intent(context, FilesActivity.class);
-                navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+                final Intent navigateIntent = buildNavigateIntent(context, stack);
                 navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
                 navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
                 navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -228,8 +227,7 @@
         mIsCancelled = false;
 
         final Context context = getApplicationContext();
-        final Intent navigateIntent = new Intent(context, FilesActivity.class);
-        navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+        final Intent navigateIntent = buildNavigateIntent(context, stack);
 
         final String contentTitle = getString(copying ? R.string.copy_notification_title
                 : R.string.move_notification_title);
@@ -592,4 +590,14 @@
             }
         }
     }
+
+    /**
+     * Creates an intent for navigating back to the destination directory.
+     */
+    private Intent buildNavigateIntent(Context context, DocumentStack stack) {
+        Intent intent = new Intent(context, FilesActivity.class);
+        intent.setAction(DocumentsContract.ACTION_BROWSE);
+        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+        return intent;
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 13c481c..e965050 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -19,7 +19,7 @@
 import static com.android.documentsui.State.ACTION_CREATE;
 import static com.android.documentsui.State.ACTION_GET_CONTENT;
 import static com.android.documentsui.State.ACTION_OPEN;
-import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
 import static com.android.documentsui.State.ACTION_OPEN_TREE;
 import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
 
@@ -123,7 +123,7 @@
             final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
             SaveFragment.show(getFragmentManager(), mimeType, title);
         } else if (mState.action == ACTION_OPEN_TREE ||
-                   mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                   mState.action == ACTION_PICK_COPY_DESTINATION) {
             PickFragment.show(getFragmentManager());
         }
 
@@ -135,7 +135,7 @@
         } else if (mState.action == ACTION_OPEN ||
                    mState.action == ACTION_CREATE ||
                    mState.action == ACTION_OPEN_TREE ||
-                   mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                   mState.action == ACTION_PICK_COPY_DESTINATION) {
             RootsFragment.show(getFragmentManager(), null);
         }
 
@@ -163,8 +163,8 @@
             state.action = ACTION_GET_CONTENT;
         } else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
             state.action = ACTION_OPEN_TREE;
-        } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
-            state.action = ACTION_OPEN_COPY_DESTINATION;
+        } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
+            state.action = ACTION_PICK_COPY_DESTINATION;
         }
 
         if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
@@ -172,9 +172,9 @@
                     Intent.EXTRA_ALLOW_MULTIPLE, false);
         }
 
-        if (state.action == ACTION_OPEN_COPY_DESTINATION) {
+        if (state.action == ACTION_PICK_COPY_DESTINATION) {
             state.directoryCopy = intent.getBooleanExtra(
-                    BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
+                    Shared.EXTRA_DIRECTORY_COPY, false);
             state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
                     CopyService.TRANSFER_MODE_COPY);
         }
@@ -257,7 +257,7 @@
                     mState.action == ACTION_OPEN_TREE) {
                     mRootsToolbar.setTitle(R.string.title_open);
                 } else if (mState.action == ACTION_CREATE ||
-                           mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                           mState.action == ACTION_PICK_COPY_DESTINATION) {
                     mRootsToolbar.setTitle(R.string.title_save);
                 }
             }
@@ -324,7 +324,7 @@
         boolean recents = cwd == null;
         boolean picking = mState.action == ACTION_CREATE
                 || mState.action == ACTION_OPEN_TREE
-                || mState.action == ACTION_OPEN_COPY_DESTINATION;
+                || mState.action == ACTION_PICK_COPY_DESTINATION;
 
         createDir.setVisible(picking && !recents && cwd.isCreateSupported());
         mSearchManager.showMenu(!picking);
@@ -361,7 +361,7 @@
             // No directory means recents
             if (mState.action == ACTION_CREATE ||
                 mState.action == ACTION_OPEN_TREE ||
-                mState.action == ACTION_OPEN_COPY_DESTINATION) {
+                mState.action == ACTION_PICK_COPY_DESTINATION) {
                 RecentsCreateFragment.show(fm);
             } else {
                 DirectoryFragment.showRecentsOpen(fm, anim);
@@ -391,7 +391,7 @@
         }
 
         if (mState.action == ACTION_OPEN_TREE ||
-            mState.action == ACTION_OPEN_COPY_DESTINATION) {
+            mState.action == ACTION_PICK_COPY_DESTINATION) {
             final PickFragment pick = PickFragment.get(fm);
             if (pick != null) {
                 pick.setPickTarget(mState.action, mState.transferMode, cwd);
@@ -444,7 +444,7 @@
         if (mState.action == ACTION_OPEN_TREE) {
             result = DocumentsContract.buildTreeDocumentUri(
                     pickTarget.authority, pickTarget.documentId);
-        } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+        } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
             result = pickTarget.derivedUri;
         } else {
             // Should not be reached.
@@ -461,7 +461,7 @@
         final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
         if (mState.action == ACTION_CREATE ||
             mState.action == ACTION_OPEN_TREE ||
-            mState.action == ACTION_OPEN_COPY_DESTINATION) {
+            mState.action == ACTION_PICK_COPY_DESTINATION) {
             // Remember stack for last create
             values.clear();
             values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -500,7 +500,7 @@
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-        } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+        } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
             intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 48e28dc..bbf4682 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -110,7 +110,7 @@
                 mPick.setText(R.string.button_select);
                 mCancel.setVisibility(View.GONE);
                 break;
-            case State.ACTION_OPEN_COPY_DESTINATION:
+            case State.ACTION_PICK_COPY_DESTINATION:
                 mPick.setText(R.string.button_copy);
                 mCancel.setVisibility(View.VISIBLE);
                 break;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 4fc3788..72ee6cbab 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -360,7 +360,7 @@
 
             // Exclude read-only devices when creating
             if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
-            if (state.action == State.ACTION_OPEN_COPY_DESTINATION && !supportsCreate) continue;
+            if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
             // Exclude roots that don't support directory picking
             if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
             // Exclude advanced devices when not requested
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index a4d6dc5..570c9bf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -20,6 +20,16 @@
 
 /** @hide */
 public final class Shared {
+    /** Intent action name to pick a copy destination. */
+    public static final String ACTION_PICK_COPY_DESTINATION =
+            "com.android.documentsui.PICK_COPY_DESTINATION";
+
+    /**
+     * Extra boolean flag for {@link ACTION_PICK_COPY_DESTINATION}, which
+     * specifies if the destination directory needs to create new directory or not.
+     */
+    public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
+
     public static final boolean DEBUG = true;
     public static final String TAG = "Documents";
     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 4306a0e..49a1e66 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -75,7 +75,7 @@
     public static final int ACTION_OPEN_TREE = 4;
     public static final int ACTION_MANAGE = 5;
     public static final int ACTION_BROWSE = 6;
-    public static final int ACTION_OPEN_COPY_DESTINATION = 8;
+    public static final int ACTION_PICK_COPY_DESTINATION = 8;
 
     public static final int MODE_UNKNOWN = 0;
     public static final int MODE_LIST = 1;
@@ -150,4 +150,4 @@
             return new State[size];
         }
     };
-}
\ No newline at end of file
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 21420c8..8b3893f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -111,8 +111,8 @@
 import com.android.documentsui.State;
 import com.android.documentsui.ThumbnailCache;
 import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.BaseActivity.DocumentsIntent;
 import com.android.documentsui.ProviderExecutor.Preemptable;
+import com.android.documentsui.Shared;
 import com.android.documentsui.RecentsProvider.StateColumns;
 import com.android.documentsui.dirlist.MultiSelectManager.Callback;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
@@ -897,7 +897,7 @@
         // Pop up a dialog to pick a destination.  This is inadequate but works for now.
         // TODO: Implement a picker that is to spec.
         final Intent intent = new Intent(
-                BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
+                Shared.ACTION_PICK_COPY_DESTINATION,
                 Uri.EMPTY,
                 getActivity(),
                 DocumentsActivity.class);
@@ -914,7 +914,7 @@
                         break;
                     }
                 }
-                intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
+                intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
                 intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
                 startActivityForResult(intent, REQUEST_COPY_DESTINATION);
             }
@@ -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/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index f0f8161..7c0676f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -31,6 +31,7 @@
 import android.provider.DocumentsProvider;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.FileNotFoundException;
@@ -59,6 +60,7 @@
 
     private MtpManager mMtpManager;
     private ContentResolver mResolver;
+    @GuardedBy("mDeviceToolkits")
     private Map<Integer, DeviceToolkit> mDeviceToolkits;
     private RootScanner mRootScanner;
     private Resources mResources;
@@ -222,9 +224,11 @@
 
     @Override
     public void onTrimMemory(int level) {
-      for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
-          toolkit.mDocumentLoader.clearCompletedTasks();
-      }
+        synchronized (mDeviceToolkits) {
+            for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
+                toolkit.mDocumentLoader.clearCompletedTasks();
+            }
+        }
     }
 
     @Override
@@ -254,21 +258,28 @@
     }
 
     void openDevice(int deviceId) throws IOException {
-        mMtpManager.openDevice(deviceId);
-        mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
-        mRootScanner.scanNow();
+        synchronized (mDeviceToolkits) {
+            mMtpManager.openDevice(deviceId);
+            mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+        }
+        mRootScanner.resume();
     }
 
-    void closeDevice(int deviceId) throws IOException {
+    void closeDevice(int deviceId) throws IOException, InterruptedException {
         // TODO: Flush the device before closing (if not closed externally).
-        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
-        mDeviceToolkits.remove(deviceId);
-        mDatabase.removeDeviceRows(deviceId);
-        mMtpManager.closeDevice(deviceId);
+        synchronized (mDeviceToolkits) {
+            getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+            mDeviceToolkits.remove(deviceId);
+            mDatabase.removeDeviceRows(deviceId);
+            mMtpManager.closeDevice(deviceId);
+        }
         mRootScanner.notifyChange();
+        if (!hasOpenedDevices()) {
+            mRootScanner.pause();
+        }
     }
 
-    void close() throws InterruptedException {
+    synchronized void closeAllDevices() throws InterruptedException {
         boolean closed = false;
         for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
             try {
@@ -282,15 +293,30 @@
         }
         if (closed) {
             mRootScanner.notifyChange();
+            mRootScanner.pause();
         }
-        mRootScanner.close();
-        mDatabase.close();
     }
 
     boolean hasOpenedDevices() {
         return mMtpManager.getOpenedDeviceIds().length != 0;
     }
 
+    /**
+     * Finalize the content provider for unit tests.
+     */
+    @Override
+    public void shutdown() {
+        try {
+            closeAllDevices();
+        } catch (InterruptedException e) {
+            // It should fail unit tests by throwing runtime exception.
+            throw new RuntimeException(e.getMessage());
+        } finally {
+            mDatabase.close();
+            super.shutdown();
+        }
+    }
+
     private void notifyChildDocumentsChange(String parentDocumentId) {
         mResolver.notifyChange(
                 DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
@@ -299,11 +325,13 @@
     }
 
     private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
-        final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
-        if (toolkit == null) {
-            throw new FileNotFoundException();
+        synchronized (mDeviceToolkits) {
+            final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
+            if (toolkit == null) {
+                throw new FileNotFoundException();
+            }
+            return toolkit;
         }
-        return toolkit;
     }
 
     private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 2d1af26..723dc14 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -67,10 +67,10 @@
                 provider.openDevice(device.getDeviceId());
                 return START_STICKY;
             } catch (IOException error) {
-                Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+                Log.e(MtpDocumentsProvider.TAG, error.getMessage());
             }
         } else {
-            Log.d(MtpDocumentsProvider.TAG, "Received unknown intent action.");
+            Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
         }
         stopSelfIfNeeded();
         return Service.START_NOT_STICKY;
@@ -82,7 +82,7 @@
         unregisterReceiver(mReceiver);
         mReceiver = null;
         try {
-            provider.close();
+            provider.closeAllDevices();
         } catch (InterruptedException e) {
             Log.e(MtpDocumentsProvider.TAG, e.getMessage());
         }
@@ -105,8 +105,8 @@
                 final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
                 try {
                     provider.closeDevice(device.getDeviceId());
-                } catch (IOException error) {
-                    Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+                } catch (IOException | InterruptedException error) {
+                    Log.e(MtpDocumentsProvider.TAG, error.getMessage());
                 }
                 stopSelfIfNeeded();
             }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index d9ed4ab..b0962dd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -9,6 +9,10 @@
 import android.util.Log;
 
 import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
 
 final class RootScanner {
     /**
@@ -27,13 +31,18 @@
      */
     private final static long SHORT_POLLING_TIMES = 10;
 
+    /**
+     * Milliseconds we wait for background thread when pausing.
+     */
+    private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
+
     final ContentResolver mResolver;
     final Resources mResources;
     final MtpManager mManager;
     final MtpDatabase mDatabase;
-    boolean mClosed = false;
-    int mPollingCount;
-    Thread mBackgroundThread;
+
+    ExecutorService mExecutor;
+    FutureTask<Void> mCurrentTask;
 
     RootScanner(
             ContentResolver resolver,
@@ -46,6 +55,9 @@
         mDatabase = database;
     }
 
+    /**
+     * Notifies a change of the roots list via ContentResolver.
+     */
     void notifyChange() {
         final Uri uri =
                 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
@@ -56,74 +68,81 @@
      * Starts to check new changes right away.
      * If the background thread has already gone, it restarts another background thread.
      */
-    synchronized void scanNow() {
-        if (mClosed) {
+    synchronized void resume() {
+        if (mExecutor == null) {
+            // Only single thread updates the database.
+            mExecutor = Executors.newSingleThreadExecutor();
+        }
+        if (mCurrentTask != null) {
+            // Cancel previous task.
+            mCurrentTask.cancel(true);
+        }
+        mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+        mExecutor.submit(mCurrentTask);
+    }
+
+    /**
+     * Stops background thread and wait for its termination.
+     * @throws InterruptedException
+     */
+    synchronized void pause() throws InterruptedException {
+        if (mExecutor == null) {
             return;
         }
-        mPollingCount = 0;
-        if (mBackgroundThread == null) {
-            mBackgroundThread = new BackgroundLoaderThread();
-            mBackgroundThread.start();
-        } else {
-            notify();
+        mExecutor.shutdownNow();
+        if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
+            Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread.");
         }
+        mExecutor = null;
     }
 
-    void close() throws InterruptedException {
-        Thread thread;
-        synchronized (this) {
-            mClosed = true;
-            thread = mBackgroundThread;
-            if (mBackgroundThread == null) {
-                return;
-            }
-            notify();
-        }
-        thread.join();
-    }
-
-    private final class BackgroundLoaderThread extends Thread {
+    /**
+     * Runnable to scan roots and update the database information.
+     */
+    private final class UpdateRootsRunnable implements Runnable {
         @Override
         public void run() {
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            synchronized (RootScanner.this) {
-                while (!mClosed) {
-                    final int[] deviceIds = mManager.getOpenedDeviceIds();
-                    if (deviceIds.length == 0) {
-                        break;
-                    }
-                    boolean changed = false;
-                    for (int deviceId : deviceIds) {
+            int pollingCount = 0;
+            while (!Thread.interrupted()) {
+                final int[] deviceIds = mManager.getOpenedDeviceIds();
+                if (deviceIds.length == 0) {
+                    return;
+                }
+                boolean changed = false;
+                for (int deviceId : deviceIds) {
+                    try {
+                        final MtpRoot[] roots = mManager.getRoots(deviceId);
                         mDatabase.startAddingRootDocuments(deviceId);
                         try {
-                            changed = mDatabase.putRootDocuments(
-                                    deviceId, mResources, mManager.getRoots(deviceId)) || changed;
-                        } catch (IOException|SQLiteException exp) {
-                            // The error may happen on the device. We would like to continue getting
-                            // roots for other devices.
-                            Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
-                            continue;
+                            if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+                                changed = true;
+                            }
                         } finally {
-                            changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
+                            if (mDatabase.stopAddingRootDocuments(deviceId)) {
+                                changed = true;
+                            }
                         }
-                    }
-                    if (changed) {
-                        notifyChange();
-                    }
-                    mPollingCount++;
-                    try {
-                        // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
-                        // more likely to add new root just after the device is added.
-                        // TODO: Use short interval only for a device that is just added.
-                        RootScanner.this.wait(
-                                mPollingCount > SHORT_POLLING_TIMES ?
-                                        LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
-                    } catch (InterruptedException exception) {
-                        break;
+                    } catch (IOException | SQLiteException exception) {
+                        // The error may happen on the device. We would like to continue getting
+                        // roots for other devices.
+                        Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
                     }
                 }
-
-                mBackgroundThread = null;
+                if (changed) {
+                    notifyChange();
+                }
+                pollingCount++;
+                try {
+                    // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
+                    // more likely to add new root just after the device is added.
+                    // TODO: Use short interval only for a device that is just added.
+                    Thread.sleep(pollingCount > SHORT_POLLING_TIMES ?
+                        LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
+                } catch (InterruptedException exp) {
+                    // The while condition handles the interrupted flag.
+                    continue;
+                }
             }
         }
     }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 82e08cd..cabb08d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -49,11 +49,7 @@
 
     @Override
     public void tearDown() {
-        try {
-            mProvider.close();
-        } catch (InterruptedException e) {
-            fail();
-        }
+        mProvider.shutdown();
     }
 
     public void testOpenAndCloseDevice() throws Exception {
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index c7cf61a..5a6f1d1 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -61,7 +61,7 @@
 
         <activity
             android:name=".ui.PrintActivity"
-            android:configChanges="orientation|screenSize"
+            android:configChanges="screenSize|smallestScreenSize|orientation"
             android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
             android:theme="@style/PrintActivity">
             <intent-filter>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 3db0848..4469d38 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -33,4 +33,8 @@
 
     <!-- Title for documents backend that offers bugreports. -->
     <string name="bugreport_storage_title">Bug reports</string>
+
+    <!-- Toast message sent when the bugreport file could be read. -->
+    <string name="bugreport_unreadable_text">Bug report file could not be read</string>
+
 </resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index e90a3b5..c264372 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -37,6 +37,7 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Patterns;
+import android.widget.Toast;
 
 import com.google.android.collect.Lists;
 import libcore.io.Streams;
@@ -105,6 +106,13 @@
      */
     private void triggerLocalNotification(final Context context, final File bugreportFile,
             final File screenshotFile) {
+        if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+            Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+            Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+                    Toast.LENGTH_LONG).show();
+            return;
+        }
+
         boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
         if (!isPlainText) {
             // Already zipped, send it right away.
@@ -141,10 +149,12 @@
         intent.putExtra(Intent.EXTRA_TEXT, messageBody);
         final ClipData clipData = new ClipData(null, new String[] { mimeType },
                 new ClipData.Item(null, null, null, bugreportUri));
-        clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+        if (screenshotUri != null) {
+            clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+            attachments.add(screenshotUri);
+        }
         intent.setClipData(clipData);
-
-        final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
 
         final Account sendToAccount = findSendToAccount(context);
@@ -162,8 +172,8 @@
             File screenshotFile) {
         // Files are kept on private storage, so turn into Uris that we can
         // grant temporary permissions for.
-        final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
-        final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+        final Uri bugreportUri = getUri(context, bugreportFile);
+        final Uri screenshotUri = getUri(context, screenshotFile);
 
         Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
         Intent notifIntent;
@@ -272,6 +282,10 @@
         return foundAccount;
     }
 
+    private static Uri getUri(Context context, File file) {
+        return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+    }
+
     private static File getFileExtra(Intent intent, String key) {
         final String path = intent.getStringExtra(key);
         if (path != null) {
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index c36cab8..585f3c7 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -21,10 +21,6 @@
     <PreferenceScreen
         android:title="@string/quick_settings">
 
-        <Preference
-            android:key="qs_tuner"
-            android:title="@string/qs_rearrange" />
-
         <PreferenceCategory
             android:title="@string/experimental">
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 049754e..bb2b8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -77,9 +77,9 @@
     private Record mDetailRecord;
     private Callback mCallback;
     private BrightnessController mBrightnessController;
-    private QSTileHost mHost;
+    protected QSTileHost mHost;
 
-    private QSFooter mFooter;
+    protected QSFooter mFooter;
     private boolean mGridContentVisible = true;
 
     protected LinearLayout mQsContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 5d928d6..7f45545 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -44,8 +44,8 @@
  * state update pass on tile looper.
  */
 public abstract class QSTile<TState extends State> implements Listenable {
-    protected final String TAG = "QSTile." + getClass().getSimpleName();
-    protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
+    protected final String TAG = "Tile." + getClass().getSimpleName();
+    protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
 
     protected final Host mHost;
     protected final Context mContext;
@@ -332,7 +332,7 @@
         Looper getLooper();
         Context getContext();
         Collection<QSTile<?>> getTiles();
-        void setCallback(Callback callback);
+        void addCallback(Callback callback);
         BluetoothController getBluetoothController();
         LocationController getLocationController();
         RotationLockController getRotationLockController();
@@ -453,9 +453,9 @@
     public static class State {
         public boolean visible;
         public Icon icon;
-        public String label;
-        public String contentDescription;
-        public String dualLabelContentDescription;
+        public CharSequence label;
+        public CharSequence contentDescription;
+        public CharSequence dualLabelContentDescription;
         public boolean autoMirrorDrawable = true;
 
         public boolean copyTo(State other) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
new file mode 100644
index 0000000..55f4736
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import android.os.IBinder;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+
+public class QSTileServiceWrapper implements IQSTileService {
+    private static final String TAG = "IQSTileServiceWrapper";
+
+    private final IQSTileService mService;
+    
+    public QSTileServiceWrapper(IQSTileService service) {
+        mService = service;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return mService.asBinder();
+    }
+
+    @Override
+    public void setQSTile(Tile tile) {
+        try {
+            mService.setQSTile(tile);
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onTileAdded() {
+        try {
+            mService.onTileAdded();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onTileRemoved() {
+        try {
+            mService.onTileRemoved();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onStartListening() {
+        try {
+            mService.onStartListening();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onStopListening() {
+        try {
+            mService.onStopListening();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+
+    @Override
+    public void onClick() {
+        try {
+            mService.onClick();
+        } catch (Exception e) {
+            Log.d(TAG, "Caught exception from QSTileService", e);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index cc264a0..f32cfdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -24,22 +25,16 @@
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
 import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
 import android.widget.TextView;
-
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile.AnimationIcon;
-import com.android.systemui.qs.QSTile.State;
 
 import java.util.Objects;
 
@@ -227,6 +222,7 @@
         final ImageView icon = new ImageView(mContext);
         icon.setId(android.R.id.icon);
         icon.setScaleType(ScaleType.CENTER_INSIDE);
+        icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
         return icon;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
new file mode 100644
index 0000000..a4ff685
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class BlankCustomTile extends QSTile<QSTile.State> {
+    public static final String PREFIX = "custom(";
+
+    private final ComponentName mComponent;
+
+    private BlankCustomTile(Host host, String action) {
+        super(host);
+        mComponent = ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(Host host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return new BlankCustomTile(host, action);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleUserSwitch(int newUserId) {
+        super.handleUserSwitch(newUserId);
+    }
+
+    @Override
+    protected void handleClick() {
+        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+    }
+
+    @Override
+    protected void handleLongClick() {
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            state.visible = true;
+            state.icon = new DrawableIcon(info.loadIcon(pm));
+            state.label = info.loadLabel(pm).toString();
+            state.contentDescription = state.label;
+        } catch (Exception e) {
+            state.visible = false;
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_INTENT;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index 8866e55..422ae4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -15,16 +15,34 @@
  */
 package com.android.systemui.qs.customize;
 
+import android.app.ActivityManager;
+import android.app.Service;
 import android.content.ClipData;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.service.quicksettings.IQSTileService;
+import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.qs.tiles.CustomTile;
 import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * A version of QSPanel that allows tiles to be dragged around rather than
@@ -32,8 +50,14 @@
  * and the saving/ordering is handled by the CustomQSTileHost.
  */
 public class CustomQSPanel extends QSPanel {
+    
+    private static final String TAG = "CustomQSPanel";
 
-    private CustomQSTileHost mCustomHost;
+    private List<String> mSavedTiles;
+    private ArrayList<String> mStash;
+    private List<String> mTiles = new ArrayList<>();
+
+    private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
 
     public CustomQSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -41,12 +65,9 @@
                 .inflate(R.layout.qs_customize_layout, mQsContainer, false);
         mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
         ((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
-    }
+        removeView(mFooter.getView());
 
-    @Override
-    public void setHost(QSTileHost host) {
-        super.setHost(host);
-        mCustomHost = (CustomQSTileHost) host;
+        TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
     }
 
     @Override
@@ -55,17 +76,16 @@
             // No Brightness for you.
             super.onTuningChanged(key, "0");
         }
-    }
-
-    public CustomQSTileHost getCustomHost() {
-        return mCustomHost;
+        if (QSTileHost.TILES_SETTING.equals(key)) {
+            mSavedTiles = QSTileHost.loadTileSpecs(mContext, newValue);
+        }
     }
 
     public void tileSelected(QSTile<?> tile, ClipData currentClip) {
         String sourceSpec = getSpec(currentClip);
         String destSpec = tile.getTileSpec();
         if (!sourceSpec.equals(destSpec)) {
-            mCustomHost.moveTo(sourceSpec, destSpec);
+            moveTo(sourceSpec, destSpec);
         }
     }
 
@@ -79,4 +99,124 @@
     public String getSpec(ClipData data) {
         return data.getItemAt(0).getText().toString();
     }
+
+    public void setSavedTiles() {
+        setTiles(mSavedTiles);
+    }
+
+    public void saveCurrentTiles() {
+        for (int i = 0; i < mSavedTiles.size(); i++) {
+            String tileSpec = mSavedTiles.get(i);
+            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+            if (!mTiles.contains(tileSpec)) {
+                mContext.bindServiceAsUser(
+                        new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+                        new ServiceConnection() {
+                            @Override
+                            public void onServiceDisconnected(ComponentName name) {
+                            }
+
+                            @Override
+                            public void onServiceConnected(ComponentName name, IBinder service) {
+                                QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+                                        IQSTileService.Stub.asInterface(service));
+                                wrapper.onStopListening();
+                                wrapper.onTileRemoved();
+                                mContext.unbindService(this);
+                            }
+                        }, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+            }
+        }
+        for (int i = 0; i < mTiles.size(); i++) {
+            String tileSpec = mTiles.get(i);
+            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+            if (!mSavedTiles.contains(tileSpec)) {
+                mContext.bindServiceAsUser(
+                        new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+                        new ServiceConnection() {
+                            @Override
+                            public void onServiceDisconnected(ComponentName name) {
+                            }
+
+                            @Override
+                            public void onServiceConnected(ComponentName name, IBinder service) {
+                                QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+                                        IQSTileService.Stub.asInterface(service));
+                                wrapper.onTileAdded();
+                                mContext.unbindService(this);
+                            }
+                        }, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+            }
+        }
+        Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
+                TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
+    }
+
+    public void stashCurrentTiles() {
+        mStash = new ArrayList<>(mTiles);
+    }
+
+    public void unstashTiles() {
+        setTiles(mStash);
+    }
+
+    @Override
+    public void setTiles(Collection<QSTile<?>> tiles) {
+        setTilesInternal();
+    }
+
+    private void setTilesInternal() {
+        for (int i = 0; i < mCurrentTiles.size(); i++) {
+            mCurrentTiles.get(i).destroy();
+        }
+        mCurrentTiles.clear();
+        for (int i = 0; i < mTiles.size(); i++) {
+            if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
+                mCurrentTiles.add(BlankCustomTile.create(mHost, mTiles.get(i)));
+            } else {
+                mCurrentTiles.add(mHost.createTile(mTiles.get(i)));
+            }
+            mCurrentTiles.get(mCurrentTiles.size() - 1).setTileSpec(mTiles.get(i));
+        }
+        super.setTiles(mCurrentTiles);
+    }
+    
+    public void addTile(String spec) {
+        mTiles.add(spec);
+        setTilesInternal();
+    }
+
+    public void moveTo(String from, String to) {
+        int fromIndex = mTiles.indexOf(from);
+        if (fromIndex < 0) {
+            Log.e(TAG, "Unknown from tile " + from);
+            return;
+        }
+        int index = mTiles.indexOf(to);
+        if (index < 0) {
+            Log.e(TAG, "Unknown to tile " + to);
+            return;
+        }
+        mTiles.remove(fromIndex);
+        mTiles.add(index, from);
+        setTilesInternal();
+    }
+
+    public void remove(String spec) {
+        if (!mTiles.remove(spec)) {
+            Log.e(TAG, "Unknown remove spec " + spec);
+        }
+        setTilesInternal();
+    }
+
+    public void setTiles(List<String> tiles) {
+        mTiles = new ArrayList<>(tiles);
+        setTilesInternal();
+    }
+
+    public Collection<QSTile<?>> getTiles() {
+        return mCurrentTiles;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
deleted file mode 100644
index 3f85982..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @see CustomQSPanel
- */
-public class CustomQSTileHost extends QSTileHost {
-
-    private static final String TAG = "CustomHost";
-    private List<String> mTiles;
-    private List<String> mSavedTiles;
-    private ArrayList<String> mStash;
-
-    public CustomQSTileHost(Context context, QSTileHost host) {
-        super(context, null, host.getBluetoothController(), host.getLocationController(),
-                host.getRotationLockController(), host.getNetworkController(),
-                host.getZenModeController(), host.getHotspotController(), host.getCastController(),
-                host.getFlashlightController(), host.getUserSwitcherController(),
-                host.getUserInfoController(), host.getKeyguardMonitor(),
-                new BlankSecurityController(), host.getBatteryController());
-    }
-
-    @Override
-    public QSTile<?> createTile(String tileSpec) {
-        QSTile<?> tile = super.createTile(tileSpec);
-        tile.setTileSpec(tileSpec);
-        return tile;
-    }
-
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        // No Tunings For You.
-        if (TILES_SETTING.equals(key)) {
-            mSavedTiles = super.loadTileSpecs(newValue);
-        }
-    }
-
-    public void setSavedTiles() {
-        setTiles(mSavedTiles);
-    }
-
-    public void saveCurrentTiles() {
-        Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
-                TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
-    }
-
-    public void stashCurrentTiles() {
-        mStash = new ArrayList<>(mTiles);
-    }
-
-    public void unstashTiles() {
-        setTiles(mStash);
-    }
-
-    public void moveTo(String from, String to) {
-        int fromIndex = mTiles.indexOf(from);
-        if (fromIndex < 0) {
-            Log.e(TAG, "Unknown from tile " + from);
-            return;
-        }
-        int index = mTiles.indexOf(to);
-        if (index < 0) {
-            Log.e(TAG, "Unknown to tile " + to);
-            return;
-        }
-        mTiles.remove(fromIndex);
-        mTiles.add(index, from);
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    public void remove(String spec) {
-        if (!mTiles.remove(spec)) {
-            Log.e(TAG, "Unknown remove spec " + spec);
-        }
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    public void setTiles(List<String> tiles) {
-        mTiles = new ArrayList<>(tiles);
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    @Override
-    protected List<String> loadTileSpecs(String tileList) {
-        return mTiles;
-    }
-
-    public void addTile(String spec) {
-        mTiles.add(spec);
-        super.onTuningChanged(TILES_SETTING, null);
-    }
-
-    public void replace(String oldTile, String newTile) {
-        if (oldTile.equals(newTile)) {
-            return;
-        }
-        MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
-                + newTile);
-        List<String> order = new ArrayList<>(mTileSpecs);
-        int index = order.indexOf(oldTile);
-        if (index < 0) {
-            Log.e(TAG, "Can't find " + oldTile);
-            return;
-        }
-        order.remove(newTile);
-        order.add(index, newTile);
-        setTiles(order);
-    }
-
-    /**
-     * Blank so that the customizing QS view doesn't show any security messages in the footer.
-     */
-    private static class BlankSecurityController implements SecurityController {
-        @Override
-        public boolean hasDeviceOwner() {
-            return false;
-        }
-
-        @Override
-        public boolean hasProfileOwner() {
-            return false;
-        }
-
-        @Override
-        public String getDeviceOwnerName() {
-            return null;
-        }
-
-        @Override
-        public String getProfileOwnerName() {
-            return null;
-        }
-
-        @Override
-        public boolean isVpnEnabled() {
-            return false;
-        }
-
-        @Override
-        public boolean isVpnRestricted() {
-            return false;
-        }
-
-        @Override
-        public String getPrimaryVpnName() {
-            return null;
-        }
-
-        @Override
-        public String getProfileVpnName() {
-            return null;
-        }
-
-        @Override
-        public void onUserSwitched(int newUserId) {
-        }
-
-        @Override
-        public void addCallback(SecurityControllerCallback callback) {
-        }
-
-        @Override
-        public void removeCallback(SecurityControllerCallback callback) {
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 1669278..d0d5b54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -83,8 +83,8 @@
         record.tileView.setVisibility(View.VISIBLE);
         record.tileView.init(null, null, null);
         record.tileView.setOnTouchListener(this);
-        if (mCurrentClip != null
-                && mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) {
+        if (mCurrentClip != null && mCurrentClip.getItemAt(0)
+                .getText().toString().equals(record.tile.getTileSpec())) {
             record.tileView.setAlpha(.3f);
             mCurrentView = record.tileView;
         }
@@ -180,7 +180,7 @@
             case MotionEvent.ACTION_DOWN:
                 // Stash the current tiles, in case the drop is on info, that we can restore
                 // the previous state.
-                mPanel.getCustomHost().stashCurrentTiles();
+                mPanel.stashCurrentTiles();
                 mCurrentView = v;
                 mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
                 View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index b5a885c..baad370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -22,6 +22,7 @@
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnDismissListener;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.DragEvent;
@@ -71,11 +72,11 @@
     private CustomQSPanel mQsPanel;
 
     private boolean isShown;
-    private CustomQSTileHost mHost;
     private DropButton mInfoButton;
     private DropButton mRemoveButton;
     private FloatingActionButton mFab;
     private SystemUIDialog mDialog;
+    private QSTileHost mHost;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -85,11 +86,11 @@
     }
 
     public void setHost(QSTileHost host) {
-        mHost = new CustomQSTileHost(mContext, host);
-        mHost.setCallback(this);
+        mHost = host;
+        mHost.addCallback(this);
         mQsPanel.setTiles(mHost.getTiles());
         mQsPanel.setHost(mHost);
-        mHost.setSavedTiles();
+        mQsPanel.setSavedTiles();
     }
 
     @Override
@@ -129,7 +130,7 @@
 
     public void show(int x, int y) {
         isShown = true;
-        mHost.setSavedTiles();
+        mQsPanel.setSavedTiles();
         mPhoneStatusBar.getStatusBarWindow().addView(this);
         mQsPanel.setListening(true);
         mClipper.animateCircularClip(x, y, true, this);
@@ -150,7 +151,7 @@
         for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) {
             tiles.add(tile);
         }
-        mHost.setTiles(tiles);
+        mQsPanel.setTiles(tiles);
     }
 
     private void setDragging(boolean dragging) {
@@ -158,7 +159,8 @@
     }
 
     private void save() {
-        mHost.saveCurrentTiles();
+        Log.d("CustomQSPanel", "Save!");
+        mQsPanel.saveCurrentTiles();
         // TODO: At save button.
         hide(0, 0);
     }
@@ -167,6 +169,7 @@
     public boolean onMenuItemClick(MenuItem item) {
         switch (item.getItemId()) {
             case MENU_SAVE:
+                Log.d("CustomQSPanel", "Save...");
                 save();
                 break;
             case MENU_RESET:
@@ -179,7 +182,7 @@
     @Override
     public void onTileSelected(String spec) {
         if (mDialog != null) {
-            mHost.addTile(spec);
+            mQsPanel.addTile(spec);
             mDialog.dismiss();
         }
     }
@@ -203,9 +206,9 @@
 
     public void onDrop(View v, ClipData data) {
         if (v == mRemoveButton) {
-            mHost.remove(mQsPanel.getSpec(data));
+            mQsPanel.remove(mQsPanel.getSpec(data));
         } else if (v == mInfoButton) {
-            mHost.unstashTiles();
+            mQsPanel.unstashTiles();
             SystemUIDialog dialog = new SystemUIDialog(mContext);
             dialog.setTitle(mQsPanel.getSpec(data));
             dialog.setPositiveButton(R.string.ok, null);
@@ -220,7 +223,7 @@
                     android.R.style.Theme_Material_Dialog);
             View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
             ListView listView = (ListView) view.findViewById(android.R.id.list);
-            TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+            TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
             adapter.setListener(this);
             listView.setDivider(null);
             listView.setDividerHeight(0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 579f58d..144b202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -27,6 +27,7 @@
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Looper;
+import android.service.quicksettings.TileService;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -82,8 +83,10 @@
                     continue;
                 }
                 if (tileSpecs.contains(spec)) {
+                    Log.d(TAG, "Skipping " + spec);
                     continue;
                 }
+                Log.d(TAG, "Trying " + spec);
                 final QSTile<?> tile = host.createTile(spec);
                 // Bad, bad, very bad.
                 tile.setListening(true);
@@ -156,7 +159,7 @@
             Log.d(TAG, "Added " + mLabel);
         }
 
-        private void addTile(String spec, Drawable icon, String label) {
+        private void addTile(String spec, Drawable icon, CharSequence label) {
             TileInfo info = new TileInfo();
             info.label = label;
             info.drawable = icon;
@@ -164,7 +167,7 @@
             mTiles.add(info);
         }
 
-        private void addTile(String spec, Icon icon, String label, Context context) {
+        private void addTile(String spec, Icon icon, CharSequence label, Context context) {
             addTile(spec, icon.getDrawable(context), label);
         }
 
@@ -208,19 +211,17 @@
     private static class TileInfo {
         private String spec;
         private Drawable drawable;
-        private String label;
+        private CharSequence label;
     }
 
     private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
-        // TODO: Become non-prototype and an API.
-        private static final String TILE_ACTION = "android.intent.action.QS_TILE";
-
         @Override
         protected Collection<TileGroup> doInBackground(Void... params) {
             HashMap<String, TileGroup> pkgMap = new HashMap<>();
             PackageManager pm = mContext.getPackageManager();
             // TODO: Handle userness.
-            List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+            List<ResolveInfo> services = pm.queryIntentServices(
+                    new Intent(TileService.ACTION_QS_TILE), 0);
             for (ResolveInfo info : services) {
                 String packageName = info.serviceInfo.packageName;
                 ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index fd70d02..7f07ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -133,7 +133,7 @@
                     R.string.accessibility_quick_settings_bluetooth_off);
         }
 
-        String bluetoothName = state.label;
+        CharSequence bluetoothName = state.label;
         if (connected) {
             bluetoothName = state.dualLabelContentDescription = mContext.getString(
                     R.string.accessibility_bluetooth_name, state.label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index cf76ed4..b0d885a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -16,36 +16,93 @@
 
 package com.android.systemui.qs.tiles;
 
+import android.app.ActivityManager;
+import android.app.Service;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.statusbar.phone.QSTileHost;
 
 public class CustomTile extends QSTile<QSTile.State> {
     public static final String PREFIX = "custom(";
 
-    private final ComponentName mComponent;
+    // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
+    // So instead we have a period of waiting.
+    private static final long UNBIND_DELAY = 30000;
 
-    private CustomTile(Host host, String action) {
+    private final ComponentName mComponent;
+    private final Tile mTile;
+
+    private QSTileServiceWrapper mService;
+    private boolean mListening;
+    private boolean mBound;
+
+    private CustomTile(QSTileHost host, String action) {
         super(host);
         mComponent = ComponentName.unflattenFromString(action);
+        mTile = new Tile(mComponent, host);
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            mTile.setIcon(android.graphics.drawable.Icon
+                    .createWithResource(mComponent.getPackageName(), info.icon));
+            mTile.setLabel(info.loadLabel(pm));
+        } catch (Exception e) {
+        }
     }
 
-    public static QSTile<?> create(Host host, String spec) {
-        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
-            throw new IllegalArgumentException("Bad intent tile spec: " + spec);
-        }
-        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
-        if (action.isEmpty()) {
-            throw new IllegalArgumentException("Empty intent tile spec action");
-        }
-        return new CustomTile(host, action);
+    public ComponentName getComponent() {
+        return mComponent;
+    }
+
+    public Tile getQsTile() {
+        return mTile;
+    }
+
+    public void updateState(Tile tile) {
+        Log.d("TileService", "Setting state " + tile.getLabel());
+        mTile.setIcon(tile.getIcon());
+        mTile.setLabel(tile.getLabel());
+        mTile.setContentDescription(tile.getContentDescription());
     }
 
     @Override
     public void setListening(boolean listening) {
+        if (mListening == listening) return;
+        mListening = listening;
+        if (listening) {
+            mHandler.removeCallbacks(mUnbind);
+            if (!mBound) {
+                // TODO: Guarantee re-bind on user-switch.
+                mContext.bindServiceAsUser(new Intent().setComponent(mComponent),
+                        mServiceConnection, Service.BIND_AUTO_CREATE,
+                        new UserHandle(ActivityManager.getCurrentUser()));
+                mBound = true;
+            }
+        } else {
+            if (mService!= null) {
+                mService.onStopListening();
+            }
+            mHandler.postDelayed(mUnbind, UNBIND_DELAY);
+        }
+    }
+    
+    @Override
+    protected void handleDestroy() {
+        super.handleDestroy();
+        mHandler.removeCallbacks(mUnbind);
+        mUnbind.run();
     }
 
     @Override
@@ -60,6 +117,11 @@
 
     @Override
     protected void handleClick() {
+        if (mService != null) {
+            mService.onClick();
+        } else {
+            Log.e(TAG, "Click with no service " + getTileSpec());
+        }
         MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
     }
 
@@ -69,16 +131,13 @@
 
     @Override
     protected void handleUpdateState(State state, Object arg) {
-        // TODO: Actual things.
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
-            state.visible = true;
-            state.icon = new DrawableIcon(info.loadIcon(pm));
-            state.label = info.loadLabel(pm).toString();
+        state.visible = true;
+        state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+        state.label = mTile.getLabel();
+        if (mTile.getContentDescription() != null) {
+            state.contentDescription = mTile.getContentDescription();
+        } else {
             state.contentDescription = state.label;
-        } catch (Exception e) {
-            state.visible = false;
         }
     }
 
@@ -86,4 +145,48 @@
     public int getMetricsCategory() {
         return MetricsLogger.QS_INTENT;
     }
+
+    private final ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = new QSTileServiceWrapper(IQSTileService.Stub.asInterface(service));
+            if (mListening) {
+                mService.setQSTile(mTile);
+                mService.onStartListening();
+            } else {
+                mService.onStopListening();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    };
+
+    private final Runnable mUnbind = new Runnable() {
+        @Override
+        public void run() {
+            mContext.unbindService(mServiceConnection);
+            mBound = false;
+        }
+    };
+
+    public static ComponentName getComponentFromSpec(String spec) {
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(QSTileHost host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty custom tile spec action");
+        }
+        return new CustomTile(host, action);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 3763618..7f4442a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -166,7 +166,7 @@
         state.contentDescription = mContext.getString(
                 R.string.accessibility_quick_settings_wifi,
                 signalContentDescription);
-        String wifiName = state.label;
+        CharSequence wifiName = state.label;
         if (state.connected) {
             wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index fe67fd9..965e7a67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -239,7 +239,8 @@
             SystemServicesProxy ssp, Resources res) {
         Bitmap tdIcon = iconBitmap != null
                 ? iconBitmap
-                : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename);
+                : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename,
+                        taskKey.userId);
         if (tdIcon != null) {
             return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId);
         }
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 4ecb80a..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;
@@ -31,7 +33,6 @@
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.WindowManagerGlobal;
 import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsDebugFlags;
@@ -48,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;
@@ -93,7 +95,7 @@
             final boolean lockToTask, final Rect bounds, int destinationStack) {
         final ActivityOptions opts = ActivityOptions.makeBasic();
         if (bounds != null) {
-            opts.setBounds(bounds.isEmpty() ? null : bounds);
+            opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
         }
 
         final ActivityOptions.OnAnimationStartedListener animStartedListener;
@@ -244,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/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d3d9bef..f8ab35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2161,13 +2161,10 @@
         boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
         boolean isFullscreen = notification.fullScreenIntent != null;
         boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
-        boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
-                Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
         boolean accessibilityForcesLaunch = isFullscreen
                 && mAccessibilityManager.isTouchExplorationEnabled();
         boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
         boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
-                && isAllowed
                 && !accessibilityForcesLaunch
                 && !justLaunchedFullScreenIntent
                 && mPowerManager.isScreenOn()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 0cddf1d..51bcf0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -927,7 +927,7 @@
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
             mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
             mHeader.setQSPanel(mQSPanel);
-            qsh.setCallback(new QSTileHost.Callback() {
+            qsh.addCallback(new QSTileHost.Callback() {
                 @Override
                 public void onTilesChanged() {
                     mQSPanel.setTiles(qsh.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 96b919e..57c2648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,18 +17,56 @@
 package com.android.systemui.statusbar.phone;
 
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
+import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
+import android.os.RemoteException;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.Tile;
 import android.util.Log;
 
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.tiles.*;
-import com.android.systemui.statusbar.policy.*;
+import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.BatteryTile;
+import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.qs.tiles.CastTile;
+import com.android.systemui.qs.tiles.CellularTile;
+import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.qs.tiles.FlashlightTile;
+import com.android.systemui.qs.tiles.HotspotTile;
+import com.android.systemui.qs.tiles.IntentTile;
+import com.android.systemui.qs.tiles.LocationTile;
+import com.android.systemui.qs.tiles.QAirplaneTile;
+import com.android.systemui.qs.tiles.QBluetoothTile;
+import com.android.systemui.qs.tiles.QFlashlightTile;
+import com.android.systemui.qs.tiles.QLockTile;
+import com.android.systemui.qs.tiles.QRotationLockTile;
+import com.android.systemui.qs.tiles.QWifiTile;
+import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.UserTile;
+import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
 
@@ -40,7 +78,7 @@
 import java.util.Map;
 
 /** Platform implementation of the quick settings tile host **/
-public class QSTileHost implements QSTile.Host, Tunable {
+public final class QSTileHost extends IQSService.Stub implements QSTile.Host, Tunable {
     private static final String TAG = "QSTileHost";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -65,7 +103,7 @@
     private final SecurityController mSecurity;
     private final BatteryController mBattery;
 
-    private Callback mCallback;
+    private final List<Callback> mCallbacks = new ArrayList<>();
 
     public QSTileHost(Context context, PhoneStatusBar statusBar,
             BluetoothController bluetooth, LocationController location,
@@ -107,8 +145,8 @@
     }
 
     @Override
-    public void setCallback(Callback callback) {
-        mCallback = callback;
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
     }
 
     @Override
@@ -209,14 +247,14 @@
     public SecurityController getSecurityController() {
         return mSecurity;
     }
-    
+
     @Override
     public void onTuningChanged(String key, String newValue) {
         if (!TILES_SETTING.equals(key)) {
             return;
         }
         if (DEBUG) Log.d(TAG, "Recreating tiles");
-        final List<String> tileSpecs = loadTileSpecs(newValue);
+        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
         if (tileSpecs.equals(mTileSpecs)) return;
         for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
             if (!tileSpecs.contains(tile.getKey())) {
@@ -227,11 +265,15 @@
         final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
         for (String tileSpec : tileSpecs) {
             if (mTiles.containsKey(tileSpec)) {
-                newTiles.put(tileSpec, mTiles.get(tileSpec));
+                QSTile<?> tile = mTiles.get(tileSpec);
+                if (DEBUG) Log.d(TAG, "Adding " + tile);
+                newTiles.put(tileSpec, tile);
             } else {
                 if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                 try {
-                    newTiles.put(tileSpec, createTile(tileSpec));
+                    QSTile<?> tile = createTile(tileSpec);
+                    tile.setTileSpec(tileSpec);
+                    newTiles.put(tileSpec, tile);
                 } catch (Throwable t) {
                     Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
                 }
@@ -241,11 +283,46 @@
         mTileSpecs.addAll(tileSpecs);
         mTiles.clear();
         mTiles.putAll(newTiles);
-        if (mCallback != null) {
-            mCallback.onTilesChanged();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onTilesChanged();
         }
     }
 
+    @Override
+    public void updateQsTile(Tile tile) throws RemoteException {
+        verifyCaller(tile.getComponentName().getPackageName());
+        CustomTile customTile = getTileForComponent(tile.getComponentName());
+        if (customTile != null) {
+            Log.d("TileService", "Got tile update for " + tile.getComponentName());
+            customTile.updateState(tile);
+            customTile.refreshState();
+        }
+    }
+
+    private void verifyCaller(String packageName) {
+        try {
+            int uid = mContext.getPackageManager().getPackageUid(packageName,
+                    Binder.getCallingUserHandle().getIdentifier());
+            if (Binder.getCallingUid() != uid) {
+                throw new SecurityException("Component outside caller's uid");
+            }
+        } catch (NameNotFoundException e) {
+            throw new SecurityException(e);
+        }
+    }
+
+    private CustomTile getTileForComponent(ComponentName component) {
+        // TODO: Build map for easier lookup.
+        for (QSTile<?> qsTile : mTiles.values()) {
+            if (qsTile instanceof CustomTile) {
+                if (((CustomTile) qsTile).getComponent().equals(component)) {
+                    return (CustomTile) qsTile;
+                }
+            }
+        }
+        return null;
+    }
+
     public QSTile<?> createTile(String tileSpec) {
         if (tileSpec.equals("wifi")) return new WifiTile(this, false);
         else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
@@ -276,8 +353,8 @@
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
     }
 
-    protected List<String> loadTileSpecs(String tileList) {
-        final Resources res = mContext.getResources();
+    public static List<String> loadTileSpecs(Context context, String tileList) {
+        final Resources res = context.getResources();
         final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
         if (tileList == null) {
             tileList = res.getString(R.string.quick_settings_tiles);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 662dbd9..cc9f5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -192,7 +192,7 @@
                 host.getBatteryController());
         mHeaderQsPanel.setHost(myHost);
         mHeaderQsPanel.setTiles(myHost.getTiles());
-        myHost.setCallback(new QSTile.Host.Callback() {
+        myHost.addCallback(new QSTile.Host.Callback() {
             @Override
             public void onTilesChanged() {
                 mHeaderQsPanel.setTiles(myHost.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
deleted file mode 100644
index 05e3fd5..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.tuner;
-
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnDragListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ScrollView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.QSTile.ResourceIcon;
-import com.android.systemui.qs.QSTileBaseView;
-import com.android.systemui.qs.QSTileView;
-import com.android.systemui.qs.tiles.IntentTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class QsTuner extends Fragment implements Callback {
-
-    private static final String TAG = "QsTuner";
-
-    private static final int MENU_RESET = Menu.FIRST;
-
-    private DraggableQsPanel mQsPanel;
-    private CustomHost mTileHost;
-
-    private FrameLayout mDropTarget;
-
-    private ScrollView mScrollRoot;
-
-    private FrameLayout mAddTarget;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
-    }
-
-    public void onResume() {
-        super.onResume();
-        MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, true);
-    }
-
-    public void onPause() {
-        super.onPause();
-        MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, false);
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_RESET:
-                mTileHost.reset();
-                break;
-            case android.R.id.home:
-                getFragmentManager().popBackStack();
-                break;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
-
-        mQsPanel = new DraggableQsPanel(getContext());
-        mTileHost = new CustomHost(getContext());
-        mTileHost.setCallback(this);
-        mQsPanel.setTiles(mTileHost.getTiles());
-        mQsPanel.setHost(mTileHost);
-        mQsPanel.refreshAllTiles();
-        ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
-
-        mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
-        setupDropTarget();
-        mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
-        setupAddTarget();
-        return mScrollRoot;
-    }
-
-    @Override
-    public void onDestroyView() {
-        mTileHost.destroy();
-        super.onDestroyView();
-    }
-
-    private void setupDropTarget() {
-        QSTileView tileView = new QSTileView(getContext());
-        QSTile.State state = new QSTile.State();
-        state.visible = true;
-        state.icon = ResourceIcon.get(R.drawable.ic_delete);
-        state.label = getString(com.android.internal.R.string.delete);
-        tileView.onStateChanged(state);
-        mDropTarget.addView(tileView);
-        mDropTarget.setVisibility(View.GONE);
-        new DragHelper(tileView, new DropListener() {
-            @Override
-            public void onDrop(String sourceText) {
-                mTileHost.remove(sourceText);
-            }
-        });
-    }
-
-    private void setupAddTarget() {
-        QSTileView tileView = new QSTileView(getContext());
-        QSTile.State state = new QSTile.State();
-        state.visible = true;
-        state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
-        state.label = getString(R.string.add_tile);
-        tileView.onStateChanged(state);
-        mAddTarget.addView(tileView);
-        tileView.setClickable(true);
-        tileView.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mTileHost.showAddDialog();
-            }
-        });
-    }
-
-    public void onStartDrag() {
-        mDropTarget.post(new Runnable() {
-            @Override
-            public void run() {
-                mDropTarget.setVisibility(View.VISIBLE);
-                mAddTarget.setVisibility(View.GONE);
-            }
-        });
-    }
-
-    public void stopDrag() {
-        mDropTarget.post(new Runnable() {
-            @Override
-            public void run() {
-                mDropTarget.setVisibility(View.GONE);
-                mAddTarget.setVisibility(View.VISIBLE);
-            }
-        });
-    }
-
-    @Override
-    public void onTilesChanged() {
-        mQsPanel.setTiles(mTileHost.getTiles());
-    }
-
-    private static int getLabelResource(String spec) {
-        if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
-        else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
-        else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
-        else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
-        else if (spec.equals("airplane")) return R.string.airplane_mode;
-        else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
-        else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
-        else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
-        else if (spec.equals("location")) return R.string.quick_settings_location_label;
-        else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
-        else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
-        return 0;
-    }
-
-    private static class CustomHost extends QSTileHost {
-
-        public CustomHost(Context context) {
-            super(context, null, null, null, null, null, null, null, null, null, null,
-                    null, null, new BlankSecurityController(), null);
-        }
-
-        @Override
-        public QSTile<?> createTile(String tileSpec) {
-            return new DraggableTile(this, tileSpec);
-        }
-
-        public void replace(String oldTile, String newTile) {
-            if (oldTile.equals(newTile)) {
-                return;
-            }
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
-                    + newTile);
-            List<String> order = new ArrayList<>(mTileSpecs);
-            int index = order.indexOf(oldTile);
-            if (index < 0) {
-                Log.e(TAG, "Can't find " + oldTile);
-                return;
-            }
-            order.remove(newTile);
-            order.add(index, newTile);
-            setTiles(order);
-        }
-
-        public void remove(String tile) {
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
-            List<String> tiles = new ArrayList<>(mTileSpecs);
-            tiles.remove(tile);
-            setTiles(tiles);
-        }
-
-        public void add(String tile) {
-            MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
-            List<String> tiles = new ArrayList<>(mTileSpecs);
-            tiles.add(tile);
-            setTiles(tiles);
-        }
-
-        public void reset() {
-            Secure.putStringForUser(getContext().getContentResolver(),
-                    TILES_SETTING, "default", ActivityManager.getCurrentUser());
-        }
-
-        private void setTiles(List<String> tiles) {
-            Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
-                    TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
-        }
-
-        public void showAddDialog() {
-            List<String> tiles = mTileSpecs;
-            int numBroadcast = 0;
-            for (int i = 0; i < tiles.size(); i++) {
-                if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
-                    numBroadcast++;
-                }
-            }
-            String[] defaults =
-                getContext().getString(R.string.quick_settings_tiles_default).split(",");
-            final String[] available = new String[defaults.length + 1
-                                                  - (tiles.size() - numBroadcast)];
-            final String[] availableTiles = new String[available.length];
-            int index = 0;
-            for (int i = 0; i < defaults.length; i++) {
-                if (tiles.contains(defaults[i])) {
-                    continue;
-                }
-                int resource = getLabelResource(defaults[i]);
-                if (resource != 0) {
-                    availableTiles[index] = defaults[i];
-                    available[index++] = getContext().getString(resource);
-                } else {
-                    availableTiles[index] = defaults[i];
-                    available[index++] = defaults[i];
-                }
-            }
-            available[index++] = getContext().getString(R.string.broadcast_tile);
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.add_tile)
-                    .setItems(available, new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            if (which < available.length - 1) {
-                                add(availableTiles[which]);
-                            } else {
-                                showBroadcastTileDialog();
-                            }
-                        }
-                    }).show();
-        }
-
-        public void showBroadcastTileDialog() {
-            final EditText editText = new EditText(getContext());
-            new AlertDialog.Builder(getContext())
-                    .setTitle(R.string.broadcast_tile)
-                    .setView(editText)
-                    .setNegativeButton(android.R.string.cancel, null)
-                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-                        public void onClick(DialogInterface dialog, int which) {
-                            String action = editText.getText().toString();
-                            if (isValid(action)) {
-                                add(IntentTile.PREFIX + action + ')');
-                            }
-                        }
-                    }).show();
-        }
-
-        private boolean isValid(String action) {
-            for (int i = 0; i < action.length(); i++) {
-                char c = action.charAt(i);
-                if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        private static class BlankSecurityController implements SecurityController {
-            @Override
-            public boolean hasDeviceOwner() {
-                return false;
-            }
-
-            @Override
-            public boolean hasProfileOwner() {
-                return false;
-            }
-
-            @Override
-            public String getDeviceOwnerName() {
-                return null;
-            }
-
-            @Override
-            public String getProfileOwnerName() {
-                return null;
-            }
-
-            @Override
-            public boolean isVpnEnabled() {
-                return false;
-            }
-
-            @Override
-            public boolean isVpnRestricted() {
-                return false;
-            }
-
-            @Override
-            public String getPrimaryVpnName() {
-                return null;
-            }
-
-            @Override
-            public String getProfileVpnName() {
-                return null;
-            }
-
-            @Override
-            public void onUserSwitched(int newUserId) {
-            }
-
-            @Override
-            public void addCallback(SecurityControllerCallback callback) {
-            }
-
-            @Override
-            public void removeCallback(SecurityControllerCallback callback) {
-            }
-        }
-    }
-
-    private static class DraggableTile extends QSTile<QSTile.State>
-            implements DropListener {
-        private String mSpec;
-        private QSTileBaseView mView;
-
-        protected DraggableTile(QSTile.Host host, String tileSpec) {
-            super(host);
-            Log.d(TAG, "Creating tile " + tileSpec);
-            mSpec = tileSpec;
-        }
-
-        @Override
-        public QSTileBaseView createTileView(Context context) {
-            mView = super.createTileView(context);
-            return mView;
-        }
-        
-        @Override
-        public int getTileType() {
-            return "wifi".equals(mSpec) || "bt".equals(mSpec) ? QSTileView.QS_TYPE_DUAL
-                    : QSTileView.QS_TYPE_NORMAL;
-        }
-
-        @Override
-        public void setListening(boolean listening) {
-        }
-
-        @Override
-        protected QSTile.State newTileState() {
-            return new QSTile.State();
-        }
-
-        @Override
-        protected void handleClick() {
-        }
-
-        @Override
-        protected void handleUpdateState(QSTile.State state, Object arg) {
-            state.visible = true;
-            state.icon = ResourceIcon.get(getIcon());
-            state.label = getLabel();
-        }
-
-        private String getLabel() {
-            int resource = getLabelResource(mSpec);
-            if (resource != 0) {
-                return mContext.getString(resource);
-            }
-            if (mSpec.startsWith(IntentTile.PREFIX)) {
-                int lastDot = mSpec.lastIndexOf('.');
-                if (lastDot >= 0) {
-                    return mSpec.substring(lastDot + 1, mSpec.length() - 1);
-                } else {
-                    return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1);
-                }
-            }
-            return mSpec;
-        }
-
-        private int getIcon() {
-            if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3;
-            else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected;
-            else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable;
-            else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3;
-            else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
-            else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on;
-            else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
-            else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
-            else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
-            else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
-            else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
-            return R.drawable.android;
-        }
-
-        @Override
-        public int getMetricsCategory() {
-            return 20000;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof DraggableTile) {
-                return mSpec.equals(((DraggableTile) o).mSpec);
-            }
-            return false;
-        }
-
-        @Override
-        public void onDrop(String sourceText) {
-            ((CustomHost) mHost).replace(mSpec, sourceText);
-        }
-
-    }
-
-    private class DragHelper implements OnDragListener {
-
-        private final View mView;
-        private final DropListener mListener;
-
-        public DragHelper(View view, DropListener dropListener) {
-            mView = view;
-            mListener = dropListener;
-            mView.setOnDragListener(this);
-        }
-
-        @Override
-        public boolean onDrag(View v, DragEvent event) {
-            switch (event.getAction()) {
-                case DragEvent.ACTION_DRAG_ENTERED:
-                    mView.setBackgroundColor(0x77ffffff);
-                    break;
-                case DragEvent.ACTION_DRAG_ENDED:
-                    stopDrag();
-                case DragEvent.ACTION_DRAG_EXITED:
-                    mView.setBackgroundColor(0x0);
-                    break;
-                case DragEvent.ACTION_DROP:
-                    stopDrag();
-                    String text = event.getClipData().getItemAt(0).getText().toString();
-                    mListener.onDrop(text);
-                    break;
-            }
-            return true;
-        }
-
-    }
-
-    public interface DropListener {
-        void onDrop(String sourceText);
-    }
-
-    private class DraggableQsPanel extends QSPanel implements OnTouchListener {
-        public DraggableQsPanel(Context context) {
-            super(context);
-            mBrightnessView.setVisibility(View.GONE);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            for (TileRecord r : mRecords) {
-                new DragHelper(r.tileView, (DraggableTile) r.tile);
-                r.tileView.setTag(r.tile);
-                r.tileView.setOnTouchListener(this);
-
-                for (int i = 0; i < r.tileView.getChildCount(); i++) {
-                    r.tileView.getChildAt(i).setClickable(false);
-                }
-            }
-        }
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                    String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
-                    ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
-                    v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
-                    onStartDrag();
-                    return true;
-            }
-            return false;
-        }
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index dc7c967..b620b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -59,8 +59,6 @@
 
     private SwitchPreference mBatteryPct;
 
-    private Preference mQsTuner;
-
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -68,17 +66,6 @@
         getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
         setHasOptionsMenu(true);
 
-        mQsTuner = findPreference(KEY_QS_TUNER);
-        mQsTuner.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                FragmentTransaction ft = getFragmentManager().beginTransaction();
-                ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
-                ft.addToBackStack(null);
-                ft.commit();
-                return true;
-            }
-        });
         findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
             @Override
             public boolean onPreferenceClick(Preference preference) {
@@ -96,13 +83,6 @@
                 new TunerWarningFragment().show(getFragmentManager(), WARNING_TAG);
             }
         }
-        TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_THE_NEW_QS);
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        TunerService.get(getContext()).removeTunable(mQsPaging);
     }
 
     @Override
@@ -175,14 +155,6 @@
         }
     };
 
-    private final Tunable mQsPaging = new Tunable() {
-        @Override
-        public void onTuningChanged(String key, String newValue) {
-            // Only enable QS rearranging when paging is off, because its very broken.
-            mQsTuner.setEnabled(newValue == null || Integer.parseInt(newValue) == 0);
-        }
-    };
-
     public static class TunerWarningFragment extends DialogFragment {
         @Override
         public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/preloaded-classes b/preloaded-classes
index d6b4ec9..e48255c 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1566,6 +1566,7 @@
 android.security.keystore.AndroidKeyStoreKey
 android.security.keystore.AndroidKeyStoreProvider
 android.security.keystore.KeyStoreCryptoOperation
+android.security.net.config.NetworkSecurityConfigProvider
 android.service.persistentdata.PersistentDataBlockManager
 android.system.ErrnoException
 android.system.GaiException
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/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index ae9ddc0..5e4f2b2 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -309,11 +309,18 @@
     ClientState mCurClient;
 
     /**
-     * The last window token that gained focus.
+     * The last window token that we confirmed to be focused.  This is always updated upon reports
+     * from the input method client.  If the window state is already changed before the report is
+     * handled, this field just keeps the last value.
      */
     IBinder mCurFocusedWindow;
 
     /**
+     * The client by which {@link #mCurFocusedWindow} was reported.  Used only for debugging.
+     */
+    ClientState mCurFocusedWindowClient;
+
+    /**
      * The input context last provided by the current client.
      */
     IInputContext mCurInputContext;
@@ -1196,6 +1203,12 @@
             ClientState cs = mClients.remove(client.asBinder());
             if (cs != null) {
                 clearClientSessionLocked(cs);
+                if (mCurClient == cs) {
+                    mCurClient = null;
+                }
+                if (mCurFocusedWindowClient == cs) {
+                    mCurFocusedWindowClient = null;
+                }
             }
         }
     }
@@ -2191,6 +2204,7 @@
                     return null;
                 }
                 mCurFocusedWindow = windowToken;
+                mCurFocusedWindowClient = cs;
 
                 // Should we auto-show the IME even if the caller has not
                 // specified what should be done with it?
@@ -3702,6 +3716,7 @@
 
         IInputMethod method;
         ClientState client;
+        ClientState focusedWindowClient;
 
         final Printer p = new PrintWriterPrinter(pw);
 
@@ -3726,6 +3741,8 @@
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
+            focusedWindowClient = mCurFocusedWindowClient;
+            p.println("  mCurFocusedWindowClient=" + focusedWindowClient);
             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
                     + " mBoundToMethod=" + mBoundToMethod);
             p.println("  mCurToken=" + mCurToken);
@@ -3757,6 +3774,20 @@
             p.println("No input method client.");
         }
 
+        if (focusedWindowClient != null && client != focusedWindowClient) {
+            p.println(" ");
+            p.println("Warning: Current input method client doesn't match the last focused. "
+                    + "window.");
+            p.println("Dumping input method client in the last focused window just in case.");
+            p.println(" ");
+            pw.flush();
+            try {
+                focusedWindowClient.client.asBinder().dump(fd, args);
+            } catch (RemoteException e) {
+                p.println("Input method client in focused window dead: " + e);
+            }
+        }
+
         p.println(" ");
         if (method != null) {
             pw.flush();
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 9ac4ba3..37d47c3 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -88,6 +88,7 @@
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
@@ -114,6 +115,7 @@
 public class AccountManagerService
         extends IAccountManager.Stub
         implements RegisteredServicesCacheListener<AuthenticatorDescription> {
+
     private static final String TAG = "AccountManagerService";
 
     private static final String DATABASE_NAME = "accounts.db";
@@ -2283,6 +2285,193 @@
         }
     }
 
+    @Override
+    public void startAddAccountSession(final IAccountManagerResponse response, final String accountType,
+            final String authTokenType, final String[] requiredFeatures,
+            final boolean expectActivityLaunch,
+            final Bundle optionsIn) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG,
+                    "startAddAccountSession: accountType " + accountType
+                    + ", response " + response
+                    + ", authTokenType " + authTokenType
+                    + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) {
+            throw new IllegalArgumentException("response is null");
+        }
+        if (accountType == null) {
+            throw new IllegalArgumentException("accountType is null");
+        }
+
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!canUserModifyAccounts(userId)) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+                        "User is not allowed to add an account!");
+            } catch (RemoteException re) {
+            }
+            showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId);
+            return;
+        }
+        if (!canUserModifyAccountsForType(userId, accountType)) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                        "User cannot modify accounts of this type (policy).");
+            } catch (RemoteException re) {
+            }
+            showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+                    userId);
+            return;
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+        options.putInt(AccountManager.KEY_CALLER_UID, uid);
+        options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+        int usrId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            UserAccounts accounts = getUserAccounts(usrId);
+            logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD,
+                    TABLE_ACCOUNTS, uid);
+            new StartAccountSession(accounts, response, accountType, expectActivityLaunch,
+                    null /* accountName */, false /* authDetailsRequired */,
+                    true /* updateLastAuthenticationTime */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType,
+                            requiredFeatures, options);
+                }
+
+                @Override
+                protected String toDebugString(long now) {
+                    String requiredFeaturesStr = TextUtils.join(",", requiredFeatures);
+                    return super.toDebugString(now) + ", startAddAccountSession" + ", accountType "
+                            + accountType + ", requiredFeatures "
+                            + (requiredFeatures != null ? requiredFeaturesStr : null);
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */
+    private abstract class StartAccountSession extends Session {
+
+        public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+                String accountType, boolean expectActivityLaunch, String accountName,
+                boolean authDetailsRequired, boolean updateLastAuthenticationTime) {
+            super(accounts, response, accountType, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */, accountName, authDetailsRequired,
+                    updateLastAuthenticationTime);
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            mNumResults++;
+            Intent intent = null;
+
+            if (result != null
+                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+                /*
+                 * The Authenticator API allows third party authenticators to
+                 * supply arbitrary intents to other apps that they can run,
+                 * this can be very bad when those apps are in the system like
+                 * the System Settings.
+                 */
+                int authenticatorUid = Binder.getCallingUid();
+                long bid = Binder.clearCallingIdentity();
+                try {
+                    PackageManager pm = mContext.getPackageManager();
+                    ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                    int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+                    if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid,
+                            targetUid)) {
+                        throw new SecurityException("Activity to be started with KEY_INTENT must "
+                                + "share Authenticator's signatures");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(bid);
+                }
+            }
+
+            IAccountManagerResponse response;
+            if (mExpectActivityLaunch && result != null
+                    && result.containsKey(AccountManager.KEY_INTENT)) {
+                response = mResponse;
+            } else {
+                response = getResponseAndClose();
+            }
+            if (response == null) {
+                return;
+            }
+            if (result == null) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, getClass().getSimpleName() + " calling onError() on response "
+                            + response);
+                }
+                sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                        "null bundle returned");
+                return;
+            }
+
+            if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && (intent == null)) {
+                // All AccountManager error codes are greater
+                // than 0
+                sendErrorResponse(response, result.getInt(AccountManager.KEY_ERROR_CODE),
+                        result.getString(AccountManager.KEY_ERROR_MESSAGE));
+                return;
+            }
+
+            // Strip auth token from result.
+            result.remove(AccountManager.KEY_AUTHTOKEN);
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG,
+                        getClass().getSimpleName() + " calling onResult() on response " + response);
+            }
+
+            // Get the session bundle created by authenticator. The
+            // bundle contains data necessary for finishing the session
+            // later. The session bundle will be encrypted here and
+            // decrypted later when trying to finish the session.
+            Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+            if (sessionBundle != null) {
+                String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                if (TextUtils.isEmpty(accountType)
+                        && !mAccountType.equalsIgnoreCase(mAccountType)) {
+                    Log.w(TAG, "Account type in session bundle doesn't match request.");
+                }
+                // Add accountType info to session bundle. This will
+                // override any value set by authenticator.
+                sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+
+                // Encrypt session bundle before returning to caller.
+                try {
+                    CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+                    Bundle encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
+                    result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, encryptedBundle);
+                } catch (GeneralSecurityException e) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.v(TAG, "Failed to encrypt session bundle!", e);
+                    }
+                    sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "failed to encrypt session bundle");
+                    return;
+                }
+            }
+
+            sendResponse(response, result);
+        }
+    }
+
     private void showCantAddAccount(int errorCode, int userId) {
         Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
         cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode);
@@ -3336,6 +3525,11 @@
         private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
         private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
 
+        // TODO: This action doesn't add account to accountdb. Account is only
+        // added in finishAddAccount or finishAddAccountAsUser which may be in
+        // a different user profile.
+        private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
+
         private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 
         private static void createDebugTable(SQLiteDatabase db) {
@@ -4300,4 +4494,29 @@
             return mContext;
         }
     }
+
+    private void sendResponse(IAccountManagerResponse response, Bundle result) {
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
+
+    private void sendErrorResponse(IAccountManagerResponse response, int errorCode,
+            String errorMessage) {
+        try {
+            response.onError(errorCode, errorMessage);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/accounts/CryptoHelper.java b/services/core/java/com/android/server/accounts/CryptoHelper.java
new file mode 100644
index 0000000..2b59b74
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/CryptoHelper.java
@@ -0,0 +1,140 @@
+package com.android.server.accounts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
+ * key for {@link AccountManagerService}.
+ */
+/* default */ class CryptoHelper {
+    private static final String TAG = "Account";
+
+    private static final String KEY_CIPHER = "cipher";
+    private static final String KEY_MAC = "mac";
+    private static final String KEY_ALGORITHM = "AES";
+    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+    private static final String MAC_ALGORITHM = "HMACSHA256";
+    private static final int IV_LENGTH = 16;
+
+    private static CryptoHelper sInstance;
+    // Keys used for encrypting and decrypting data returned in a Bundle.
+    private final SecretKeySpec mCipherKeySpec;
+    private final SecretKeySpec mMacKeySpec;
+    private final IvParameterSpec mIv;
+
+    /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
+        if (sInstance == null) {
+            sInstance = new CryptoHelper();
+        }
+        return sInstance;
+    }
+
+    private CryptoHelper() throws NoSuchAlgorithmException {
+        KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
+        SecretKey skey = kgen.generateKey();
+        mCipherKeySpec = new SecretKeySpec(skey.getEncoded(), KEY_ALGORITHM);
+
+        kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
+        skey = kgen.generateKey();
+        mMacKeySpec = new SecretKeySpec(skey.getEncoded(), MAC_ALGORITHM);
+
+        // Create random iv
+        byte[] iv = new byte[IV_LENGTH];
+        SecureRandom secureRandom = new SecureRandom();
+        secureRandom.nextBytes(iv);
+        mIv = new IvParameterSpec(iv);
+    }
+
+    @NonNull
+    /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+        Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
+        Parcel parcel = Parcel.obtain();
+        bundle.writeToParcel(parcel, 0);
+        byte[] bytes = parcel.marshall();
+        parcel.recycle();
+
+        Bundle encryptedBundle = new Bundle();
+
+        byte[] cipher = encrypt(bytes);
+        byte[] mac = createMac(cipher);
+
+        encryptedBundle.putByteArray(KEY_CIPHER, cipher);
+        encryptedBundle.putByteArray(KEY_MAC, mac);
+
+        return encryptedBundle;
+    }
+
+    @Nullable
+    /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+        Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
+        byte[] cipherArray = bundle.getByteArray(KEY_CIPHER);
+        byte[] macArray = bundle.getByteArray(KEY_MAC);
+
+        if (!verifyMac(cipherArray, macArray)) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Escrow mac mismatched!");
+            }
+            return null;
+        }
+
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(Cipher.DECRYPT_MODE, mCipherKeySpec, mIv);
+        byte[] decryptedBytes = cipher.doFinal(cipherArray);
+
+        Parcel decryptedParcel = Parcel.obtain();
+        decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
+        decryptedParcel.setDataPosition(0);
+        Bundle decryptedBundle = new Bundle();
+        decryptedBundle.readFromParcel(decryptedParcel);
+        decryptedParcel.recycle();
+        return decryptedBundle;
+    }
+
+    private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] macArray)
+            throws GeneralSecurityException {
+
+        if (cipherArray == null || cipherArray.length == 0 || macArray == null
+                || macArray.length == 0) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Cipher or MAC is empty!");
+            }
+            return false;
+        }
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mMacKeySpec);
+        mac.update(cipherArray);
+        return Arrays.equals(macArray, mac.doFinal());
+    }
+
+    @NonNull
+    private byte[] encrypt(@NonNull byte[] data) throws GeneralSecurityException {
+        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, mCipherKeySpec, mIv);
+        return cipher.doFinal(data);
+    }
+
+    @NonNull
+    private byte[] createMac(@NonNull byte[] cipher) throws GeneralSecurityException {
+        Mac mac = Mac.getInstance(MAC_ALGORITHM);
+        mac.init(mMacKeySpec);
+        return mac.doFinal(cipher);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4d05f9a..17b3d2a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1094,15 +1094,20 @@
                 }
                 r = smap.mServicesByName.get(name);
                 if (r == null && createIfNeeded) {
-                    // Before going further -- if this app is not allowed to run in the background,
-                    // then at this point we aren't going to let it period.
-                    if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
-                            sInfo.packageName, callingPid)) {
-                        Slog.w(TAG, "Background execution not allowed: service "
-                                + r.intent + " to " + name.flattenToShortString()
-                                + " from pid=" + callingPid + " uid=" + callingUid
-                                + " pkg=" + callingPackage);
-                        return null;
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // Before going further -- if this app is not allowed to run in the
+                        // background, then at this point we aren't going to let it period.
+                        if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+                                sInfo.packageName, callingPid)) {
+                            Slog.w(TAG, "Background execution not allowed: service "
+                                    + r.intent + " to " + name.flattenToShortString()
+                                    + " from pid=" + callingPid + " uid=" + callingUid
+                                    + " pkg=" + callingPackage);
+                            return null;
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
                     }
 
                     Intent.FilterComparison filter
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e6bfb7..3a0d80b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,72 +16,8 @@
 
 package com.android.server.am;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-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.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static com.android.internal.util.XmlUtils.readBooleanAttribute;
-import static com.android.internal.util.XmlUtils.readIntAttribute;
-import static com.android.internal.util.XmlUtils.readLongAttribute;
-import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
-import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
-import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
-import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.Manifest;
-import android.app.ActivityManager.StackId;
-import android.app.AppOpsManager;
-import android.app.ApplicationThreadNative;
-import android.app.BroadcastOptions;
-import android.app.IActivityContainer;
-import android.app.IActivityContainerCallback;
-import android.app.IAppTask;
-import android.app.ITaskStackListener;
-import android.app.ProfilerInfo;
-import android.app.assist.AssistContent;
-import android.app.assist.AssistStructure;
-import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
-import android.content.pm.PermissionInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.BatteryStats;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.os.ResultReceiver;
-import android.os.Trace;
-import android.os.TransactionTooLargeException;
-import android.os.WorkSource;
-import android.os.storage.IMountService;
-import android.os.storage.MountServiceInternal;
-import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.VoiceInteractionSession;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.DebugUtils;
-import android.view.Display;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -118,19 +54,16 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.wm.AppTransition;
 import com.android.server.wm.WindowManagerService;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityManager.TaskThumbnailInfo;
 import android.app.ActivityManagerInternal;
@@ -140,24 +73,37 @@
 import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
+import android.app.AppOpsManager;
 import android.app.ApplicationErrorReport;
+import android.app.ApplicationThreadNative;
+import android.app.BroadcastOptions;
 import android.app.Dialog;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
 import android.app.IActivityController;
+import android.app.IAppTask;
 import android.app.IApplicationThread;
 import android.app.IInstrumentationWatcher;
 import android.app.INotificationManager;
 import android.app.IProcessObserver;
 import android.app.IServiceConnection;
 import android.app.IStopUserCallback;
-import android.app.IUidObserver;
+import android.app.ITaskStackListener;
 import android.app.IUiAutomationConnection;
+import android.app.IUidObserver;
 import android.app.IUserSwitchObserver;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.backup.IBackupManager;
+import android.app.ProfilerInfo;
 import android.app.admin.DevicePolicyManager;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.backup.IBackupManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ClipData;
@@ -181,18 +127,24 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.UserInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.net.Proxy;
 import android.net.ProxyInfo;
 import android.net.Uri;
+import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -210,21 +162,35 @@
 import android.os.Message;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.TransactionTooLargeException;
 import android.os.UpdateLock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.WorkSource;
+import android.os.storage.IMountService;
+import android.os.storage.MountServiceInternal;
+import android.os.storage.StorageManager;
 import android.provider.Settings;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionSession;
 import android.text.format.DateUtils;
 import android.text.format.Time;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
+import android.util.DebugUtils;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -233,13 +199,12 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.Xml;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 
-import dalvik.system.VMRuntime;
-
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
@@ -269,6 +234,100 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
+import dalvik.system.VMRuntime;
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
+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.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
+import static android.provider.Settings.Global.DEBUG_APP;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
 public final class ActivityManagerService extends ActivityManagerNative
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
 
@@ -1211,7 +1270,8 @@
     String mOrigDebugApp = null;
     boolean mOrigWaitForDebugger = false;
     boolean mAlwaysFinishActivities = false;
-    boolean mForceResizableActivites;
+    boolean mForceResizableActivities;
+    boolean mSupportsFreeformWindowManagement;
     IActivityController mController = null;
     String mProfileApp = null;
     ProcessRecord mProfileProc = null;
@@ -8827,12 +8887,19 @@
     }
 
     @Override
-    public Bitmap getTaskDescriptionIcon(String filename) {
-        if (!FileUtils.isValidExtFilename(filename)
-                || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
-            throw new IllegalArgumentException("Bad filename: " + filename);
+    public Bitmap getTaskDescriptionIcon(String filePath, int userId) {
+        if (userId != UserHandle.getCallingUserId()) {
+            enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "getTaskDescriptionIcon");
         }
-        return mTaskPersister.getTaskDescriptionIcon(filename);
+        final File passedIconFile = new File(filePath);
+        final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId),
+                passedIconFile.getName());
+        if (!legitIconFile.getPath().equals(filePath)
+                || !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
+            throw new IllegalArgumentException("Bad file path: " + filePath);
+        }
+        return mTaskPersister.getTaskDescriptionIcon(filePath);
     }
 
     @Override
@@ -11805,21 +11872,21 @@
 
     private void retrieveSettings() {
         final ContentResolver resolver = mContext.getContentResolver();
-        String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP);
-        boolean waitForDebugger = Settings.Global.getInt(
-            resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
-        boolean alwaysFinishActivities = Settings.Global.getInt(
-            resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
-        boolean forceRtl = Settings.Global.getInt(
-                resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
-        int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
-        boolean forceResizable = Settings.Global.getInt(
-                resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
-                defaultForceResizable) != 0;
-        // Transfer any global setting for forcing RTL layout, into a System Property
-        SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+        final boolean freeformWindowManagement =
+                mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
 
-        Configuration configuration = new Configuration();
+        final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+        final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
+        final boolean alwaysFinishActivities =
+                Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+        final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
+        final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
+        final boolean forceResizable = Settings.Global.getInt(
+                resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, defaultForceResizable) != 0;
+        // Transfer any global setting for forcing RTL layout, into a System Property
+        SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+
+        final Configuration configuration = new Configuration();
         Settings.System.getConfiguration(resolver, configuration);
         if (forceRtl) {
             // This will take care of setting the correct layout direction flags
@@ -11830,7 +11897,8 @@
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
             mAlwaysFinishActivities = alwaysFinishActivities;
-            mForceResizableActivites = forceResizable;
+            mForceResizableActivities = forceResizable;
+            mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
             // This happens before any activities are started, so we can
             // change mConfiguration in-place.
             updateConfigurationLocked(configuration, null, true);
@@ -20152,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/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index aa04bd7..ea8a12f 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -61,6 +61,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -1212,8 +1213,10 @@
         if (_taskDescription.getIconFilename() == null &&
                 (icon = _taskDescription.getIcon()) != null) {
             final String iconFilename = createImageFilename(createTime, task.taskId);
-            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilename);
-            _taskDescription.setIconFilename(iconFilename);
+            final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
+            final String iconFilePath = iconFile.getAbsolutePath();
+            mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath);
+            _taskDescription.setIconFilename(iconFilePath);
         }
         taskDescription = _taskDescription;
     }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0ec4b18..124d2ef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -99,7 +99,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.TransactionTooLargeException;
 import android.os.UserHandle;
@@ -1666,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);
 
@@ -1926,9 +1923,9 @@
         boolean overrideBounds = false;
         Rect newBounds = null;
         if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) {
-            if (options.hasBounds()) {
+            if (canUseActivityOptionsLaunchBounds(options)) {
                 overrideBounds = true;
-                newBounds = options.getBounds();
+                newBounds = options.getLaunchBounds();
             }
         }
 
@@ -2930,8 +2927,8 @@
         }
 
         if (task.mResizeable && options != null) {
-            if (options.hasBounds()) {
-                Rect bounds = options.getBounds();
+            if (canUseActivityOptionsLaunchBounds(options)) {
+                Rect bounds = options.getLaunchBounds();
                 task.updateOverrideConfiguration(bounds);
                 final int stackId = task.getLaunchStackId();
                 if (stackId != task.stack.mStackId) {
@@ -2955,6 +2952,12 @@
                 "findTaskToMoveToFront: moved to front of stack=" + task.stack);
     }
 
+    private boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
+        // We use the launch bounds in the activity options is the device supports freeform
+        // window management.
+        return options.hasLaunchBounds() && mService.mSupportsFreeformWindowManagement;
+    }
+
     ActivityStack getStack(int stackId) {
         return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
     }
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 150baf0..9a00075 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,10 +16,11 @@
 
 package com.android.server.am;
 
-import android.content.pm.IPackageManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
 import android.os.SystemClock;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -27,7 +28,6 @@
 import android.util.Xml;
 import android.os.Process;
 
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
@@ -44,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 
 import libcore.io.IoUtils;
 
@@ -54,8 +55,10 @@
     /** When not flushing don't write out files faster than this */
     private static final long INTER_WRITE_DELAY_MS = 500;
 
-    /** When not flushing delay this long before writing the first file out. This gives the next
-     * task being launched a chance to load its resources without this occupying IO bandwidth. */
+    /**
+     * When not flushing delay this long before writing the first file out. This gives the next task
+     * being launched a chance to load its resources without this occupying IO bandwidth.
+     */
     private static final long PRE_TASK_DELAY_MS = 3000;
 
     /** The maximum number of entries to keep in the queue before draining it automatically. */
@@ -72,24 +75,23 @@
 
     private static final String TAG_TASK = "task";
 
-    static File sImagesDir;
-    static File sTasksDir;
-
     private final ActivityManagerService mService;
     private final ActivityStackSupervisor mStackSupervisor;
     private final RecentTasks mRecentTasks;
 
-    /** Value determines write delay mode as follows:
-     *    < 0 We are Flushing. No delays between writes until the image queue is drained and all
-     * tasks needing persisting are written to disk. There is no delay between writes.
-     *    == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
-     *    > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
-     * delayed by #INTER_WRITE_DELAY_MS. */
+    /**
+     * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
+     * until the image queue is drained and all tasks needing persisting are written to disk. There
+     * is no delay between writes. == 0 We are Idle. Next writes will be delayed by
+     * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent
+     * writes will be delayed by #INTER_WRITE_DELAY_MS.
+     */
     private long mNextWriteTime = 0;
 
     private final LazyTaskWriterThread mLazyTaskWriterThread;
 
     private static class WriteQueueItem {}
+
     private static class TaskWriteQueueItem extends WriteQueueItem {
         final TaskRecord mTask;
 
@@ -97,12 +99,13 @@
             mTask = task;
         }
     }
+
     private static class ImageWriteQueueItem extends WriteQueueItem {
-        final String mFilename;
+        final String mFilePath;
         Bitmap mImage;
 
-        ImageWriteQueueItem(String filename, Bitmap image) {
-            mFilename = filename;
+        ImageWriteQueueItem(String filePath, Bitmap image) {
+            mFilePath = filePath;
             mImage = image;
         }
     }
@@ -111,19 +114,18 @@
 
     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
             RecentTasks recentTasks) {
-        sTasksDir = new File(systemDir, TASKS_DIRNAME);
-        if (!sTasksDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
-            if (!sTasksDir.mkdir()) {
-                Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
+
+        final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
+        if (legacyImagesDir.exists()) {
+            if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
+                Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
             }
         }
 
-        sImagesDir = new File(systemDir, IMAGES_DIRNAME);
-        if (!sImagesDir.exists()) {
-            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
-            if (!sImagesDir.mkdir()) {
-                Slog.e(TAG, "Failure creating images directory " + sImagesDir);
+        final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
+        if (legacyTasksDir.exists()) {
+            if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
+                Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
             }
         }
 
@@ -144,8 +146,8 @@
         for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
             final WriteQueueItem item = mWriteQueue.get(queueNdx);
             if (item instanceof ImageWriteQueueItem &&
-                    ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
-                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
+                    ((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) {
+                if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
                         " from write queue");
                 mWriteQueue.remove(queueNdx);
             }
@@ -213,14 +215,14 @@
         }
     }
 
-    void saveImage(Bitmap image, String filename) {
+    void saveImage(Bitmap image, String filePath) {
         synchronized (this) {
             int queueNdx;
             for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    if (imageWriteQueueItem.mFilename.equals(filename)) {
+                    if (imageWriteQueueItem.mFilePath.equals(filePath)) {
                         // replace the Bitmap with the new one.
                         imageWriteQueueItem.mImage = image;
                         break;
@@ -228,14 +230,14 @@
                 }
             }
             if (queueNdx < 0) {
-                mWriteQueue.add(new ImageWriteQueueItem(filename, image));
+                mWriteQueue.add(new ImageWriteQueueItem(filePath, image));
             }
             if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
                 mNextWriteTime = FLUSH_QUEUE;
             } else if (mNextWriteTime == 0) {
                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
             }
-            if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+            if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" +
                     SystemClock.uptimeMillis() + " mNextWriteTime=" +
                     mNextWriteTime + " Callers=" + Debug.getCallers(4));
             notifyAll();
@@ -244,22 +246,22 @@
         yieldIfQueueTooDeep();
     }
 
-    Bitmap getTaskDescriptionIcon(String filename) {
+    Bitmap getTaskDescriptionIcon(String filePath) {
         // See if it is in the write queue
-        final Bitmap icon = getImageFromWriteQueue(filename);
+        final Bitmap icon = getImageFromWriteQueue(filePath);
         if (icon != null) {
             return icon;
         }
-        return restoreImage(filename);
+        return restoreImage(filePath);
     }
 
-    Bitmap getImageFromWriteQueue(String filename) {
+    Bitmap getImageFromWriteQueue(String filePath) {
         synchronized (this) {
             for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    if (imageWriteQueueItem.mFilename.equals(filename)) {
+                    if (imageWriteQueueItem.mFilePath.equals(filePath)) {
                         return imageWriteQueueItem.mImage;
                     }
                 }
@@ -275,7 +277,7 @@
         xmlSerializer.setOutput(stringWriter);
 
         if (DEBUG) xmlSerializer.setFeature(
-                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                "http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
         // save task
         xmlSerializer.startDocument(null, true);
@@ -321,19 +323,22 @@
         return null;
     }
 
-    ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) {
-        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+    private List<TaskRecord> restoreTasksForUserLocked(final int userId) {
+        final List<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
 
-        File[] recentFiles = sTasksDir.listFiles();
+        File userTasksDir = getUserTasksDir(userId);
+
+        File[] recentFiles = userTasksDir.listFiles();
         if (recentFiles == null) {
-            Slog.e(TAG, "Unable to list files from " + sTasksDir);
+            Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
             return tasks;
         }
 
         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
             File taskFile = recentFiles[taskNdx];
-            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+            if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId
+                    + ", taskFile=" + taskFile.getName());
             BufferedReader reader = null;
             boolean deleteFile = false;
             try {
@@ -348,30 +353,29 @@
                     if (event == XmlPullParser.START_TAG) {
                         if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
                         if (TAG_TASK.equals(name)) {
-                            final TaskRecord task =
-                                    TaskRecord.restoreFromXml(in, mStackSupervisor);
-                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
-                                    task);
+                            final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
+                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task="
+                                    + task);
                             if (task != null) {
                                 // XXX Don't add to write queue... there is no reason to write
                                 // out the stuff we just read, if we don't write it we will
                                 // read the same thing again.
-                                //mWriteQueue.add(new TaskWriteQueueItem(task));
+                                // mWriteQueue.add(new TaskWriteQueueItem(task));
                                 final int taskId = task.taskId;
                                 mStackSupervisor.setNextTaskId(taskId);
                                 // Check if it's a valid user id. Don't add tasks for removed users.
-                                if (ArrayUtils.contains(validUserIds, task.userId)) {
+                                if (userId == task.userId) {
                                     task.isPersistable = true;
                                     tasks.add(task);
                                     recoveredTaskIds.add(taskId);
                                 }
                             } else {
-                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
-                                        fileToString(taskFile));
+                                Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
+                                        + taskFile + ": " + fileToString(taskFile));
                             }
                         } else {
-                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
-                                    " name=" + name);
+                            Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
+                                    + " name=" + name);
                         }
                     }
                     XmlUtils.skipCurrentTag(in);
@@ -390,10 +394,19 @@
         }
 
         if (!DEBUG) {
-            removeObsoleteFiles(recoveredTaskIds);
+            removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
+        }
+        return tasks;
+    }
+
+    ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
+        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+
+        for (int userId : validUserIds) {
+            tasks.addAll(restoreTasksForUserLocked(userId));
         }
 
-        // Fixup task affiliation from taskIds
+        // Fix up task affiliation from taskIds
         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = tasks.get(taskNdx);
             task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
@@ -420,7 +433,7 @@
     }
 
     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
-        if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
+        if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
                 " files=" + files);
         if (files == null) {
             Slog.e(TAG, "File error accessing recents directory (too many files open?).");
@@ -434,14 +447,14 @@
                 final int taskId;
                 try {
                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+                    if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
                 } catch (Exception e) {
-                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
+                    Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
                     file.delete();
                     continue;
                 }
                 if (!persistentTaskIds.contains(taskId)) {
-                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
+                    if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
                     file.delete();
                 }
             }
@@ -449,13 +462,39 @@
     }
 
     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
-        removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
-        removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
+        for (int userId : mService.getRunningUserIds()) {
+            removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
+            removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
+        }
     }
 
     static Bitmap restoreImage(String filename) {
         if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
-        return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
+        return BitmapFactory.decodeFile(filename);
+    }
+
+    static File getUserTasksDir(int userId) {
+        File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
+
+        if (!userTasksDir.exists()) {
+            if (!userTasksDir.mkdir()) {
+                Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": "
+                        + userTasksDir);
+            }
+        }
+        return userTasksDir;
+    }
+
+    static File getUserImagesDir(int userId) {
+        File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
+
+        if (!userImagesDir.exists()) {
+            if (!userImagesDir.mkdir()) {
+                Slog.e(TAG, "Failure creating images directory for user " + userId + ": "
+                        + userImagesDir);
+            }
+        }
+        return userImagesDir;
     }
 
     private class LazyTaskWriterThread extends Thread {
@@ -508,7 +547,6 @@
                                 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
                     }
 
-
                     while (mWriteQueue.isEmpty()) {
                         if (mNextWriteTime != 0) {
                             mNextWriteTime = 0; // idle.
@@ -542,15 +580,15 @@
 
                 if (item instanceof ImageWriteQueueItem) {
                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
-                    final String filename = imageWriteQueueItem.mFilename;
+                    final String filePath = imageWriteQueueItem.mFilePath;
                     final Bitmap bitmap = imageWriteQueueItem.mImage;
-                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
                     FileOutputStream imageFile = null;
                     try {
-                        imageFile = new FileOutputStream(new File(sImagesDir, filename));
+                        imageFile = new FileOutputStream(new File(filePath));
                         bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
                     } catch (Exception e) {
-                        Slog.e(TAG, "saveImage: unable to save " + filename, e);
+                        Slog.e(TAG, "saveImage: unable to save " + filePath, e);
                     } finally {
                         IoUtils.closeQuietly(imageFile);
                     }
@@ -575,18 +613,21 @@
                         FileOutputStream file = null;
                         AtomicFile atomicFile = null;
                         try {
-                            atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
-                                    task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
+                            atomicFile = new AtomicFile(new File(
+                                    getUserTasksDir(task.userId),
+                                    String.valueOf(task.taskId) + RECENTS_FILENAME
+                                    + TASK_EXTENSION));
                             file = atomicFile.startWrite();
                             file.write(stringWriter.toString().getBytes());
                             file.write('\n');
                             atomicFile.finishWrite(file);
+
                         } catch (IOException e) {
                             if (file != null) {
                                 atomicFile.failWrite(file);
                             }
-                            Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
-                                    e);
+                            Slog.e(TAG,
+                                    "Unable to open " + atomicFile + " for persisting. " + e);
                         }
                     }
                 }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index b214080..3fc6846 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,15 +27,23 @@
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
-import android.app.ActivityManager.TaskThumbnail;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskThumbnail;
 import android.app.ActivityManager.TaskThumbnailInfo;
@@ -58,8 +66,10 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.util.XmlUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -237,7 +247,8 @@
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        userId = UserHandle.getUserId(info.applicationInfo.uid);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
         mLastThumbnailInfo = new TaskThumbnailInfo();
         taskId = _taskId;
         mAffiliatedTaskId = _taskId;
@@ -256,7 +267,8 @@
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        userId = UserHandle.getUserId(info.applicationInfo.uid);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
         mLastThumbnailInfo = thumbnailInfo;
         taskId = _taskId;
         mAffiliatedTaskId = _taskId;
@@ -276,7 +288,6 @@
 
         taskType = APPLICATION_ACTIVITY_TYPE;
         mTaskToReturnTo = HOME_ACTIVITY_TYPE;
-        userId = UserHandle.getUserId(info.applicationInfo.uid);
         lastTaskDescription = _taskDescription;
         mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
     }
@@ -294,7 +305,7 @@
         mService = service;
         mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
                 TaskPersister.IMAGE_EXTENSION;
-        mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+        mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(_userId), mFilename);
         mLastThumbnailInfo = lastThumbnailInfo;
         taskId = _taskId;
         intent = _intent;
@@ -326,7 +337,7 @@
         mNextAffiliateTaskId = nextTaskId;
         mCallingUid = callingUid;
         mCallingPackage = callingPackage;
-        mResizeable = resizeable || mService.mForceResizableActivites;
+        mResizeable = resizeable || mService.mForceResizableActivities;
         mPrivileged = privileged;
         ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null;
         mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -428,7 +439,7 @@
         } else {
             autoRemoveRecents = false;
         }
-        mResizeable = info.resizeable || mService.mForceResizableActivites;
+        mResizeable = info.resizeable || mService.mForceResizableActivities;
         mLockTaskMode = info.lockTaskLaunchMode;
         mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
         setLockTaskAuth();
@@ -537,7 +548,7 @@
                     mLastThumbnailFile.delete();
                 }
             } else {
-                mService.mTaskPersister.saveImage(thumbnail, mFilename);
+                mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
             }
             return true;
         }
@@ -549,7 +560,8 @@
         thumbs.thumbnailInfo = mLastThumbnailInfo;
         thumbs.thumbnailFileDescriptor = null;
         if (mLastThumbnail == null) {
-            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename);
+            thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(
+                    mLastThumbnailFile.getAbsolutePath());
         }
         // Only load the thumbnail file if we don't have a thumbnail
         if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) {
@@ -682,7 +694,7 @@
         // Only set this based on the first activity
         if (mActivities.isEmpty()) {
             taskType = r.mActivityType;
-            if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) {
+            if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivities) {
                 mResizeable = r.info.resizeable;
             }
             isPersistable = r.isPersistable();
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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd1e9dd..4424838b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1224,19 +1224,6 @@
         }
 
         @Override
-        public void setPackagePeekable(String pkg, int uid, boolean peekable) {
-            checkCallerIsSystem();
-
-            mRankingHelper.setPackagePeekable(pkg, uid, peekable);
-        }
-
-        @Override
-        public boolean getPackagePeekable(String pkg, int uid) {
-            checkCallerIsSystem();
-            return mRankingHelper.getPackagePeekable(pkg, uid);
-        }
-
-        @Override
         public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
             checkCallerIsSystem();
             mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
@@ -2157,14 +2144,6 @@
                             notification.priority = Notification.PRIORITY_HIGH;
                         }
                     }
-                    // force no heads up per package config
-                    if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
-                        if (notification.extras == null) {
-                            notification.extras = new Bundle();
-                        }
-                        notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
-                                Notification.HEADS_UP_NEVER);
-                    }
 
                     // 1. initial score: buckets of 10, around the app [-20..20]
                     final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 803db10..aea137b 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -20,10 +20,6 @@
 
     void setPackagePriority(String packageName, int uid, int priority);
 
-    boolean getPackagePeekable(String packageName, int uid);
-
-    void setPackagePeekable(String packageName, int uid, boolean peekable);
-
     int getPackageVisibilityOverride(String packageName, int uid);
 
     void setPackageVisibilityOverride(String packageName, int uid, int visibility);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 66381f5..f8b661f 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -49,11 +49,9 @@
     private static final String ATT_NAME = "name";
     private static final String ATT_UID = "uid";
     private static final String ATT_PRIORITY = "priority";
-    private static final String ATT_PEEKABLE = "peekable";
     private static final String ATT_VISIBILITY = "visibility";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
-    private static final boolean DEFAULT_PEEKABLE = true;
     private static final int DEFAULT_VISIBILITY =
             NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
 
@@ -141,7 +139,6 @@
                 if (TAG_PACKAGE.equals(tag)) {
                     int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
                     int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
-                    boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
                     int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
                     String name = parser.getAttributeValue(null, ATT_NAME);
 
@@ -167,9 +164,6 @@
                         if (priority != DEFAULT_PRIORITY) {
                             r.priority = priority;
                         }
-                        if (peekable != DEFAULT_PEEKABLE) {
-                            r.peekable = peekable;
-                        }
                         if (vis != DEFAULT_VISIBILITY) {
                             r.visibility = vis;
                         }
@@ -200,8 +194,7 @@
         final int N = mRecords.size();
         for (int i = N - 1; i >= 0; i--) {
             final Record r = mRecords.valueAt(i);
-            if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
-                    && r.visibility == DEFAULT_VISIBILITY) {
+            if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) {
                 mRecords.remove(i);
             }
         }
@@ -223,9 +216,6 @@
             if (r.priority != DEFAULT_PRIORITY) {
                 out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
             }
-            if (r.peekable != DEFAULT_PEEKABLE) {
-                out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
-            }
             if (r.visibility != DEFAULT_VISIBILITY) {
                 out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
             }
@@ -348,22 +338,6 @@
     }
 
     @Override
-    public boolean getPackagePeekable(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.peekable : DEFAULT_PEEKABLE;
-    }
-
-    @Override
-    public void setPackagePeekable(String packageName, int uid, boolean peekable) {
-        if (peekable == getPackagePeekable(packageName, uid)) {
-            return;
-        }
-        getOrCreateRecord(packageName, uid).peekable = peekable;
-        removeDefaultRecords();
-        updateConfig();
-    }
-
-    @Override
     public int getPackageVisibilityOverride(String packageName, int uid) {
         final Record r = mRecords.get(recordKey(packageName, uid));
         return r != null ? r.visibility : DEFAULT_VISIBILITY;
@@ -415,10 +389,6 @@
                     pw.print(" priority=");
                     pw.print(Notification.priorityToString(r.priority));
                 }
-                if (r.peekable != DEFAULT_PEEKABLE) {
-                    pw.print(" peekable=");
-                    pw.print(r.peekable);
-                }
                 if (r.visibility != DEFAULT_VISIBILITY) {
                     pw.print(" visibility=");
                     pw.print(Notification.visibilityToString(r.visibility));
@@ -460,7 +430,6 @@
         String pkg;
         int uid = UNKNOWN_UID;
         int priority = DEFAULT_PRIORITY;
-        boolean peekable = DEFAULT_PEEKABLE;
         int visibility = DEFAULT_VISIBILITY;
     }
 
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 ab0b182..baeccb4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -322,7 +322,7 @@
             final int userSize = mUsers.size();
             for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i);
-                if (ui.isPrimary()) {
+                if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) {
                     return ui;
                 }
             }
@@ -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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 121ef21..ae6874f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5318,11 +5318,9 @@
     }
 
     private boolean shouldDispatchInputWhenNonInteractive() {
-        if (mDisplay == null || mDisplay.getState() == Display.STATE_OFF) {
-            return false;
-        }
-        // Send events to keyguard while the screen is on and it's showing.
-        if (isKeyguardShowingAndNotOccluded()) {
+        // Send events to keyguard while the screen is on.
+        if (isKeyguardShowingAndNotOccluded() && mDisplay != null
+                && mDisplay.getState() != Display.STATE_OFF) {
             return true;
         }
 
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/Session.java b/services/core/java/com/android/server/wm/Session.java
index b85a6923..1caeca0 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -401,32 +401,6 @@
         }
     }
 
-    public void cancelDrag(IBinder dragToken) {
-        if (WindowManagerService.DEBUG_DRAG) {
-            Slog.d(WindowManagerService.TAG, "cancel drag");
-        }
-
-        synchronized (mService.mWindowMap) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                if (mService.mDragState == null) {
-                    Slog.w(WindowManagerService.TAG, "cancelDrag() without prepareDrag()");
-                    throw new IllegalStateException("cancelDrag() without prepareDrag()");
-                }
-
-                if (mService.mDragState.mToken != dragToken) {
-                    Slog.w(WindowManagerService.TAG, "cancelDrag() does not match prepareDrag()");
-                    throw new IllegalStateException("cancelDrag() does not match prepareDrag()");
-                }
-
-                mService.mDragState.mDragResult = false;
-                mService.mDragState.endDragLw();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
-
     public void dragRecipientEntered(IWindow window) {
         if (WindowManagerService.DEBUG_DRAG) {
             Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
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) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d..7ef7566 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -17,6 +17,7 @@
 package com.android.tools.layoutlib.create;
 
 import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
@@ -40,6 +41,7 @@
     private final String mClassName;
     private final Set<String> mDelegateMethods;
     private final Log mLog;
+    private boolean mIsStaticInnerClass;
 
     /**
      * Creates a new {@link DelegateClassAdapter} that can transform some methods
@@ -62,16 +64,30 @@
         mLog = log;
         mClassName = className;
         mDelegateMethods = delegateMethods;
+        // If this is an inner class, by default, we assume it's static. If it's not we will detect
+        // by looking at the fields (see visitField)
+        mIsStaticInnerClass = className.contains("$");
     }
 
     //----------------------------------
     // Methods from the ClassAdapter
 
     @Override
+    public FieldVisitor visitField(int access, String name, String desc, String signature,
+            Object value) {
+        if (mIsStaticInnerClass && "this$0".equals(name)) {
+            // Having a "this$0" field, proves that this class is not a static inner class.
+            mIsStaticInnerClass = false;
+        }
+
+        return super.visitField(access, name, desc, signature, value);
+    }
+
+    @Override
     public MethodVisitor visitMethod(int access, String name, String desc,
             String signature, String[] exceptions) {
 
-        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+        boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
         boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
 
         boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
@@ -96,7 +112,8 @@
             MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
 
             DelegateMethodAdapter a = new DelegateMethodAdapter(
-                    mLog, null, mwDelegate, mClassName, name, desc, isStatic);
+                    mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
+                    mIsStaticInnerClass);
 
             // A native has no code to visit, so we need to generate it directly.
             a.generateDelegateCode();
@@ -120,6 +137,7 @@
                                                      desc, signature, exceptions);
 
         return new DelegateMethodAdapter(
-                mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+                mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
+                mIsStaticInnerClass);
     }
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 12690db..cca9e57 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -85,6 +85,8 @@
     private String mDesc;
     /** True if the original method is static. */
     private final boolean mIsStatic;
+    /** True if the method is contained in a static inner class */
+    private final boolean mIsStaticInnerClass;
     /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
     private final String mClassName;
     /** The method name. */
@@ -120,7 +122,8 @@
             String className,
             String methodName,
             String desc,
-            boolean isStatic) {
+            boolean isStatic,
+            boolean isStaticClass) {
         super(Opcodes.ASM4);
         mLog = log;
         mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@
         mMethodName = methodName;
         mDesc = desc;
         mIsStatic = isStatic;
+        mIsStaticInnerClass = isStaticClass;
     }
 
     /**
@@ -206,7 +210,7 @@
         // by the 'this' of any outer class, if any.
         if (!mIsStatic) {
 
-            if (outerType != null) {
+            if (outerType != null && !mIsStaticInnerClass) {
                 // The first-level inner class has a package-protected member called 'this$0'
                 // that points to the outer class.
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 648cea43..e37a09b 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
 import com.android.tools.layoutlib.create.dataclass.OuterClass;
 import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,6 +57,8 @@
     private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
     private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
                                                    InnerClass.class.getSimpleName();
+    private static final String STATIC_INNER_CLASS_NAME =
+            OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
 
     @Before
     public void setUp() throws Exception {
@@ -294,6 +297,61 @@
         }
     }
 
+    @Test
+    public void testDelegateStaticInner() throws Throwable {
+        // We'll delegate the "get" method of both the inner and outer class.
+        HashSet<String> delegateMethods = new HashSet<String>();
+        delegateMethods.add("get");
+
+        // Generate the delegate for the outer class.
+        ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+        String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+                mLog, cwOuter, outerClassName, delegateMethods);
+        ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+        cr.accept(cvOuter, 0 /* flags */);
+
+        // Generate the delegate for the static inner class.
+        ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+        String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cvInner = new DelegateClassAdapter(
+                mLog, cwInner, innerClassName, delegateMethods);
+        cr = new ClassReader(STATIC_INNER_CLASS_NAME);
+        cr.accept(cvInner, 0 /* flags */);
+
+        // Load the generated classes in a different class loader and try them
+        ClassLoader2 cl2 = null;
+        try {
+            cl2 = new ClassLoader2() {
+                @Override
+                public void testModifiedInstance() throws Exception {
+
+                    // Check the outer class
+                    Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+                    Object o2 = outerClazz2.newInstance();
+                    assertNotNull(o2);
+
+                    // Check the inner class. Since it's not a static inner class, we need
+                    // to use the hidden constructor that takes the outer class as first parameter.
+                    Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
+                    Constructor<?> innerCons = innerClazz2.getConstructor();
+                    Object i2 = innerCons.newInstance();
+                    assertNotNull(i2);
+
+                    // The original StaticInner.get returns 100+10+20,
+                    // but the delegate makes it return 6+10+20
+                    assertEquals(6+10+20, callGet(i2, 10, 20));
+                    assertEquals(100+10+20, callGet_Original(i2, 10, 20));
+                }
+            };
+            cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+            cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
+            cl2.testModifiedInstance();
+        } catch (Throwable t) {
+            throw dumpGeneratedClass(t, cl2);
+        }
+    }
+
     //-------
 
     /**
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index f083e76..6dfb816 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -45,6 +45,16 @@
         }
     }
 
+    public static class StaticInnerClass {
+        public StaticInnerClass() {
+        }
+
+        // StaticInnerClass.get returns 100 + a + b
+        public int get(int a, long b) {
+            return 100 + a + (int) b;
+        }
+    }
+
     @SuppressWarnings("unused")
     private String privateMethod() {
         return "outerPrivateMethod";
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
new file mode 100644
index 0000000..a29439e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_StaticInnerClass_Delegate {
+    // The delegate override of Inner.get return 6 + a + b
+    public static int get(StaticInnerClass inner, int a, long b) {
+        return 6 + a + (int) b;
+    }
+}