Merge "AccountManager: add startUpdateCredentials API."
diff --git a/Android.mk b/Android.mk
index 71bba0f..f545207 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)
@@ -427,7 +430,7 @@
 			$(framework_res_source_path)/com/android/internal/R.java
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart conscrypt okhttp core-junit bouncycastle ext
+LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
 
 LOCAL_MODULE := framework
 
@@ -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)
@@ -715,6 +719,7 @@
 	$(framework_res_source_path)/com/android/internal/R.java
 
 framework_docs_LOCAL_API_CHECK_JAVA_LIBRARIES := \
+	core-oj \
 	core-libart \
 	conscrypt \
 	bouncycastle \
@@ -1100,7 +1105,7 @@
 LOCAL_SRC_FILES := $(ext_src_files)
 
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := core-libart
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
 LOCAL_STATIC_JAVA_LIBRARIES := libphonenumber-platform
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ext
diff --git a/api/current.txt b/api/current.txt
index 2a363cb..2f5686e 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";
@@ -3501,6 +3502,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();
@@ -4867,6 +4869,7 @@
     field public static final int PRIORITY_MAX = 2; // 0x2
     field public static final int PRIORITY_MIN = -2; // 0xfffffffe
     field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+    field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
     field public static final int VISIBILITY_PRIVATE = 0; // 0x0
     field public static final int VISIBILITY_PUBLIC = 1; // 0x1
     field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -5865,6 +5868,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+    field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
     field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
     field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -25795,6 +25799,7 @@
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
     field public static final android.net.Uri CONTENT_VCARD_URI;
+    field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -25915,12 +25920,15 @@
   }
 
   public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+    method public static boolean isEnterpriseDirectoryId(long);
+    method public static boolean isRemoteDirectory(long);
     method public static void notifyDirectoryChange(android.content.ContentResolver);
     field public static final java.lang.String ACCOUNT_NAME = "accountName";
     field public static final java.lang.String ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final android.net.Uri CORP_CONTENT_URI;
     field public static final long DEFAULT = 0L; // 0x0L
     field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
     field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -26280,6 +26288,7 @@
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
     field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
@@ -29052,6 +29061,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 {
@@ -34416,6 +34454,7 @@
     ctor public LocaleList(java.util.Locale[]);
     method public static android.util.LocaleList forLanguageTags(java.lang.String);
     method public java.util.Locale get(int);
+    method public java.util.Locale getBestMatch(java.lang.String[]);
     method public static android.util.LocaleList getDefault();
     method public static android.util.LocaleList getEmptyLocaleList();
     method public java.util.Locale getPrimary();
@@ -36247,7 +36286,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
-    method public final void cancelDrag();
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -36265,6 +36304,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);
@@ -36490,6 +36530,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();
@@ -36726,7 +36767,8 @@
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
     method public void startAnimation(android.view.animation.Animation);
-    method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
     method public boolean startNestedScroll(int);
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
@@ -37099,6 +37141,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);
@@ -37165,6 +37208,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();
@@ -37279,6 +37324,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);
@@ -37308,12 +37354,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 3b7ffcb..cbe8db7 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";
@@ -3604,6 +3605,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();
@@ -4985,6 +4987,7 @@
     field public static final int PRIORITY_MAX = 2; // 0x2
     field public static final int PRIORITY_MIN = -2; // 0xfffffffe
     field public static final deprecated int STREAM_DEFAULT = -1; // 0xffffffff
+    field public static final java.lang.String TOPIC_DEFAULT = "system_default_topic";
     field public static final int VISIBILITY_PRIVATE = 0; // 0x0
     field public static final int VISIBILITY_PUBLIC = 1; // 0x1
     field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
@@ -5999,6 +6002,7 @@
     field public static final java.lang.String EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED = "android.app.extra.PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCALE = "android.app.extra.PROVISIONING_LOCALE";
     field public static final java.lang.String EXTRA_PROVISIONING_LOCAL_TIME = "android.app.extra.PROVISIONING_LOCAL_TIME";
+    field public static final java.lang.String EXTRA_PROVISIONING_LOGO_URI = "android.app.extra.PROVISIONING_LOGO_URI";
     field public static final java.lang.String EXTRA_PROVISIONING_SKIP_ENCRYPTION = "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
     field public static final java.lang.String EXTRA_PROVISIONING_TIME_ZONE = "android.app.extra.PROVISIONING_TIME_ZONE";
     field public static final java.lang.String EXTRA_PROVISIONING_WIFI_HIDDEN = "android.app.extra.PROVISIONING_WIFI_HIDDEN";
@@ -8524,6 +8528,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";
@@ -8571,6 +8576,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";
@@ -27776,6 +27782,7 @@
     field public static final android.net.Uri CONTENT_URI;
     field public static final java.lang.String CONTENT_VCARD_TYPE = "text/x-vcard";
     field public static final android.net.Uri CONTENT_VCARD_URI;
+    field public static final android.net.Uri CORP_CONTENT_FILTER_URI;
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS";
     field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES";
@@ -27896,12 +27903,15 @@
   }
 
   public static final class ContactsContract.Directory implements android.provider.BaseColumns {
+    method public static boolean isEnterpriseDirectoryId(long);
+    method public static boolean isRemoteDirectory(long);
     method public static void notifyDirectoryChange(android.content.ContentResolver);
     field public static final java.lang.String ACCOUNT_NAME = "accountName";
     field public static final java.lang.String ACCOUNT_TYPE = "accountType";
     field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_directory";
     field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/contact_directories";
     field public static final android.net.Uri CONTENT_URI;
+    field public static final android.net.Uri CORP_CONTENT_URI;
     field public static final long DEFAULT = 0L; // 0x0L
     field public static final java.lang.String DIRECTORY_AUTHORITY = "authority";
     field public static final java.lang.String DISPLAY_NAME = "displayName";
@@ -28291,6 +28301,7 @@
     field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1
     field public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 512; // 0x200
     field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2
+    field public static final int FLAG_VIRTUAL_DOCUMENT = 1024; // 0x400
     field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
   }
 
@@ -31198,6 +31209,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 {
@@ -36737,6 +36777,7 @@
     ctor public LocaleList(java.util.Locale[]);
     method public static android.util.LocaleList forLanguageTags(java.lang.String);
     method public java.util.Locale get(int);
+    method public java.util.Locale getBestMatch(java.lang.String[]);
     method public static android.util.LocaleList getDefault();
     method public static android.util.LocaleList getEmptyLocaleList();
     method public java.util.Locale getPrimary();
@@ -38568,7 +38609,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
-    method public final void cancelDrag();
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -38586,6 +38627,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);
@@ -38811,6 +38853,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();
@@ -39047,7 +39090,8 @@
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int);
     method public void startAnimation(android.view.animation.Animation);
-    method public final boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final deprecated boolean startDrag(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+    method public final boolean startDragAndDrop(android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
     method public boolean startNestedScroll(int);
     method public void stopNestedScroll();
     method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
@@ -39420,6 +39464,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);
@@ -39486,6 +39531,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();
@@ -39600,6 +39647,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);
@@ -39629,12 +39677,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/appops/Android.mk b/cmds/appops/Android.mk
index 1e15204..6801ce9 100644
--- a/cmds/appops/Android.mk
+++ b/cmds/appops/Android.mk
@@ -3,14 +3,8 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := appops
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := appops
 LOCAL_SRC_FILES := appops
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
-
diff --git a/cmds/appops/appops b/cmds/appops/appops
index 407e551..25d2031 100755
--- a/cmds/appops/appops
+++ b/cmds/appops/appops
@@ -1,5 +1 @@
-# Script to start "appwidget" on the device, which has a very rudimentary shell.
-base=/system
-export CLASSPATH=$base/framework/appops.jar
-exec app_process $base/bin com.android.commands.appops.AppOpsCommand "$@"
-
+cmd appops $@
diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
deleted file mode 100644
index c9b9e58..0000000
--- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-** Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package com.android.commands.appops;
-
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import android.util.TimeUtils;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.os.BaseCommand;
-
-import java.io.PrintStream;
-import java.util.List;
-
-/**
- * This class is a command line utility for manipulating AppOps permissions.
- */
-public class AppOpsCommand extends BaseCommand {
-
-    public static void main(String[] args) {
-        new AppOpsCommand().run(args);
-    }
-
-    @Override
-    public void onShowUsage(PrintStream out) {
-        out.println("usage: appops set [--user <USER_ID>] <PACKAGE> <OP> <MODE>\n"
-                + "       appops get [--user <USER_ID>] <PACKAGE> [<OP>]\n"
-                + "       appops reset [--user <USER_ID>] [<PACKAGE>]\n"
-                + "  <PACKAGE> an Android package name.\n"
-                + "  <OP>      an AppOps operation.\n"
-                + "  <MODE>    one of allow, ignore, deny, or default\n"
-                + "  <USER_ID> the user id under which the package is installed. If --user is not\n"
-                + "            specified, the current user is assumed.\n");
-    }
-
-    private static final String COMMAND_SET = "set";
-    private static final String COMMAND_GET = "get";
-    private static final String COMMAND_RESET = "reset";
-
-    @Override
-    public void onRun() throws Exception {
-        String command = nextArgRequired();
-        switch (command) {
-            case COMMAND_SET:
-                runSet();
-                break;
-
-            case COMMAND_GET:
-                runGet();
-                break;
-
-            case COMMAND_RESET:
-                runReset();
-                break;
-
-            default:
-                System.err.println("Error: Unknown command: '" + command + "'.");
-                break;
-        }
-    }
-
-    private static final String ARGUMENT_USER = "--user";
-
-    // Modes
-    private static final String MODE_ALLOW = "allow";
-    private static final String MODE_DENY = "deny";
-    private static final String MODE_IGNORE = "ignore";
-    private static final String MODE_DEFAULT = "default";
-
-    private int strOpToOp(String op) {
-        try {
-            return AppOpsManager.strOpToOp(op);
-        } catch (IllegalArgumentException e) {
-        }
-        try {
-            return Integer.parseInt(op);
-        } catch (NumberFormatException e) {
-        }
-        try {
-            return AppOpsManager.strDebugOpToOp(op);
-        } catch (IllegalArgumentException e) {
-            System.err.println("Error: " + e.getMessage());
-            return -1;
-        }
-    }
-
-    private void runSet() throws Exception {
-        String packageName = null;
-        String op = null;
-        String mode = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else if (mode == null) {
-                    mode = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        } else if (op == null) {
-            System.err.println("Error: Operation not specified.");
-            return;
-        } else if (mode == null) {
-            System.err.println("Error: Mode not specified.");
-            return;
-        }
-
-        final int opInt = strOpToOp(op);
-        if (opInt < 0) {
-            return;
-        }
-        final int modeInt;
-        switch (mode) {
-            case MODE_ALLOW:
-                modeInt = AppOpsManager.MODE_ALLOWED;
-                break;
-            case MODE_DENY:
-                modeInt = AppOpsManager.MODE_ERRORED;
-                break;
-            case MODE_IGNORE:
-                modeInt = AppOpsManager.MODE_IGNORED;
-                break;
-            case MODE_DEFAULT:
-                modeInt = AppOpsManager.MODE_DEFAULT;
-                break;
-            default:
-                System.err.println("Error: Mode " + mode + " is not valid,");
-                return;
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid;
-        if ("root".equals(packageName)) {
-            uid = 0;
-        } else {
-            uid = pm.getPackageUid(packageName, userId);
-        }
-        if (uid < 0) {
-            System.err.println("Error: No UID for " + packageName + " in user " + userId);
-            return;
-        }
-        appOpsService.setMode(opInt, uid, packageName, modeInt);
-    }
-
-    private void runGet() throws Exception {
-        String packageName = null;
-        String op = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                userId = Integer.parseInt(nextArgRequired());
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else if (op == null) {
-                    op = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        if (packageName == null) {
-            System.err.println("Error: Package name not specified.");
-            return;
-        }
-
-        final int opInt = op != null ? strOpToOp(op) : 0;
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IPackageManager pm = ActivityThread.getPackageManager();
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        final int uid;
-        if ("root".equals(packageName)) {
-            uid = 0;
-        } else {
-            uid = pm.getPackageUid(packageName, userId);
-        }
-        if (uid < 0) {
-            System.err.println("Error: No UID for " + packageName + " in user " + userId);
-            return;
-        }
-        List<AppOpsManager.PackageOps> ops = appOpsService.getOpsForPackage(uid, packageName,
-                op != null ? new int[] {opInt} : null);
-        if (ops == null || ops.size() <= 0) {
-            System.out.println("No operations.");
-            return;
-        }
-        final long now = System.currentTimeMillis();
-        for (int i=0; i<ops.size(); i++) {
-            List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
-            for (int j=0; j<entries.size(); j++) {
-                AppOpsManager.OpEntry ent = entries.get(j);
-                System.out.print(AppOpsManager.opToName(ent.getOp()));
-                System.out.print(": ");
-                switch (ent.getMode()) {
-                    case AppOpsManager.MODE_ALLOWED:
-                        System.out.print("allow");
-                        break;
-                    case AppOpsManager.MODE_IGNORED:
-                        System.out.print("ignore");
-                        break;
-                    case AppOpsManager.MODE_ERRORED:
-                        System.out.print("deny");
-                        break;
-                    case AppOpsManager.MODE_DEFAULT:
-                        System.out.print("default");
-                        break;
-                    default:
-                        System.out.print("mode=");
-                        System.out.print(ent.getMode());
-                        break;
-                }
-                if (ent.getTime() != 0) {
-                    System.out.print("; time=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getRejectTime() != 0) {
-                    System.out.print("; rejectTime=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(now - ent.getRejectTime(), sb);
-                    System.out.print(sb);
-                    System.out.print(" ago");
-                }
-                if (ent.getDuration() == -1) {
-                    System.out.print(" (running)");
-                } else if (ent.getDuration() != 0) {
-                    System.out.print("; duration=");
-                    StringBuilder sb = new StringBuilder();
-                    TimeUtils.formatDuration(ent.getDuration(), sb);
-                    System.out.print(sb);
-                }
-                System.out.println();
-            }
-        }
-    }
-
-    private void runReset() throws Exception {
-        String packageName = null;
-        int userId = UserHandle.USER_CURRENT;
-        for (String argument; (argument = nextArg()) != null;) {
-            if (ARGUMENT_USER.equals(argument)) {
-                String userStr = nextArgRequired();
-                if ("all".equals(userStr)) {
-                    userId = UserHandle.USER_ALL;
-                } else if ("current".equals(userStr)) {
-                    userId = UserHandle.USER_CURRENT;
-                } else if ("owner".equals(userStr) || "system".equals(userStr)) {
-                    userId = UserHandle.USER_SYSTEM;
-                } else {
-                    userId = Integer.parseInt(nextArgRequired());
-                }
-            } else {
-                if (packageName == null) {
-                    packageName = argument;
-                } else {
-                    System.err.println("Error: Unsupported argument: " + argument);
-                    return;
-                }
-            }
-        }
-
-        // Parsing complete, let's execute the command.
-
-        if (userId == UserHandle.USER_CURRENT) {
-            userId = ActivityManager.getCurrentUser();
-        }
-
-        final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
-        appOpsService.resetAllModes(userId, packageName);
-        System.out.print("Reset all modes for: ");
-        if (userId == UserHandle.USER_ALL) {
-            System.out.print("all users");
-        } else {
-            System.out.print("user "); System.out.print(userId);
-        }
-        System.out.print(", ");
-        if (packageName == null) {
-            System.out.println("all packages");
-        } else {
-            System.out.print("package "); System.out.println(packageName);
-        }
-    }
-}
diff --git a/core/java/android/app/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/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/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 84ddd9f..c1d5b19 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -47,11 +47,11 @@
     void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
     boolean areNotificationsEnabledForPackage(String pkg, int uid);
 
-    void setPackagePriority(String pkg, int uid, int priority);
-    int getPackagePriority(String pkg, int uid);
-
-    void setPackageVisibilityOverride(String pkg, int uid, int visibility);
-    int getPackageVisibilityOverride(String pkg, int uid);
+    ParceledListSlice getTopics(String pkg, int uid);
+    void setTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic, int visibility);
+    int getTopicVisibilityOverride(String pkg, int uid, in Notification.Topic topic);
+    void setTopicPriority(String pkg, int uid, in Notification.Topic topic, int priority);
+    int getTopicPriority(String pkg, int uid, in Notification.Topic topic);
 
     // TODO: Remove this when callers have been migrated to the equivalent
     // INotificationListener method.
diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl
index 9d8129c..3f1d113 100644
--- a/core/java/android/app/Notification.aidl
+++ b/core/java/android/app/Notification.aidl
@@ -17,3 +17,4 @@
 package android.app;
 
 parcelable Notification;
+parcelable Notification.Topic;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4e6548b..848b33f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -1433,6 +1434,9 @@
                 };
     }
 
+    @SystemApi
+    public static final String TOPIC_DEFAULT = "system_default_topic";
+
     private Topic topic;
 
     public Topic getTopic() {
@@ -3419,6 +3423,10 @@
                 mN.extras.putStringArray(EXTRA_PEOPLE,
                         mPersonList.toArray(new String[mPersonList.size()]));
             }
+            if (mN.topic == null) {
+                mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
+                        R.string.default_notification_topic_label));
+            }
             return mN;
         }
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index faed7a0..89e974e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.net.ProxyInfo;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.os.Process;
@@ -130,6 +131,7 @@
      * As of {@link android.os.Build.VERSION_CODES#M}, it should contain the extra
      * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME} instead, although specifying only
      * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} is still supported.
+     * This intent may also contain the extra {@link #EXTRA_PROVISIONING_LOGO_URI}.
      *
      * <p> When managed provisioning has completed, broadcasts are sent to the application specified
      * in the provisioning intent. The
@@ -196,6 +198,7 @@
      * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED}, optional</li>
      * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+     * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li>
      * </ul>
      *
      * <p> When device owner provisioning has completed, an intent of the type
@@ -573,6 +576,28 @@
              "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
 
     /**
+     * A {@link Uri} extra pointing to a logo image. This image will be shown during the
+     * provisioning. If this extra is not passed, a default image will be shown.
+     * <h5>The following URI schemes are accepted:</h5>
+     * <ul>
+     * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
+     * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
+     * </ul>
+     *
+     * <p> It is the responsability of the caller to provide an image with a reasonable
+     * pixed density for the device.
+     *
+     * <p> If a content: URI is passed, the intent should have the flag
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and the uri should be added to the
+     * {@link android.content.ClipData} of the intent too.
+     *
+     * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_PROFILE} or
+     * {@link #ACTION_PROVISION_MANAGED_DEVICE}
+     */
+    public static final String EXTRA_PROVISIONING_LOGO_URI =
+            "android.app.extra.PROVISIONING_LOGO_URI";
+
+    /**
      * This MIME type is used for starting the Device Owner provisioning.
      *
      * <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -3591,6 +3616,48 @@
     }
 
     /**
+     * Called by a device owner to get the list of apps to keep around as APKs even if no user has
+     * currently installed it.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     *
+     * @return List of package names to keep cached.
+     * @hide
+     */
+    public List<String> getKeepUninstalledPackages(@NonNull ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getKeepUninstalledPackages(admin);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Called by a device owner to set a list of apps to keep around as APKs even if no user has
+     * currently installed it.
+     *
+     * <p>Please note that setting this policy does not imply that specified apps will be
+     * automatically pre-cached.</p>
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param packageNames List of package names to keep cached.
+     * @hide
+     */
+    public void setKeepUninstalledPackages(@NonNull ComponentName admin,
+            @NonNull List<String> packageNames) {
+        if (mService != null) {
+            try {
+                mService.setKeepUninstalledPackages(admin, packageNames);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed talking with device policy service", e);
+            }
+        }
+    }
+
+    /**
      * Called by a device owner to create a user with the specified name. The UserHandle returned
      * by this method should not be persisted as user handles are recycled as users are removed and
      * created. If you need to persist an identifier for this user, use
@@ -3763,12 +3830,18 @@
      * {@link UserManager#getUserRestrictions()}.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @throws SecurityException if the {@code admin} is not an active admin.
      */
     public Bundle getUserRestrictions(@NonNull ComponentName admin) {
+        return getUserRestrictions(admin, myUserId());
+    }
+
+    /** @hide per-user version */
+    public Bundle getUserRestrictions(@NonNull ComponentName admin, int userHandle) {
         Bundle ret = null;
         if (mService != null) {
             try {
-                ret = mService.getUserRestrictions(admin);
+                ret = mService.getUserRestrictions(admin, userHandle);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed talking with device policy service", e);
             }
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 4270e16..0a0d77d 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -16,8 +16,6 @@
 
 package android.app.admin;
 
-import android.os.Bundle;
-
 import java.util.List;
 
 /**
@@ -71,13 +69,4 @@
      * @return true if the uid is an active admin with the given policy.
      */
     public abstract boolean isActiveAdminWithPolicy(int uid, int reqPolicy);
-
-    /**
-     * Takes a {@link Bundle} containing "base" user restrictions stored in
-     * {@link com.android.server.pm.UserManagerService}, mixes restrictions set by the device owner
-     * and the profile owner and returns the merged restrictions.
-     *
-     * This method always returns a new {@link Bundle}.
-     */
-    public abstract Bundle getComposedUserRestrictions(int userId, Bundle inBundle);
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 7601cf2..c43fa9a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -149,7 +149,7 @@
     ComponentName getRestrictionsProvider(int userHandle);
 
     void setUserRestriction(in ComponentName who, in String key, boolean enable);
-    Bundle getUserRestrictions(in ComponentName who);
+    Bundle getUserRestrictions(in ComponentName who, int userId);
     void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
     void clearCrossProfileIntentFilters(in ComponentName admin);
 
@@ -231,4 +231,6 @@
             String permission, int grantState);
     int getPermissionGrantState(in ComponentName admin, String packageName, String permission);
     boolean isProvisioningAllowed(String action);
+    void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
+    List<String> getKeepUninstalledPackages(in ComponentName admin);
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30fe531..4a7cbc7 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.
@@ -2926,8 +2956,8 @@
      * multiple selection), then you can specify {@link #EXTRA_ALLOW_MULTIPLE}
      * to indicate this.
      * <p>
-     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
-     * returned URIs can be opened with
+     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+     * URIs that can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
      * Output: The URI of the item that was picked, returned in
@@ -2962,8 +2992,8 @@
      * Callers can provide an initial display name through {@link #EXTRA_TITLE},
      * but the user may change this value before creating the file.
      * <p>
-     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent so that
-     * returned URIs can be opened with
+     * Callers must include {@link #CATEGORY_OPENABLE} in the Intent to obtain
+     * URIs that can be opened with
      * {@link ContentResolver#openFileDescriptor(Uri, String)}.
      * <p>
      * Output: The URI of the item that was created. This must be a
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 1fbef7a..e7dc764 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import com.android.internal.util.HexDump;
+
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -118,7 +120,7 @@
         final int N = mDigest.length;
         for (int i = 0; i < N; i++) {
             final byte b = mDigest[i];
-            IntegralToString.appendByteAsHex(sb, b, false);
+            HexDump.appendByteAsHex(sb, b, false);
             sb.append(',');
         }
         sb.append('}');
@@ -142,4 +144,4 @@
         }
     };
 
-}
\ No newline at end of file
+}
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index bf70d6c..905ac5e 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -16,7 +16,7 @@
 
 package android.content.pm;
 
-import android.annotation.NonNull;
+import java.util.List;
 
 /**
  * Package manager local system service interface.
@@ -115,4 +115,11 @@
      */
     public abstract void grantDefaultPermissionsToDefaultSimCallManager(String packageName,
             int userId);
+
+    /**
+     * Sets a list of apps to keep in PM's internal data structures and as APKs even if no user has
+     * currently installed it. The apps are not preloaded.
+     * @param packageList List of package names to keep cached.
+     */
+    public abstract void setKeepUninstalledPackages(List<String> packageList);
 }
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 5b60c0d..2715af0 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -16,6 +16,8 @@
 
 package android.net.http;
 
+import com.android.internal.util.HexDump;
+
 import android.content.Context;
 import android.os.Bundle;
 import android.text.format.DateFormat;
@@ -285,7 +287,7 @@
         StringBuilder sb = new StringBuilder();
         for (int i = 0; i < bytes.length; i++) {
             byte b = bytes[i];
-            IntegralToString.appendByteAsHex(sb, b, true);
+            HexDump.appendByteAsHex(sb, b, true);
             if (i+1 != bytes.length) {
                 sb.append(':');
             }
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index 9178ec6..26c7a1e 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -15,6 +15,9 @@
  */
 package android.os;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * @hide Only for use within the system server.
  */
@@ -31,32 +34,18 @@
     }
 
     /**
-     * Lock that must be held when calling certain methods in this class.
+     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
+     * to set per-user as well as global user restrictions.
      *
-     * This is used to avoid dead lock between
-     * {@link com.android.server.pm.UserManagerService} and
-     * {@link com.android.server.devicepolicy.DevicePolicyManagerService}.  This lock should not
-     * be newly taken while holding the DPMS lock, which would cause a dead lock.  Take this
-     * lock first before taking the DPMS lock to avoid that.
+     * @param userId target user id for the local restrictions.
+     * @param localRestrictions per-user restrictions.
+     *     Caller must not change it once passed to this method.
+     * @param globalRestrictions global restrictions set by DO.  Must be null when PO changed user
+     *     restrictions, in which case global restrictions won't change.
+     *     Caller must not change it once passed to this method.
      */
-    public abstract Object getUserRestrictionsLock();
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsLR(int userId);
-
-    /**
-     * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to get
-     * {@link com.android.server.pm.UserManagerService} to update effective user restrictions.
-     *
-     * Must be called while taking the {@link #getUserRestrictionsLock()} lock.
-     */
-    public abstract void updateEffectiveUserRestrictionsForAllUsersLR();
-
+    public abstract void setDevicePolicyUserRestrictions(int userId,
+            @NonNull Bundle localRestrictions, @Nullable Bundle globalRestrictions);
     /**
      * Returns the "base" user restrictions.
      *
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index e3694e8..b860bfb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -394,6 +394,14 @@
                 Uri.withAppendedPath(AUTHORITY_URI, "directories");
 
         /**
+         * The content:// style URI for enterprise Directory table. Requests to this URI can be
+         * performed on the UI thread because they are always unblocking.
+         *
+         */
+        public static final Uri CORP_CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+                "directories_corp");
+
+        /**
          * The MIME-type of {@link #CONTENT_URI} providing a directory of
          * contact directories.
          */
@@ -417,6 +425,22 @@
         public static final long LOCAL_INVISIBLE = 1;
 
         /**
+         * _ID of the work profile default directory, which represents locally stored contacts.
+         *
+         * @hide
+         */
+        public static final long ENTERPRISE_DEFAULT = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+                + DEFAULT;
+
+        /**
+         * _ID of the work profile directory that represents locally stored invisible contacts.
+         *
+         * @hide
+         */
+        public static final long ENTERPRISE_LOCAL_INVISIBLE = Directory.ENTERPRISE_DIRECTORY_ID_BASE
+                + LOCAL_INVISIBLE;
+
+        /**
          * The name of the package that owns this directory. Contacts Provider
          * fill it in with the name of the package containing the directory provider.
          * If the package is later uninstalled, the directories it owns are
@@ -472,6 +496,15 @@
         public static final String ACCOUNT_NAME = "accountName";
 
         /**
+         * Mimimal ID for corp directory returned from
+         * {@link Directory#CORP_CONTENT_URI}.
+         *
+         * @hide
+         */
+        // slightly smaller than 2 ** 30
+        public static final long ENTERPRISE_DIRECTORY_ID_BASE = 1000000000;
+
+        /**
          * One of {@link #EXPORT_SUPPORT_NONE}, {@link #EXPORT_SUPPORT_ANY_ACCOUNT},
          * {@link #EXPORT_SUPPORT_SAME_ACCOUNT_ONLY}. This is the expectation the
          * directory has for data exported from it.  Clients must obey this setting.
@@ -555,6 +588,24 @@
         public static final int PHOTO_SUPPORT_FULL = 3;
 
         /**
+         * Return TRUE if it is a remote stored directory.
+         */
+        public static boolean isRemoteDirectory(long directoryId) {
+            return directoryId != Directory.DEFAULT
+                    && directoryId != Directory.LOCAL_INVISIBLE
+                    && directoryId != Directory.ENTERPRISE_DEFAULT
+                    && directoryId != Directory.ENTERPRISE_LOCAL_INVISIBLE;
+        }
+
+        /**
+         * Return TRUE if a directory ID is from the contacts provider on the enterprise profile.
+         *
+         */
+        public static boolean isEnterpriseDirectoryId(long directoryId) {
+            return directoryId >= ENTERPRISE_DIRECTORY_ID_BASE;
+        }
+
+        /**
          * Notifies the system of a change in the list of directories handled by
          * a particular directory provider. The Contacts provider will turn around
          * and send a query to the directory provider for the full list of directories,
@@ -1589,6 +1640,14 @@
                 CONTENT_URI, "filter");
 
         /**
+         * It supports the same semantics as {@link #CONTENT_FILTER_URI} and returns the same
+         * columns. If there is a corp profile linked to the current profile, it will query corp
+         * profile, otherwise it will return null.
+         */
+        public static final Uri CORP_CONTENT_FILTER_URI = Uri.withAppendedPath(
+                CORP_CONTENT_URI, "filter");
+
+        /**
          * The content:// style URI for this table joined with useful data from
          * {@link ContactsContract.Data}, filtered to include only starred contacts
          * and the most frequently contacted contacts.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index af7f472..8ae899f 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -233,6 +233,7 @@
          * @see #FLAG_SUPPORTS_TYPED_DOCUMENT
          * @see #FLAG_DIR_PREFERS_GRID
          * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
+         * @see #FLAG_VIRTUAL_DOCUMENT
          */
         public static final String COLUMN_FLAGS = "flags";
 
@@ -356,6 +357,17 @@
         public static final int FLAG_SUPPORTS_TYPED_DOCUMENT = 1 << 9;
 
         /**
+         * Flag indicating that a document is virtual, and doesn't have byte
+         * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}.
+         *
+         * @see #COLUMN_FLAGS
+         * @see #COLUMN_MIME_TYPE
+         * @see DocumentsProvider#openTypedDocument(String, String, Bundle,
+         *      android.os.CancellationSignal)
+         */
+        public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 10;
+
+        /**
          * Flag indicating that document titles should be hidden when viewing
          * this directory in a larger format grid. For example, a directory
          * containing only images may want the image thumbnails to speak for
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9034cc9..f560f8a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7758,6 +7758,7 @@
             AUTO_TIME_ZONE,
             POWER_SOUNDS_ENABLED,
             DOCK_SOUNDS_ENABLED,
+            CHARGING_SOUNDS_ENABLED,
             USB_MASS_STORAGE_ENABLED,
             ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
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/LocaleList.java b/core/java/android/util/LocaleList.java
index 0d5c135..c1d23bb 100644
--- a/core/java/android/util/LocaleList.java
+++ b/core/java/android/util/LocaleList.java
@@ -49,6 +49,7 @@
         return location < mList.length ? mList[location] : null;
     }
 
+    @Nullable
     public Locale getPrimary() {
         return mList.length == 0 ? null : get(0);
     }
@@ -179,6 +180,12 @@
         }
     }
 
+    @Nullable
+    public Locale getBestMatch(String[] locales) {
+        // TODO: Fix this to actually do locale negotiation and choose the best match
+        return getPrimary();
+    }
+
     private final static Object sLock = new Object();
 
     @GuardedBy("sLock")
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index f81b5d0..b3cd8c11 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,9 +186,9 @@
 	void reportDropResult(IWindow window, boolean consumed);
 
     /**
-     * Cancel a drag operation.
+     * Cancel the current drag operation.
      */
-    void cancelDrag(IBinder dragToken);
+    void cancelDragAndDrop(IBinder dragToken);
 
     /**
      * Tell the OS that we've just dragged into a View that is willing to accept the drop
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 30408c6..66381f9 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.
      *
@@ -3679,7 +3696,7 @@
 
     /**
      * Flag indicating that a drag can cross window boundaries.  When
-     * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, all visible applications will be able to participate
      * in the drag operation and receive the dragged content.
      *
@@ -3724,7 +3741,7 @@
 
     /**
      * Flag indicating that the drag shadow will be opaque.  When
-     * {@link #startDrag(ClipData, DragShadowBuilder, Object, int)} is called
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, the drag shadow will be opaque, otherwise, it will be semitransparent.
      */
     public static final int DRAG_FLAG_OPAQUE = 1 << 9;
@@ -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
@@ -19845,6 +19910,15 @@
     }
 
     /**
+     * @deprecated Use {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)
+     * startDragAndDrop()} for newer platform versions.
+     */
+    public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+                                   Object myLocalState, int flags) {
+        return startDragAndDrop(data, shadowBuilder, myLocalState, flags);
+    }
+
+    /**
      * Starts a drag and drop operation. When your application calls this method, it passes a
      * {@link android.view.View.DragShadowBuilder} object to the system. The
      * system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
@@ -19861,9 +19935,10 @@
      *  {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
      * </p>
      * <p>
-     * Your application can invoke startDrag() on any attached View object. The View object does not
-     * need to be the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to
-     * be related to the View the user selected for dragging.
+     * Your application can invoke {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object,
+     * int) startDragAndDrop()} on any attached View object. The View object does not need to be
+     * the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to be related
+     * to the View the user selected for dragging.
      * </p>
      * @param data A {@link android.content.ClipData} object pointing to the data to be
      * transferred by the drag and drop operation.
@@ -19883,10 +19958,10 @@
      * {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
      * do a drag, and so no drag operation is in progress.
      */
-    public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
+    public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
             Object myLocalState, int flags) {
         if (ViewDebug.DEBUG_DRAG) {
-            Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " flags=" + flags);
+            Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
         }
         boolean okay = false;
 
@@ -19943,13 +20018,30 @@
         return okay;
     }
 
-    public final void cancelDrag() {
+    /**
+     * Cancels an ongoing drag and drop operation.
+     * <p>
+     * A {@link android.view.DragEvent} object with
+     * {@link android.view.DragEvent#getAction()} value of
+     * {@link android.view.DragEvent#ACTION_DRAG_ENDED} and
+     * {@link android.view.DragEvent#getResult()} value of {@code false}
+     * will be sent to every
+     * View that received {@link android.view.DragEvent#ACTION_DRAG_STARTED}
+     * even if they are not currently visible.
+     * </p>
+     * <p>
+     * This method can be called on any View in the same window as the View on which
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int) startDragAndDrop}
+     * was called.
+     * </p>
+     */
+    public final void cancelDragAndDrop() {
         if (ViewDebug.DEBUG_DRAG) {
-            Log.d(VIEW_LOG_TAG, "cancelDrag");
+            Log.d(VIEW_LOG_TAG, "cancelDragAndDrop");
         }
         if (mAttachInfo.mDragToken != null) {
             try {
-                mAttachInfo.mSession.cancelDrag(mAttachInfo.mDragToken);
+                mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken);
             } catch (Exception e) {
                 Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
             }
@@ -19981,7 +20073,8 @@
 
     /**
      * Handles drag events sent by the system following a call to
-     * {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}.
+     * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
+     * startDragAndDrop()}.
      *<p>
      * When the system calls this method, it passes a
      * {@link android.view.DragEvent} object. A call to
@@ -21867,6 +21960,7 @@
         interface Callbacks {
             void playSoundEffect(int effectId);
             boolean performHapticFeedback(int effectId, boolean always);
+            void schedulePartialLayout();
         }
 
         /**
@@ -22237,6 +22331,12 @@
         View mViewRequestingLayout;
 
         /**
+         * Used to track views that need (at least) a partial relayout at their current size
+         * during the next traversal.
+         */
+        final List<View> mPartialLayoutViews = new ArrayList<View>();
+
+        /**
          * Used to track the identity of the current drag operation.
          */
         IBinder mDragToken;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 25df004..11df9a3 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>
@@ -222,7 +224,7 @@
      * NOTE: If you change the flags below make sure to reflect the changes
      *       the DisplayList class
      */
-    
+
     // When set, ViewGroup invalidates only the child's rectangle
     // Set by default
     static final int FLAG_CLIP_CHILDREN = 0x1;
@@ -267,7 +269,7 @@
     /**
      * When set, the drawing method will call {@link #getChildDrawingOrder(int, int)}
      * to get the index of the child to draw for that iteration.
-     * 
+     *
      * @hide
      */
     protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
@@ -1325,7 +1327,7 @@
             children[i].dispatchConfigurationChanged(newConfig);
         }
     }
-    
+
     /**
      * {@inheritDoc}
      */
@@ -2212,7 +2214,7 @@
                         final float y = ev.getY(actionIndex);
                         // Find a child that can receive the event.
                         // Scan children from front to back.
-                        final ArrayList<View> preorderedList = buildOrderedChildList();
+                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                         final boolean customOrder = preorderedList == null
                                 && isChildrenDrawingOrderEnabled();
                         final View[] children = mChildren;
@@ -2345,6 +2347,18 @@
     }
 
     /**
+     * Provide custom ordering of views in which the touch will be dispatched.
+     *
+     * This is called within a tight loop, so you are not allowed to allocate objects, including
+     * the return array. Instead, you should return a pre-allocated list that will be cleared
+     * after the dispatch is finished.
+     * @hide
+     */
+    public ArrayList<View> buildTouchDispatchChildList() {
+        return buildOrderedChildList();
+    }
+
+    /**
      * Finds the child which has accessibility focus.
      *
      * @return The child that has focus.
@@ -2785,7 +2799,7 @@
      * @see #FOCUS_BEFORE_DESCENDANTS
      * @see #FOCUS_AFTER_DESCENDANTS
      * @see #FOCUS_BLOCK_DESCENDANTS
-     * @see #onRequestFocusInDescendants(int, android.graphics.Rect) 
+     * @see #onRequestFocusInDescendants(int, android.graphics.Rect)
      */
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
@@ -4102,7 +4116,7 @@
     /**
      * <p>Adds a child view. If no layout parameters are already set on the child, the
      * default parameters for this ViewGroup are set on the child.</p>
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4118,7 +4132,7 @@
     /**
      * Adds a child view. If no layout parameters are already set on the child, the
      * default parameters for this ViewGroup are set on the child.
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4558,7 +4572,7 @@
 
     /**
      * {@inheritDoc}
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -4577,7 +4591,7 @@
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
-     * 
+     *
      * @param view the view to remove from the group
      */
     public void removeViewInLayout(View view) {
@@ -4605,7 +4619,7 @@
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
-     * 
+     *
      * @param index the position in the group of the view to remove
      */
     public void removeViewAt(int index) {
@@ -4794,7 +4808,7 @@
     /**
      * Call this method to remove all child views from the
      * ViewGroup.
-     * 
+     *
      * <p><strong>Note:</strong> do not invoke this method from
      * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
      * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
@@ -5517,6 +5531,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 +6042,7 @@
      *         of its descendants
      */
     protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
-        return p;
+        return new LayoutParams(p);
     }
 
     /**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 07f1e2c..6ae448a 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -26,6 +26,11 @@
  * 
  */
 public interface ViewParent {
+    public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1;
+    public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2;
+    public static final int FLAG_LAYOUT_AXIS_ANY
+            = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL;
+
     /**
      * Called when something has changed which has invalidated the layout of a
      * child of this view parent. This will schedule a layout pass of the view
@@ -601,4 +606,48 @@
      * @return true if the action was consumed by this ViewParent
      */
     public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+    /**
+     * Called when a child of this ViewParent requires a relayout before
+     * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()}
+     * will implicitly result in a call to
+     * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this
+     * method directly. Call <code>child.requestLayout()</code> instead.
+     *
+     * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()}
+     * would cause a matching call to <code>requestLayout</code> on each parent view up to
+     * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may
+     * explicitly decide how to handle a layout request. This allows for optimizations when
+     * a view parent knows that a layout-altering change in a child will not affect its own
+     * measurement.</p>
+     *
+     * @param child Child requesting a layout
+     */
+    public void requestLayoutForChild(View child);
+
+    /**
+     * Determine which axes of this ViewParent's layout are dependent on the given
+     * direct child view. The returned value is a flag set that may contain
+     * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}.
+     * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for
+     * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>.
+     *
+     * <p>The given child must be a direct child view. Implementations should throw
+     * {@link IllegalArgumentException} otherwise.</p>
+     *
+     * <p>The caller may specify which axes it cares about. This should be treated as a filter.
+     * Implementations should never return a result that would be different from
+     * <code>result & axisFilter</code>.</p>
+     *
+     * @param child Direct child of this ViewParent to check
+     * @param axisFilter Which axes to check for dependencies. Can be
+     *                   {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+     *                   or {@link #FLAG_LAYOUT_AXIS_ANY}.
+     * @return Axes of this ViewParent that depend on the given child's layout changes
+     *
+     * @see #FLAG_LAYOUT_AXIS_HORIZONTAL
+     * @see #FLAG_LAYOUT_AXIS_VERTICAL
+     * @see #FLAG_LAYOUT_AXIS_ANY
+     */
+    public int findDependentLayoutAxes(View child, int axisFilter);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f1d9f1ab..2c0cd7a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -91,6 +91,7 @@
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -952,6 +953,25 @@
     }
 
     @Override
+    public void requestLayoutForChild(View child) {
+        requestLayout();
+    }
+
+    @Override
+    public int findDependentLayoutAxes(View child, int axisFilter) {
+        if (child != mView) {
+            return 0;
+        }
+
+        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams();
+        final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0;
+        final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT
+                || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0;
+        return (horizontal | vertical) & axisFilter;
+    }
+
+    @Override
     public boolean isLayoutRequested() {
         return mLayoutRequested;
     }
@@ -1095,6 +1115,10 @@
         }
     }
 
+    public void schedulePartialLayout() {
+        scheduleTraversals();
+    }
+
     /**
      * Notifies the HardwareRenderer that a new frame will be coming soon.
      * Currently only {@link ThreadedRenderer} cares about this, and uses
@@ -1934,7 +1958,48 @@
                 || mAttachInfo.mRecomputeGlobalAttributes;
         if (didLayout) {
             performLayout(lp, desiredWindowWidth, desiredWindowHeight);
+        }
 
+        /*
+         * Handle partial layouts.
+         *
+         * Views that have requested partial layouts will not change size or position
+         * within their parent view, therefore we will re-measure and re-layout each one
+         * after any regularly scheduled layout pass. Any view that already had its
+         * isLayoutRequested bit cleared will be skipped, since this means the view has already
+         * been measured and laid out on this traversal pass naturally. Views won't be added
+         * to this list if layout was already requested when a partial layout is requested
+         * for a view, so there should not be duplicates in the list.
+         */
+        final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+        final boolean didPartialLayout;
+        if (!partialLayoutViews.isEmpty()) {
+            final int count = partialLayoutViews.size();
+            mInLayout = true;
+            for (int i = 0; i < count; i++) {
+                final View view = partialLayoutViews.get(i);
+
+                // Make sure the view is still attached and that it still has layout requested.
+                // We might have already serviced the layout request through the standard full-tree
+                // layout pass above or even through a previous partial layout view in this list.
+                if (view.isAttachedToWindow() && view.isLayoutRequested()) {
+                    final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
+                            MeasureSpec.EXACTLY);
+                    final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
+                            MeasureSpec.EXACTLY);
+                    view.measure(widthSpec, heightSpec);
+                    view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+                }
+            }
+            mInLayout = false;
+            partialLayoutViews.clear();
+            didPartialLayout = true;
+            triggerGlobalLayoutListener = true;
+        } else {
+            didPartialLayout = false;
+        }
+
+        if (didLayout || didPartialLayout) {
             // By this point all views have been sized and positioned
             // We can compute the transparent area
 
@@ -1964,7 +2029,7 @@
 
             if (DBG) {
                 System.out.println("======================================");
-                System.out.println("performTraversals -- after setFrame");
+                System.out.println("performTraversals -- after performLayout/partial layout");
                 host.debug();
             }
         }
diff --git a/core/java/android/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/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/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 47df4e8..41f1ce7 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -937,10 +937,11 @@
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             mMenu.close();
             mOverflowPopup = null;
+
+            super.onDismiss();
         }
     }
 
@@ -959,10 +960,11 @@
         }
 
         @Override
-        public void onDismiss() {
-            super.onDismiss();
+        protected void onDismiss() {
             mActionButtonPopup = null;
             mOpenSubMenuId = 0;
+
+            super.onDismiss();
         }
     }
 
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/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index a53df88..027f874 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -19,7 +19,6 @@
 import com.android.internal.R;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPopupHelper;
-import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.ShowableListMenu;
 
 import android.annotation.MenuRes;
@@ -45,7 +44,7 @@
     private final MenuPopupHelper mPopup;
 
     private OnMenuItemClickListener mMenuItemClickListener;
-    private OnDismissListener mDismissListener;
+    private OnDismissListener mOnDismissListener;
     private OnTouchListener mDragListener;
 
     /**
@@ -114,20 +113,13 @@
 
         mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
         mPopup.setGravity(gravity);
-        mPopup.setCallback(new MenuPresenter.Callback() {
+        mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
             @Override
-            public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-                if (mDismissListener != null) {
-                    mDismissListener.onDismiss(PopupMenu.this);
+            public void onDismiss() {
+                if (mOnDismissListener != null) {
+                    mOnDismissListener.onDismiss(PopupMenu.this);
                 }
             }
-
-            @Override
-            public boolean onOpenSubMenu(MenuBuilder subMenu) {
-                // The menu presenter will handle opening the submenu itself.
-                // Nothing to do here.
-                return false;
-            }
         });
     }
 
@@ -259,7 +251,7 @@
      * @param listener the listener to notify
      */
     public void setOnDismissListener(OnDismissListener listener) {
-        mDismissListener = listener;
+        mOnDismissListener = listener;
     }
 
     /**
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/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index b523b84..a24fb40 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -84,6 +84,8 @@
     private final RadialTimePickerView mRadialTimePickerView;
     private final TextView mSeparatorView;
 
+    private final Calendar mTempCalendar;
+
     private boolean mIsEnabled = true;
     private boolean mAllowAutoAdvance;
     private int mInitialHourOfDay;
@@ -103,8 +105,6 @@
     private CharSequence mLastAnnouncedText;
     private boolean mLastAnnouncedIsHour;
 
-    private Calendar mTempCalendar;
-
     public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(delegator, context);
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 2ed230b..863d409 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -32,6 +32,7 @@
 import com.android.internal.R;
 
 import java.util.Calendar;
+import java.util.Locale;
 
 import libcore.icu.LocaleData;
 
@@ -45,11 +46,6 @@
     private static final boolean DEFAULT_ENABLED_STATE = true;
     private static final int HOURS_IN_HALF_DAY = 12;
 
-    // state
-    private boolean mIs24HourView;
-    private boolean mIsAm;
-
-    // ui components
     private final NumberPicker mHourSpinner;
     private final NumberPicker mMinuteSpinner;
     private final NumberPicker mAmPmSpinner;
@@ -66,11 +62,15 @@
 
     private final String[] mAmPmStrings;
 
+    private final Calendar mTempCalendar;
+
     private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-    private Calendar mTempCalendar;
     private boolean mHourWithTwoDigit;
     private char mHourFormat;
 
+    private boolean mIs24HourView;
+    private boolean mIsAm;
+
     public TimePickerSpinnerDelegate(TimePicker delegator, Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(delegator, context);
@@ -202,6 +202,7 @@
         updateAmPmControl();
 
         // set to current time
+        mTempCalendar = Calendar.getInstance(mLocale);
         setHour(mTempCalendar.get(Calendar.HOUR_OF_DAY));
         setMinute(mTempCalendar.get(Calendar.MINUTE));
 
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/app/SystemUserHomeActivity.java b/core/java/com/android/internal/app/SystemUserHomeActivity.java
new file mode 100644
index 0000000..26fbf6f
--- /dev/null
+++ b/core/java/com/android/internal/app/SystemUserHomeActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.Activity;
+
+/**
+ * Placeholder home activity, which is always installed on the system user. At least one home
+ * activity must be present and enabled in order for the system to boot.
+ */
+public class SystemUserHomeActivity extends Activity {
+}
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index 08c7935..c992c70 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -33,6 +33,7 @@
     public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
     public static final int ACTION_ZEN_ALLOW_PEEK = 261;
     public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
+    public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f73df00..9391c60 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -105,7 +105,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 135 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 136 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -1975,8 +1975,14 @@
 
     private int buildBatteryLevelInt(HistoryItem h) {
         return ((((int)h.batteryLevel)<<25)&0xfe000000)
-                | ((((int)h.batteryTemperature)<<14)&0x01ff8000)
-                | ((((int)h.batteryVoltage)<<1)&0x00007fff);
+                | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
+                | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
+    }
+
+    private void readBatteryLevelInt(int batteryLevelInt, HistoryItem out) {
+        out.batteryLevel = (byte)((batteryLevelInt & 0xfe000000) >>> 25);
+        out.batteryTemperature = (short)((batteryLevelInt & 0x01ff8000) >>> 15);
+        out.batteryVoltage = (char)((batteryLevelInt & 0x00007ffe) >>> 1);
     }
 
     private int buildStateInt(HistoryItem h) {
@@ -2117,9 +2123,7 @@
         final int batteryLevelInt;
         if ((firstToken&DELTA_BATTERY_LEVEL_FLAG) != 0) {
             batteryLevelInt = src.readInt();
-            cur.batteryLevel = (byte)((batteryLevelInt>>25)&0x7f);
-            cur.batteryTemperature = (short)((batteryLevelInt<<7)>>21);
-            cur.batteryVoltage = (char)(batteryLevelInt&0x3fff);
+            readBatteryLevelInt(batteryLevelInt, cur);
             cur.numReadInts += 1;
             if (DEBUG) Slog.i(TAG, "READ DELTA: batteryToken=0x"
                     + Integer.toHexString(batteryLevelInt)
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 4c221fc5..27fe03c 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1570,7 +1570,7 @@
                         new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
             }
             mDecorCaptionView.addView(root,
-                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
         } else {
             addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
         }
@@ -1672,9 +1672,7 @@
 
     void clearContentView() {
         if (mDecorCaptionView != null) {
-            if (mDecorCaptionView.getChildCount() > 1) {
-                mDecorCaptionView.removeViewAt(1);
-            }
+            mDecorCaptionView.removeContentView();
         } else {
             // This window doesn't have caption, so we need to just remove the
             // children of the decor view.
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 3c7b7ac..7be95d8 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -19,28 +19,29 @@
 public class HexDump
 {
     private final static char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
-    
+    private final static char[] HEX_LOWER_CASE_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
     public static String dumpHexString(byte[] array)
     {
         return dumpHexString(array, 0, array.length);
     }
-    
+
     public static String dumpHexString(byte[] array, int offset, int length)
     {
         StringBuilder result = new StringBuilder();
-        
+
         byte[] line = new byte[16];
         int lineIndex = 0;
-        
+
         result.append("\n0x");
         result.append(toHexString(offset));
-        
+
         for (int i = offset ; i < offset + length ; i++)
         {
             if (lineIndex == 16)
             {
                 result.append(" ");
-                
+
                 for (int j = 0 ; j < 16 ; j++)
                 {
                     if (line[j] > ' ' && line[j] < '~')
@@ -52,20 +53,20 @@
                         result.append(".");
                     }
                 }
-                
+
                 result.append("\n0x");
                 result.append(toHexString(i));
                 lineIndex = 0;
             }
-            
+
             byte b = array[i];
             result.append(" ");
             result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
             result.append(HEX_DIGITS[b & 0x0F]);
-            
+
             line[lineIndex++] = b;
         }
-        
+
         if (lineIndex != 16)
         {
             int count = (16 - lineIndex) * 3;
@@ -74,7 +75,7 @@
             {
                 result.append(" ");
             }
-            
+
             for (int i = 0 ; i < lineIndex ; i++)
             {
                 if (line[i] > ' ' && line[i] < '~')
@@ -87,10 +88,10 @@
                 }
             }
         }
-        
+
         return result.toString();
     }
-    
+
     public static String toHexString(byte b)
     {
         return toHexString(toByteArray(b));
@@ -98,48 +99,59 @@
 
     public static String toHexString(byte[] array)
     {
-        return toHexString(array, 0, array.length);
+        return toHexString(array, 0, array.length, true);
     }
-    
+
+    public static String toHexString(byte[] array, boolean upperCase)
+    {
+        return toHexString(array, 0, array.length, upperCase);
+    }
+
     public static String toHexString(byte[] array, int offset, int length)
     {
+        return toHexString(array, offset, length, true);
+    }
+
+    public static String toHexString(byte[] array, int offset, int length, boolean upperCase)
+    {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
         char[] buf = new char[length * 2];
 
         int bufIndex = 0;
-        for (int i = offset ; i < offset + length; i++) 
+        for (int i = offset ; i < offset + length; i++)
         {
             byte b = array[i];
-            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
-            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
+            buf[bufIndex++] = digits[(b >>> 4) & 0x0F];
+            buf[bufIndex++] = digits[b & 0x0F];
         }
 
-        return new String(buf);        
+        return new String(buf);
     }
-    
+
     public static String toHexString(int i)
     {
         return toHexString(toByteArray(i));
     }
-    
+
     public static byte[] toByteArray(byte b)
     {
         byte[] array = new byte[1];
         array[0] = b;
         return array;
     }
-    
+
     public static byte[] toByteArray(int i)
     {
         byte[] array = new byte[4];
-        
+
         array[3] = (byte)(i & 0xFF);
         array[2] = (byte)((i >> 8) & 0xFF);
         array[1] = (byte)((i >> 16) & 0xFF);
         array[0] = (byte)((i >> 24) & 0xFF);
-        
+
         return array;
     }
-    
+
     private static int toByte(char c)
     {
         if (c >= '0' && c <= '9') return (c - '0');
@@ -148,7 +160,7 @@
 
         throw new RuntimeException ("Invalid hex char '" + c + "'");
     }
-    
+
     public static byte[] hexStringToByteArray(String hexString)
     {
         int length = hexString.length();
@@ -158,7 +170,15 @@
         {
             buffer[i / 2] = (byte)((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i+1)));
         }
-        
+
         return buffer;
-    }    
+    }
+
+    public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+        char[] digits = upperCase ? HEX_DIGITS : HEX_LOWER_CASE_DIGITS;
+        sb.append(digits[(b >> 4) & 0xf]);
+        sb.append(digits[b & 0xf]);
+        return sb;
+    }
+
 }
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index e674ecc..59d5f94 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -18,82 +18,104 @@
 
 import com.android.internal.view.menu.MenuPresenter.Callback;
 
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
 import android.content.Context;
 import android.view.Gravity;
 import android.view.View;
-import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
 
 /**
  * Presents a menu as a small, simple popup anchored to another view.
- *
- * @hide
  */
-public class MenuPopupHelper implements PopupWindow.OnDismissListener {
+public class MenuPopupHelper {
     private final Context mContext;
+
+    // Immutable cached popup menu properties.
     private final MenuBuilder mMenu;
     private final boolean mOverflowOnly;
     private final int mPopupStyleAttr;
     private final int mPopupStyleRes;
 
+    // Mutable cached popup menu properties.
     private View mAnchorView;
-    private MenuPopup mPopup;
-
-    private int mDropDownGravity = Gravity.NO_GRAVITY;
+    private int mDropDownGravity = Gravity.START;
     private boolean mForceShowIcon;
-    private boolean mShowTitle;
     private Callback mPresenterCallback;
-    private int mInitXOffset;
-    private int mInitYOffset;
 
-    public MenuPopupHelper(Context context, MenuBuilder menu) {
+    private MenuPopup mPopup;
+    private OnDismissListener mOnDismissListener;
+
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
         this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView) {
         this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView,
+            boolean overflowOnly, @AttrRes int popupStyleAttr) {
         this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
     }
 
-    public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
-            boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+            @StyleRes int popupStyleRes) {
         mContext = context;
         mMenu = menu;
+        mAnchorView = anchorView;
         mOverflowOnly = overflowOnly;
         mPopupStyleAttr = popupStyleAttr;
         mPopupStyleRes = popupStyleRes;
-        mAnchorView = anchorView;
-        mPopup = createMenuPopup();
     }
 
-    private MenuPopup createMenuPopup() {
-        if (mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableCascadingSubmenus)) {
-            return new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr, mPopupStyleRes,
-                    mOverflowOnly);
-        }
-        return new StandardMenuPopup(
-                mContext, mMenu, mAnchorView, mPopupStyleAttr, mPopupStyleRes, mOverflowOnly);
+    public void setOnDismissListener(@Nullable OnDismissListener listener) {
+        mOnDismissListener = listener;
     }
 
-    public void setAnchorView(View anchor) {
+    /**
+      * Sets the view to which the popup window is anchored.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param anchor the view to which the popup window should be anchored
+      */
+    public void setAnchorView(@NonNull View anchor) {
         mAnchorView = anchor;
-        mPopup.setAnchorView(anchor);
     }
 
-    public void setForceShowIcon(boolean forceShow) {
-        mForceShowIcon = forceShow;
-        mPopup.setForceShowIcon(forceShow);
+    /**
+     * Sets whether the popup menu's adapter is forced to show icons in the
+     * menu item views.
+     * <p>
+     * Changes take effect on the next call to show().
+     *
+     * @param forceShowIcon {@code true} to force icons to be shown, or
+     *                  {@code false} for icons to be optionally shown
+     */
+    public void setForceShowIcon(boolean forceShowIcon) {
+        mForceShowIcon = forceShowIcon;
     }
 
+    /**
+      * Sets the alignment of the popup window relative to the anchor view.
+      * <p>
+      * Changes take effect on the next call to show().
+      *
+      * @param gravity alignment of the popup relative to the anchor
+      */
     public void setGravity(int gravity) {
         mDropDownGravity = gravity;
-        mPopup.setGravity(gravity);
     }
 
+    /**
+     * @return alignment of the popup relative to the anchor
+     */
     public int getGravity() {
         return mDropDownGravity;
     }
@@ -110,7 +132,11 @@
         }
     }
 
-    public ShowableListMenu getPopup() {
+    @NonNull
+    public MenuPopup getPopup() {
+        if (mPopup == null) {
+            mPopup = createPopup();
+        }
         return mPopup;
     }
 
@@ -129,14 +155,28 @@
             return false;
         }
 
-        mInitXOffset = 0;
-        mInitYOffset = 0;
-        mShowTitle = false;
-
-        showPopup();
+        showPopup(0, 0, false, false);
         return true;
     }
 
+    /**
+     * Shows the popup menu and makes a best-effort to anchor it to the
+     * specified (x,y) coordinate relative to the anchor view.
+     * <p>
+     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+     * display the popup with its top-left corner at (x,y) relative to the
+     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+     * popup's top-right corner will be at (x,y).
+     * <p>
+     * If the popup cannot be displayed fully on-screen, this method will
+     * attempt to scroll the anchor view's ancestors and/or offset the popup
+     * such that it may be displayed fully on-screen.
+     *
+     * @param x x coordinate relative to the anchor view
+     * @param y y coordinate relative to the anchor view
+     * @return {@code true} if the popup was shown or was already showing prior
+     *         to calling this method, {@code false} otherwise
+     */
     public boolean tryShow(int x, int y) {
         if (isShowing()) {
             return true;
@@ -146,51 +186,104 @@
             return false;
         }
 
-        mInitXOffset = x;
-        mInitYOffset = y;
-        mShowTitle = true;
-
-        showPopup();
+        showPopup(x, y, true, true);
         return true;
     }
 
-    private void showPopup() {
-        mPopup = createMenuPopup();
-        mPopup.setAnchorView(mAnchorView);
-        mPopup.setCallback(mPresenterCallback);
-        mPopup.setForceShowIcon(mForceShowIcon);
-        mPopup.setGravity(mDropDownGravity);
-        mPopup.setHorizontalOffset(mInitXOffset);
-        mPopup.setShowTitle(mShowTitle);
-        mPopup.setVerticalOffset(mInitYOffset);
+    /**
+     * Creates the popup and assigns cached properties.
+     *
+     * @return an initialized popup
+     */
+    @NonNull
+    private MenuPopup createPopup() {
+        final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableCascadingSubmenus);
 
-        // In order for subclasses of MenuPopupHelper to satisfy the OnDismissedListener interface,
-        // we must set the listener to this outer Helper rather than to the inner MenuPopup.
-        // Not to worry -- the inner MenuPopup will call our own #onDismiss method after it's done
-        // its own handling.
-        mPopup.setOnDismissListener(this);
+        final MenuPopup popup;
+        if (enableCascadingSubmenus) {
+            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        } else {
+            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        }
 
-        mPopup.addMenu(mMenu);
-        mPopup.show();
+        // Assign immutable properties.
+        popup.addMenu(mMenu);
+        popup.setOnDismissListener(mInternalOnDismissListener);
+
+        // Assign mutable properties. These may be reassigned later.
+        popup.setAnchorView(mAnchorView);
+        popup.setCallback(mPresenterCallback);
+        popup.setForceShowIcon(mForceShowIcon);
+        popup.setGravity(mDropDownGravity);
+
+        return popup;
     }
 
+    private void showPopup(int xOffset, int yOffset, boolean resolveOffsets, boolean showTitle) {
+        if (resolveOffsets) {
+            // If the resolved drop-down gravity is RIGHT, the popup's right
+            // edge will be aligned with the anchor view. Adjust by the anchor
+            // width such that the top-right corner is at the X offset.
+            final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+                    mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+                xOffset -= mAnchorView.getWidth();
+            }
+        }
+
+        final MenuPopup popup = getPopup();
+        popup.setHorizontalOffset(xOffset);
+        popup.setVerticalOffset(yOffset);
+        popup.setShowTitle(showTitle);
+        popup.show();
+    }
+
+    /**
+     * Dismisses the popup, if showing.
+     */
     public void dismiss() {
         if (isShowing()) {
             mPopup.dismiss();
         }
     }
 
-    @Override
-    public void onDismiss() {
+    /**
+     * Called after the popup has been dismissed.
+     * <p>
+     * <strong>Note:</strong> Subclasses should call the super implementation
+     * last to ensure that any necessary tear down has occurred before the
+     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+     * is called.
+     */
+    protected void onDismiss() {
         mPopup = null;
+
+        if (mOnDismissListener != null) {
+            mOnDismissListener.onDismiss();
+        }
     }
 
     public boolean isShowing() {
         return mPopup != null && mPopup.isShowing();
     }
 
-    public void setCallback(MenuPresenter.Callback cb) {
+    public void setCallback(@Nullable MenuPresenter.Callback cb) {
         mPresenterCallback = cb;
-        mPopup.setCallback(cb);
+        if (mPopup != null) {
+            mPopup.setCallback(cb);
+        }
     }
+
+    /**
+     * Listener used to proxy dismiss callbacks to the helper's owner.
+     */
+    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
+        @Override
+        public void onDismiss() {
+            MenuPopupHelper.this.onDismiss();
+        }
+    };
 }
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/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 3b7bce4..a694aca 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -30,6 +30,10 @@
  * orientation when it can't fit its child views horizontally.
  */
 public class ButtonBarLayout extends LinearLayout {
+    // Whether to allow vertically stacked button bars. This is disabled for
+    // configurations with a small (e.g. less than 320dp) screen height. -->
+    private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
+
     /** Whether the current configuration allows stacking. */
     private boolean mAllowStacking;
 
@@ -38,8 +42,12 @@
     public ButtonBarLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        final boolean allowStackingDefault =
+                context.getResources().getConfiguration().screenHeightDp
+                        >= ALLOW_STACKING_MIN_HEIGHT_DP;
         final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
-        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+                allowStackingDefault);
         ta.recycle();
     }
 
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
index e22bd10..d747686 100644
--- a/core/java/com/android/internal/widget/DecorCaptionView.java
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -19,19 +19,24 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 
 import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.RemoteException;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View;
-import android.widget.LinearLayout;
+import android.view.ViewConfiguration;
 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;
 
+import java.util.ArrayList;
+
 /**
  * This class represents the special screen elements to control a window on freeform
  * environment.
@@ -39,8 +44,8 @@
  * <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
+ * </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):
@@ -49,9 +54,29 @@
  * <li>setSurfaceFormat</li>
  * <li>..</li>
  * </ul>
+ *
+ * Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to
+ * overlaying caption on the content and drawing.
+ *
+ * First, no matter where the content View gets added, it will always be the first child and the
+ * caption will be the second. This way the caption will always be drawn on top of the content when
+ * overlaying is enabled.
+ *
+ * Second, the touch dispatch is customized to handle overlaying. This is what happens when touch
+ * is dispatched on the caption area while overlaying it on content:
+ * <ul>
+ * <li>DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the
+ * down action is performed on top close or maximize buttons; the reason for that is we want these
+ * buttons to always work.</li>
+ * <li>The content View will receive the touch event. Mind that content is actually underneath the
+ * caption, so we need to introduce our own dispatch ordering. We achieve this by overriding
+ * {@link #buildTouchDispatchChildList()}.</li>
+ * <li>If the touch event is not consumed by the content View, it will go to the caption View
+ * and the dragging logic will be executed.</li>
+ * </ul>
  */
-public class DecorCaptionView extends LinearLayout
-        implements View.OnClickListener, View.OnTouchListener {
+public class DecorCaptionView extends ViewGroup implements View.OnTouchListener,
+        GestureDetector.OnGestureListener {
     private final static String TAG = "DecorCaptionView";
     private PhoneWindow mOwner = null;
     private boolean mShow = false;
@@ -62,28 +87,99 @@
     // True when the left mouse button got released while dragging.
     private boolean mLeftMouseButtonReleased;
 
+    private boolean mOverlayWithAppContent = false;
+
+    private View mCaption;
+    private View mContent;
+    private View mMaximize;
+    private View mClose;
+
+    // Fields for detecting drag events.
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private boolean mCheckForDragging;
+    private int mDragSlop;
+
+    // Fields for detecting and intercepting click events on close/maximize.
+    private ArrayList<View> mTouchDispatchList = new ArrayList<>(2);
+    // We use the gesture detector to detect clicks on close/maximize buttons and to be consistent
+    // with existing click detection.
+    private GestureDetector mGestureDetector;
+    private final Rect mCloseRect = new Rect();
+    private final Rect mMaximizeRect = new Rect();
+    private View mClickTarget;
+
     public DecorCaptionView(Context context) {
         super(context);
+        init(context);
     }
 
     public DecorCaptionView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        init(context);
     }
 
     public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mDragSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mGestureDetector = new GestureDetector(context, this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCaption = getChildAt(0);
     }
 
     public void setPhoneWindow(PhoneWindow owner, boolean show) {
         mOwner = owner;
         mShow = show;
+        mOverlayWithAppContent = owner.getOverlayDecorCaption();
+        if (mOverlayWithAppContent) {
+            // The caption is covering the content, so we make its background transparent to make
+            // the content visible.
+            mCaption.setBackgroundColor(Color.TRANSPARENT);
+        }
         updateCaptionVisibility();
         // By changing the outline provider to BOUNDS, the window can remove its
         // background without removing the shadow.
         mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+        mMaximize = findViewById(R.id.maximize_window);
+        mClose = findViewById(R.id.close_window);
+    }
 
-        findViewById(R.id.maximize_window).setOnClickListener(this);
-        findViewById(R.id.close_window).setOnClickListener(this);
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // If the user starts touch on the maximize/close buttons, we immediately intercept, so
+        // that these buttons are always clickable.
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            final int x = (int) ev.getX();
+            final int y = (int) ev.getY();
+            if (mMaximizeRect.contains(x, y)) {
+                mClickTarget = mMaximize;
+            }
+            if (mCloseRect.contains(x, y)) {
+                mClickTarget = mClose;
+            }
+        }
+        return mClickTarget != null;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mClickTarget != null) {
+            mGestureDetector.onTouchEvent(event);
+            final int action = event.getAction();
+            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+                mClickTarget = null;
+            }
+            return true;
+        }
+        return false;
     }
 
     @Override
@@ -91,25 +187,31 @@
         // 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.
+        final int x = (int) e.getX();
+        final int y = (int) e.getY();
         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());
+                // Checking for 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 (((e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+                        (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0))) {
+                    mCheckForDragging = true;
+                    mTouchDownX = x;
+                    mTouchDownY = y;
                 }
                 break;
 
             case MotionEvent.ACTION_MOVE:
-                if (mDragging && !mLeftMouseButtonReleased) {
+                if (!mDragging && mCheckForDragging && passedSlop(x, y)) {
+                    mCheckForDragging = false;
+                    mDragging = true;
+                    mLeftMouseButtonReleased = false;
+                    startMovingTask(e.getRawX(), e.getRawY());
+                } else 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
@@ -127,9 +229,25 @@
                 }
                 // Abort the ongoing dragging.
                 mDragging = false;
-                return true;
+                return !mCheckForDragging;
         }
-        return mDragging;
+        return mDragging || mCheckForDragging;
+    }
+
+    @Override
+    public ArrayList<View> buildTouchDispatchChildList() {
+        mTouchDispatchList.ensureCapacity(3);
+        if (mCaption != null) {
+            mTouchDispatchList.add(mCaption);
+        }
+        if (mContent != null) {
+            mTouchDispatchList.add(mContent);
+        }
+        return mTouchDispatchList;
+    }
+
+    private boolean passedSlop(int x, int y) {
+        return Math.abs(x - mTouchDownX) > mDragSlop || Math.abs(y - mTouchDownY) > mDragSlop;
     }
 
     /**
@@ -142,23 +260,66 @@
     }
 
     @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");
         }
-        super.addView(child, index, params);
+        // 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();
+            mMaximize.getHitRect(mMaximizeRect);
+            mClose.getHitRect(mCloseRect);
+        } else {
+            captionHeight = 0;
+            mMaximizeRect.setEmpty();
+            mCloseRect.setEmpty();
+        }
+
+        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.
@@ -175,9 +336,8 @@
     private void updateCaptionVisibility() {
         // Don't show the caption if the window has e.g. entered full screen.
         boolean invisible = isFillingScreen() || !mShow;
-        View caption = getChildAt(0);
-        caption.setVisibility(invisible ? GONE : VISIBLE);
-        caption.setOnTouchListener(this);
+        mCaption.setVisibility(invisible ? GONE : VISIBLE);
+        mCaption.setOnTouchListener(this);
     }
 
     /**
@@ -199,7 +359,73 @@
     }
 
     public int getCaptionHeight() {
-        final View caption = getChildAt(0);
-        return (caption != null) ? caption.getHeight() : 0;
+        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;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return false;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        if (mClickTarget == mMaximize) {
+            maximizeWindow();
+        } else if (mClickTarget == mClose) {
+            mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        return false;
     }
 }
diff --git a/core/jni/android/graphics/MinikinUtils.cpp b/core/jni/android/graphics/MinikinUtils.cpp
index 7fd288a..9b774b3 100644
--- a/core/jni/android/graphics/MinikinUtils.cpp
+++ b/core/jni/android/graphics/MinikinUtils.cpp
@@ -63,6 +63,11 @@
     layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint);
 }
 
+bool MinikinUtils::hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs) {
+    const TypefaceImpl* resolvedFace = TypefaceImpl_resolveDefault(typeface);
+    return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs);
+}
+
 float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) {
     switch (paint->getTextAlign()) {
         case Paint::kCenter_Align:
diff --git a/core/jni/android/graphics/MinikinUtils.h b/core/jni/android/graphics/MinikinUtils.h
index 1ee6245..5bf1eec 100644
--- a/core/jni/android/graphics/MinikinUtils.h
+++ b/core/jni/android/graphics/MinikinUtils.h
@@ -40,6 +40,8 @@
             TypefaceImpl* typeface, const uint16_t* buf, size_t start, size_t count,
             size_t bufSize);
 
+    static bool hasVariationSelector(TypefaceImpl* typeface, uint32_t codepoint, uint32_t vs);
+
     static float xOffsetForTextAlign(Paint* paint, const Layout& layout);
 
     static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path);
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index b50046f..9c11dd1 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -39,6 +39,7 @@
 
 #include <minikin/GraphemeBreak.h>
 #include <minikin/Measurement.h>
+#include <unicode/utf16.h>
 #include "MinikinSkia.h"
 #include "MinikinUtils.h"
 #include "Paint.h"
@@ -852,45 +853,44 @@
         return false;
     }
 
-    static jboolean hasGlyphVariation(const Paint* paint, TypefaceImpl* typeface, jint bidiFlags,
-            const jchar* chars, size_t size) {
-        // TODO: query font for whether character has variation selector; requires a corresponding
-        // function in Minikin.
-        return false;
-    }
-
     static jboolean hasGlyph(JNIEnv *env, jclass, jlong paintHandle, jlong typefaceHandle,
             jint bidiFlags, jstring string) {
         const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         TypefaceImpl* typeface = reinterpret_cast<TypefaceImpl*>(typefaceHandle);
         ScopedStringChars str(env, string);
 
-        /* start by rejecting variation selectors (not supported yet) */
+        /* Start by rejecting unsupported base code point and variation selector pairs. */
         size_t nChars = 0;
+        const uint32_t kStartOfString = 0xFFFFFFFF;
+        uint32_t prevCp = kStartOfString;
         for (size_t i = 0; i < str.size(); i++) {
-            jchar c = str[i];
-            if (0xDC00 <= c && c <= 0xDFFF) {
+            jchar cu = str[i];
+            uint32_t cp = cu;
+            if (U16_IS_TRAIL(cu)) {
                 // invalid UTF-16, unpaired trailing surrogate
                 return false;
-            } else if (0xD800 <= c && c <= 0xDBFF) {
+            } else if (U16_IS_LEAD(cu)) {
                 if (i + 1 == str.size()) {
                     // invalid UTF-16, unpaired leading surrogate at end of string
                     return false;
                 }
                 i++;
-                jchar c2 = str[i];
-                if (!(0xDC00 <= c2 && c2 <= 0xDFFF)) {
+                jchar cu2 = str[i];
+                if (!U16_IS_TRAIL(cu2)) {
                     // invalid UTF-16, unpaired leading surrogate
                     return false;
                 }
-                // UTF-16 encoding of range U+E0100..U+E01EF is DB40 DD00 .. DB40 DDEF
-                if (c == 0xDB40 && 0xDD00 <= c2 && c2 <= 0xDDEF) {
-                    return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
-                }
-            } else if (0xFE00 <= c && c <= 0xFE0F) {
-                return hasGlyphVariation(paint, typeface, bidiFlags, str.get(), str.size());
+                cp = U16_GET_SUPPLEMENTARY(cu, cu2);
+            }
+
+            if (prevCp != kStartOfString &&
+                ((0xFE00 <= cp && cp <= 0xFE0F) || (0xE0100 <= cp && cp <= 0xE01EF)) &&
+                !MinikinUtils::hasVariationSelector(typeface, prevCp, cp)) {
+                // No font has a glyph for the code point and variation selector pair.
+                return false;
             }
             nChars++;
+            prevCp = cp;
         }
         Layout layout;
         MinikinUtils::doLayout(&layout, paint, bidiFlags, typeface, str.get(), 0, str.size(),
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 3d96fab..e4e73a4 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -544,39 +544,6 @@
     float totalAdvance;
 };
 
-// Same values used by Skia
-#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
-#define kStdUnderline_Offset    (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void drawTextDecorations(Canvas* canvas, float x, float y, float length, const SkPaint& paint) {
-    uint32_t flags;
-    SkDrawFilter* drawFilter = canvas->getDrawFilter();
-    if (drawFilter) {
-        SkPaint paintCopy(paint);
-        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
-        flags = paintCopy.getFlags();
-    } else {
-        flags = paint.getFlags();
-    }
-    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
-        SkScalar left = x;
-        SkScalar right = x + length;
-        float textSize = paint.getTextSize();
-        float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
-        if (flags & SkPaint::kUnderlineText_Flag) {
-            SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
-            canvas->drawRect(left, top, right, bottom, paint);
-        }
-        if (flags & SkPaint::kStrikeThruText_Flag) {
-            SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
-            SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
-            canvas->drawRect(left, top, right, bottom, paint);
-        }
-    }
-}
-
 void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount,
              float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) {
     // minikin may modify the original paint
@@ -586,8 +553,8 @@
     MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount);
 
     size_t nGlyphs = layout.nGlyphs();
-    uint16_t* glyphs = new uint16_t[nGlyphs];
-    float* pos = new float[nGlyphs * 2];
+    std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]);
+    std::unique_ptr<float[]> pos(new float[nGlyphs * 2]);
 
     x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
 
@@ -597,13 +564,9 @@
         bounds.offset(x, y);
     }
 
-    DrawTextFunctor f(layout, canvas, glyphs, pos, paint, x, y, bounds, layout.getAdvance());
+    DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(),
+            paint, x, y, bounds, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
-
-    drawTextDecorations(canvas, x, y, layout.getAdvance(), paint);
-
-    delete[] glyphs;
-    delete[] pos;
 }
 
 static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6bdf71b..6338088 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.
@@ -2835,6 +2841,17 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.SystemUserHomeActivity"
+                  android:enabled="false"
+                  android:process=":ui"
+                  android:systemUserOnly="true"
+                  android:theme="@style/Theme.Translucent.NoTitleBar">
+            <intent-filter android:priority="-100">
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.HOME" />
+            </intent-filter>
+        </activity>
+
         <receiver android:name="com.android.server.BootReceiver"
                 android:systemUserOnly="true">
             <intent-filter android:priority="1000">
diff --git a/core/res/res/layout/alert_dialog_button_bar_material.xml b/core/res/res/layout/alert_dialog_button_bar_material.xml
index 6e102f3..f7974a5 100644
--- a/core/res/res/layout/alert_dialog_button_bar_material.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_material.xml
@@ -27,7 +27,6 @@
     android:paddingTop="4dp"
     android:paddingBottom="4dp"
     android:gravity="bottom"
-    android:allowStacking="@bool/allow_stacked_button_bar"
     style="?attr/buttonBarStyle">
 
     <Button
diff --git a/core/res/res/layout/decor_caption_dark.xml b/core/res/res/layout/decor_caption_dark.xml
index 86d68af..273264d 100644
--- a/core/res/res/layout/decor_caption_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -23,6 +23,7 @@
     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"
diff --git a/core/res/res/layout/decor_caption_light.xml b/core/res/res/layout/decor_caption_light.xml
index ee03545..fd9198e 100644
--- a/core/res/res/layout/decor_caption_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -23,6 +23,7 @@
     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"
diff --git a/core/res/res/values-h320dp/bools.xml b/core/res/res/values-h320dp/bools.xml
deleted file mode 100644
index 3bbfe96..0000000
--- a/core/res/res/values-h320dp/bools.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <bool name="allow_stacked_button_bar">true</bool>
-</resources>
diff --git a/core/res/res/values-mcc208-mnc01/config.xml b/core/res/res/values-mcc208-mnc01/config.xml
index c56da24..5930e3a 100644
--- a/core/res/res/values-mcc208-mnc01/config.xml
+++ b/core/res/res/values-mcc208-mnc01/config.xml
@@ -28,9 +28,6 @@
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
     <string-array translatable="false" name="config_tether_apndata">
         <item>Orange Internet,orange.fr,,,orange,orange,,,,,208,01,1,DUN</item>
-        <item>[ApnSettingV3]Carrefour WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,33</item>
-        <item>[ApnSettingV3]VM WAP,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,52</item>
-        <item>[ApnSettingV3]NRJWEB,ofnew.fr,,,orange,orange,,,,,208,01,1,DUN,,,true,0,,,,,,,gid,4E</item>
     </string-array>
 
 </resources>
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index a32f266..d3640e5 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -29,11 +29,6 @@
     <string-array translatable="false" name="config_tether_apndata">
         <item>SFR option modem,websfr,,,,,,,,,208,10,,DUN</item>
         <item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
-        <item>[ApnSettingV3]Auchan,wap65,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,A MOBILE</item>
-        <item>[ApnSettingV3]LeclercMobile,wap66,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,spn,LeclercMobile</item>
-        <item>[ApnSettingV3]Coriolis,fnetcoriolis,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,12</item>
-        <item>[ApnSettingV3]WEB La Poste Mobile,wapdebitel,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4C</item>
-        <item>[ApnSettingV3]Darty Surf Mails,wap68,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,44</item>
     </string-array>
 
     <string-array translatable="false" name="config_operatorConsideredNonRoaming">
diff --git a/core/res/res/values-mcc214-mnc07/config.xml b/core/res/res/values-mcc214-mnc07/config.xml
index 91571a5..4b7cc7c 100644
--- a/core/res/res/values-mcc214-mnc07/config.xml
+++ b/core/res/res/values-mcc214-mnc07/config.xml
@@ -28,7 +28,6 @@
          note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" -->
     <string-array translatable="false" name="config_tether_apndata">
         <item>Conexión Compartida,movistar.es,,,MOVISTAR,MOVISTAR,,,,,214,07,1,DUN</item>
-        <item>[ApnSettingV3]Jazztel Internet,jazzinternet,,,,,,,,,214,07,,DUN,,,true,0,,,,,,,spn,JAZZTEL</item>
     </string-array>
 
 </resources>
diff --git a/core/res/res/values-mcc222-mnc10/config.xml b/core/res/res/values-mcc222-mnc10/config.xml
index 5a74462..cd6e8c6 100644
--- a/core/res/res/values-mcc222-mnc10/config.xml
+++ b/core/res/res/values-mcc222-mnc10/config.xml
@@ -20,16 +20,6 @@
 <!-- These resources are around just to allow their values to be customized
      for different hardware and product builds.  Do not translate. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
     <!-- String containing the apn value for tethering.  May be overriden by secure settings
          TETHER_DUN_APN.  Value is a comma separated series of strings:
          "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type",
diff --git a/core/res/res/values-mcc234-mnc20/config.xml b/core/res/res/values-mcc234-mnc20/config.xml
index 814960a..27c91d2 100644
--- a/core/res/res/values-mcc234-mnc20/config.xml
+++ b/core/res/res/values-mcc234-mnc20/config.xml
@@ -21,16 +21,6 @@
      for different hardware and product builds. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
-    <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
-    <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
-    <integer-array translatable="false" name="config_tether_upstream_types">
-        <item>1</item>
-        <item>4</item>
-        <item>7</item>
-        <item>9</item>
-    </integer-array>
-
     <!-- String containing the apn value for tethering.  May be overriden by secure settings
          TETHER_DUN_APN.  Value is a comma separated series of strings:
          "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 7c63950..457131a 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -25,8 +25,4 @@
     <bool name="show_ongoing_ime_switcher">true</bool>
     <bool name="action_bar_expanded_action_views_exclusive">true</bool>
     <bool name="target_honeycomb_needs_options_menu">true</bool>
-
-    <!-- Whether to allow vertically stacked button bars. This is disabled for
-         configurations with a small (e.g. less than 320dp) screen height. -->
-    <bool name="allow_stacked_button_bar">false</bool>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b87d9e2..539baa5 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>
@@ -1938,9 +1946,9 @@
      See {@link com.android.server.notification.NotificationSignalExtractor} -->
     <string-array name="config_notificationSignalExtractors">
         <item>com.android.server.notification.ValidateNotificationPeople</item>
-        <item>com.android.server.notification.PackagePriorityExtractor</item>
+        <item>com.android.server.notification.TopicPriorityExtractor</item>
         <item>com.android.server.notification.NotificationIntrusivenessExtractor</item>
-        <item>com.android.server.notification.PackageVisibilityExtractor</item>
+        <item>com.android.server.notification.TopicVisibilityExtractor</item>
     </string-array>
 
     <!-- Flag indicating that this device does not rotate and will always remain in its default
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 00c0fe8..1964bec 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4072,4 +4072,6 @@
         <item quantity="one"><xliff:g id="count" example="1">%1$d</xliff:g> selected</item>
         <item quantity="other"><xliff:g id="count" example="3">%1$d</xliff:g> selected</item>
     </plurals>
+
+    <string name="default_notification_topic_label">Miscellaneous</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ce9d8d..edd3555 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" />
@@ -2320,7 +2321,6 @@
 
   <java-symbol type="string" name="lockscreen_access_pattern_area" />
 
-  <java-symbol type="bool" name="allow_stacked_button_bar" />
   <java-symbol type="bool" name="config_eap_sim_based_auth_supported" />
 
   <java-symbol type="array" name="config_cell_retries_per_error_code" />
@@ -2336,4 +2336,5 @@
   <java-symbol type="string" name="config_iccHotswapPromptForRestartDialogComponent" />
 
   <java-symbol type="string" name="config_packagedKeyboardName" />
+  <java-symbol type="string" name="default_notification_topic_label" />
 </resources>
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
new file mode 100644
index 0000000..add3f40
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
new file mode 100644
index 0000000..7038f46
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/hasGlyphTestFont.ttx
@@ -0,0 +1,365 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <!-- The 'id' attribute is only for humans; it is ignored when parsed. -->
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="BaseChar1"/>
+    <GlyphID id="2" name="BaseChar1_VS1"/>
+    <GlyphID id="3" name="BaseChar1_VS17"/>
+    <GlyphID id="4" name="BaseChar1_VS18"/>
+    <GlyphID id="5" name="BaseChar2"/>
+    <GlyphID id="6" name="BaseChar2_VS2"/>
+    <GlyphID id="7" name="BaseChar2_VS18"/>
+    <GlyphID id="8" name="BaseChar2_VS19"/>
+    <GlyphID id="9" name="BaseChar3"/>
+    <GlyphID id="10" name="BaseChar4_VS3"/>
+    <GlyphID id="11" name="BaseChar4_VS19"/>
+    <GlyphID id="12" name="BaseChar4_VS20"/>
+    <GlyphID id="13" name="BaseChar5"/>
+    <GlyphID id="14" name="BaseChar5_VS1"/>
+    <GlyphID id="15" name="BaseChar5_VS17"/>
+    <GlyphID id="16" name="BaseChar5_VS18"/>
+    <GlyphID id="17" name="BaseChar6"/>
+    <GlyphID id="18" name="BaseChar6_VS2"/>
+    <GlyphID id="19" name="BaseChar6_VS18"/>
+    <GlyphID id="20" name="BaseChar6_VS19"/>
+    <GlyphID id="21" name="BaseChar7"/>
+    <GlyphID id="22" name="BaseChar8_VS3"/>
+    <GlyphID id="23" name="BaseChar8_VS19"/>
+    <GlyphID id="24" name="BaseChar8_VS20"/>
+  </GlyphOrder>
+
+  <head>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Wed Sep  9 08:01:17 2015"/>
+    <modified value="Wed Sep  9 08:48:07 2015"/>
+    <xMin value="30"/>
+    <yMin value="-200"/>
+    <xMax value="629"/>
+    <yMax value="800"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <indexToLocFormat value="0"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <advanceWidthMax value="659"/>
+    <minLeftSideBearing value="0"/>
+    <minRightSideBearing value="30"/>
+    <xMaxExtent value="629"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+    <numberOfHMetrics value="18"/>
+  </hhea>
+
+  <maxp>
+    <!-- Most of this table will be recalculated by the compiler -->
+    <tableVersion value="0x10000"/>
+    <numGlyphs value="54"/>
+    <maxPoints value="73"/>
+    <maxContours value="10"/>
+    <maxCompositePoints value="0"/>
+    <maxCompositeContours value="0"/>
+    <maxZones value="2"/>
+    <maxTwilightPoints value="12"/>
+    <maxStorage value="28"/>
+    <maxFunctionDefs value="119"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="61"/>
+    <maxSizeOfInstructions value="2967"/>
+    <maxComponentElements value="0"/>
+    <maxComponentDepth value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="BaseChar1" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS1" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS17" width="500" lsb="93"/>
+    <mtx name="BaseChar1_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar2" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS2" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar2_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar3" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS3" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar4_VS20" width="500" lsb="93"/>
+    <mtx name="BaseChar5" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS1" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS17" width="500" lsb="93"/>
+    <mtx name="BaseChar5_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar6" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS2" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS18" width="500" lsb="93"/>
+    <mtx name="BaseChar6_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar7" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS3" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS19" width="500" lsb="93"/>
+    <mtx name="BaseChar8_VS20" width="500" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_12 format="12" reserved="0" length="6" nGroups="1" platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="BaseChar1" />
+      <map code="0x0062" name="BaseChar2" />
+      <map code="0x0063" name="BaseChar3" />
+      <!-- No cmap4 entry for BaseChar4 -->
+      <map code="0x1F000" name="BaseChar5" />
+      <map code="0x1F001" name="BaseChar6" />
+      <map code="0x1F002" name="BaseChar7" />
+      <!-- No cmap4 entry for BaseChar8 -->
+    </cmap_format_12>
+    <cmap_format_14 format="14" platformID="0" platEncID="5" length="24" numVarSelectorRecords="3">
+      <map uvs="0xFE00" uv="0x0061" name="BaseChar1_VS1" />
+      <map uvs="0xE0100" uv="0x0061" name="BaseChar1_VS17" />
+      <map uvs="0xE0101" uv="0x0061" name="BaseChar1_VS18" />
+      <map uvs="0xE0102" uv="0x0061" name="None" />
+
+      <map uvs="0xFE01" uv="0x0062" name="BaseChar2_VS2" />
+      <map uvs="0xE0101" uv="0x0062" name="BaseChar2_VS18" />
+      <map uvs="0xE0102" uv="0x0062" name="BaseChar2_VS19" />
+      <map uvs="0xE0103" uv="0x0062" name="None" />
+
+      <map uvs="0xFE02" uv="0x0064" name="BaseChar4_VS3" />
+      <map uvs="0xE0102" uv="0x0064" name="BaseChar4_VS19" />
+      <map uvs="0xE0103" uv="0x0064" name="BaseChar4_VS20" />
+      <!-- There is no default glyph for U+0064 U+E0104 but there is a entry for
+           default UVS entry.  hasGlyph should return false in this
+           case.  -->
+      <map uvs="0xE0104" uv="0x0064" name="None" />
+
+      <map uvs="0xFE00" uv="0x1F000" name="BaseChar5_VS1" />
+      <map uvs="0xE0100" uv="0x1F000" name="BaseChar5_VS17" />
+      <map uvs="0xE0101" uv="0x1F000" name="BaseChar5_VS18" />
+      <map uvs="0xE0102" uv="0x1F000" name="None" />
+
+      <map uvs="0xFE01" uv="0x1F001" name="BaseChar6_VS2" />
+      <map uvs="0xE0101" uv="0x1F001" name="BaseChar6_VS18" />
+      <map uvs="0xE0102" uv="0x1F001" name="BaseChar6_VS19" />
+      <map uvs="0xE0103" uv="0x1F001" name="None" />
+
+      <map uvs="0xFE02" uv="0x1F003" name="BaseChar8_VS3" />
+      <map uvs="0xE0102" uv="0x1F003" name="BaseChar8_VS19" />
+      <map uvs="0xE0103" uv="0x1F003" name="BaseChar8_VS20" />
+      <!-- There is no default glyph for U+1F003 U+E0104 but there is a entry for
+           default UVS entry.  hasGlyph should return false in this
+           case.  -->
+      <map uvs="0xE0104" uv="0x1F003" name="None" />
+    </cmap_format_14>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+
+    <!-- The xMin, yMin, xMax and yMax values
+         will be recalculated by the compiler. -->
+
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+
+    <TTGlyph name="BaseChar1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar1_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar2_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar4_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS1" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS17" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar5_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS2" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS18" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar6_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar7" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS3" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS19" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+    <TTGlyph name="BaseChar8_VS20" xMin="0" yMin="0" xMax="0" yMax="0">
+      <contour></contour><instructions><assembly></assembly></instructions>
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
+      hasGlyphTestFont-Regular
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Paint.hasGlyph Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      hasGlyphTestFont Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      hasGlyphTestFont-Regular
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index e97bb33..2a3d463 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -20,6 +20,9 @@
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Arrays;
+import java.util.HashSet;
+
 /**
  * PaintTest tests {@link Paint}.
  */
@@ -94,4 +97,63 @@
                     testCase.mWidthWithHinting, widths);
         }
     }
+
+    private static class HasGlyphTestCase {
+        public final int mBaseCodepoint;
+        public final HashSet<Integer> mVariationSelectors;
+
+        public HasGlyphTestCase(int baseCodepoint, Integer[] variationSelectors) {
+            mBaseCodepoint = baseCodepoint;
+            mVariationSelectors = new HashSet<>(Arrays.asList(variationSelectors));
+        }
+    }
+
+    private static String codePointsToString(int[] codepoints) {
+        StringBuilder sb = new StringBuilder();
+        for (int codepoint : codepoints) {
+            sb.append(Character.toChars(codepoint));
+        }
+        return sb.toString();
+    }
+
+    public void testHasGlyph_variationSelectors() {
+        final Typeface fontTypeface = Typeface.createFromAsset(
+                getInstrumentation().getContext().getAssets(), "fonts/hasGlyphTestFont.ttf");
+        Paint p = new Paint();
+        p.setTypeface(fontTypeface);
+
+        // Usually latin letters U+0061..U+0064 and Mahjong Tiles U+1F000..U+1F003 don't have
+        // variation selectors.  This test may fail if system pre-installed fonts have a variation
+        // selector support for U+0061..U+0064 and U+1F000..U+1F003.
+        HasGlyphTestCase[] HAS_GLYPH_TEST_CASES = {
+            new HasGlyphTestCase(0x0061, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+            new HasGlyphTestCase(0x0062, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+            new HasGlyphTestCase(0x0063, new Integer[] {}),
+            new HasGlyphTestCase(0x0064, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+
+            new HasGlyphTestCase(0x1F000, new Integer[] {0xFE00, 0xE0100, 0xE0101, 0xE0102}),
+            new HasGlyphTestCase(0x1F001, new Integer[] {0xFE01, 0xE0101, 0xE0102, 0xE0103}),
+            new HasGlyphTestCase(0x1F002, new Integer[] {}),
+            new HasGlyphTestCase(0x1F003, new Integer[] {0xFE02, 0xE0102, 0xE0103}),
+        };
+
+        for (HasGlyphTestCase testCase : HAS_GLYPH_TEST_CASES) {
+            for (int vs = 0xFE00; vs <= 0xE01EF; ++vs) {
+                // Move to variation selector supplements after variation selectors.
+                if (vs == 0xFF00) {
+                    vs = 0xE0100;
+                }
+                final String signature =
+                        "hasGlyph(U+" + Integer.toHexString(testCase.mBaseCodepoint) +
+                        " U+" + Integer.toHexString(vs) + ")";
+                final String testString =
+                        codePointsToString(new int[] {testCase.mBaseCodepoint, vs});
+                if (testCase.mVariationSelectors.contains(vs)) {
+                    assertTrue(signature + " is expected to be true", p.hasGlyph(testString));
+                } else {
+                    assertFalse(signature + " is expected to be false", p.hasGlyph(testString));
+                }
+            }
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
new file mode 100644
index 0000000..951e87a
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/HexDumpTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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.util;
+
+import junit.framework.TestCase;
+
+public final class HexDumpTest extends TestCase {
+    public void testBytesToHexString() {
+        assertEquals("abcdef", HexDump.toHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, false));
+        assertEquals("ABCDEF", HexDump.toHexString(
+                new byte[] { (byte) 0xab, (byte) 0xcd, (byte) 0xef }, true));
+    }
+}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
index 58bf654..4186007 100644
--- a/data/keyboards/qwerty.kl
+++ b/data/keyboards/qwerty.kl
@@ -81,7 +81,7 @@
 key 39    SEMICOLON
 key 40    APOSTROPHE
 key 14    DEL
-        
+
 key 44    Z
 key 45    X
 key 46    C
@@ -93,7 +93,7 @@
 key 52    PERIOD
 key 53    SLASH
 key 28    ENTER
-        
+
 key 56    ALT_LEFT
 key 100   ALT_RIGHT
 key 42    SHIFT_LEFT
@@ -101,7 +101,7 @@
 key 15    TAB
 key 57    SPACE
 key 150   EXPLORER
-key 155   ENVELOPE        
+key 155   ENVELOPE
 
 key 12    MINUS
 key 13    EQUALS
@@ -110,3 +110,16 @@
 # On an AT keyboard: ESC, F10
 key 1     BACK
 key 68    MENU
+
+# App switch = Overview key
+key 580   APP_SWITCH
+
+# Media control keys
+key 160   MEDIA_CLOSE
+key 161   MEDIA_EJECT
+key 163   MEDIA_NEXT
+key 164   MEDIA_PLAY_PAUSE
+key 165   MEDIA_PREVIOUS
+key 166   MEDIA_STOP
+key 167   MEDIA_RECORD
+key 168   MEDIA_REWIND
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index 6e22be8..b8c105d 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -512,7 +512,7 @@
 onStartCommand()} directly.)</p>
 
 <p>For example, an activity can start the example service in the previous section ({@code
-HelloSevice}) using an explicit intent with {@link android.content.Context#startService
+HelloService}) using an explicit intent with {@link android.content.Context#startService
 startService()}:</p>
 
 <pre>
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 92226f5..cc68fb2 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -30,12 +30,14 @@
     utils/StringUtils.cpp \
     utils/TestWindowContext.cpp \
     utils/VectorDrawableUtils.cpp \
+    utils/TestUtils.cpp \
     AmbientShadow.cpp \
     AnimationContext.cpp \
     Animator.cpp \
     AnimatorManager.cpp \
     AssetAtlas.cpp \
     Caches.cpp \
+    Canvas.cpp \
     CanvasState.cpp \
     ClipArea.cpp \
     DamageAccumulator.cpp \
@@ -253,9 +255,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/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index d2d3285..d13d7ef 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -24,6 +24,9 @@
 #include "utils/GLUtils.h"
 #include "VertexBuffer.h"
 
+#include <algorithm>
+#include <math.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -183,6 +186,10 @@
     renderer.renderGlop(state, glop);
 }
 
+void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) {
+    LOG_ALWAYS_FATAL("todo");
+}
+
 void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) {
     Glop glop;
     GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
@@ -270,6 +277,91 @@
     renderer.renderGlop(state, glop);
 }
 
+static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer,
+        const TextOp& op, const BakedOpState& state) {
+    renderer.caches().textureState().activateTexture(0);
+
+    PaintUtils::TextShadow textShadow;
+    if (!PaintUtils::getTextShadow(op.paint, &textShadow)) {
+        LOG_ALWAYS_FATAL("failed to query shadow attributes");
+    }
+
+    renderer.caches().dropShadowCache.setFontRenderer(fontRenderer);
+    ShadowTexture* texture = renderer.caches().dropShadowCache.get(
+            op.paint, (const char*) op.glyphs,
+            op.glyphCount, textShadow.radius, op.positions);
+    // If the drop shadow exceeds the max texture size or couldn't be
+    // allocated, skip drawing
+    if (!texture) return;
+    const AutoTexture autoCleanup(texture);
+
+    const float sx = op.x - texture->left + textShadow.dx;
+    const float sy = op.y - texture->top + textShadow.dy;
+
+    Glop glop;
+    GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+            .setRoundRectClipState(state.roundRectClipState)
+            .setMeshTexturedUnitQuad(nullptr)
+            .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
+            .setTransform(state.computedState.transform, TransformFlags::None)
+            .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+            .build();
+    renderer.renderGlop(state, glop);
+}
+
+void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) {
+    FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer();
+
+    if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) {
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        renderTextShadow(renderer, fontRenderer, op, state);
+    }
+
+    float x = op.x;
+    float y = op.y;
+    const Matrix4& transform = state.computedState.transform;
+    const bool pureTranslate = transform.isPureTranslate();
+    if (CC_LIKELY(pureTranslate)) {
+        x = floorf(x + transform.getTranslateX() + 0.5f);
+        y = floorf(y + transform.getTranslateY() + 0.5f);
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        fontRenderer.setTextureFiltering(false);
+    } else if (CC_UNLIKELY(transform.isPerspective())) {
+        fontRenderer.setFont(op.paint, SkMatrix::I());
+        fontRenderer.setTextureFiltering(true);
+    } else {
+        // We only pass a partial transform to the font renderer. That partial
+        // matrix defines how glyphs are rasterized. Typically we want glyphs
+        // to be rasterized at their final size on screen, which means the partial
+        // matrix needs to take the scale factor into account.
+        // When a partial matrix is used to transform glyphs during rasterization,
+        // the mesh is generated with the inverse transform (in the case of scale,
+        // the mesh is generated at 1.0 / scale for instance.) This allows us to
+        // apply the full transform matrix at draw time in the vertex shader.
+        // Applying the full matrix in the shader is the easiest way to handle
+        // rotation and perspective and allows us to always generated quads in the
+        // font renderer which greatly simplifies the code, clipping in particular.
+        float sx, sy;
+        transform.decomposeScale(sx, sy);
+        fontRenderer.setFont(op.paint, SkMatrix::MakeScale(
+                roundf(std::max(1.0f, sx)),
+                roundf(std::max(1.0f, sy))));
+        fontRenderer.setTextureFiltering(true);
+    }
+
+    // TODO: Implement better clipping for scaled/rotated text
+    const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect;
+    Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
+
+    int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha;
+    SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint);
+    TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint);
+
+    bool hasActiveLayer = false; // TODO
+    fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y,
+            op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging
+}
+
 void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
     OffscreenBuffer* buffer = *op.layerHandle;
 
diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp
new file mode 100644
index 0000000..bc88c81
--- /dev/null
+++ b/libs/hwui/Canvas.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Canvas.h"
+
+#include <SkDrawFilter.h>
+
+namespace android {
+
+void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
+    uint32_t flags;
+    SkDrawFilter* drawFilter = getDrawFilter();
+    if (drawFilter) {
+        SkPaint paintCopy(paint);
+        drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+        flags = paintCopy.getFlags();
+    } else {
+        flags = paint.getFlags();
+    }
+    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+        // Same values used by Skia
+        const float kStdStrikeThru_Offset   = (-6.0f / 21.0f);
+        const float kStdUnderline_Offset    = (1.0f / 9.0f);
+        const float kStdUnderline_Thickness = (1.0f / 18.0f);
+
+        SkScalar left = x;
+        SkScalar right = x + length;
+        float textSize = paint.getTextSize();
+        float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f);
+        if (flags & SkPaint::kUnderlineText_Flag) {
+            SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth;
+            SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth;
+            drawRect(left, top, right, bottom, paint);
+        }
+        if (flags & SkPaint::kStrikeThruText_Flag) {
+            SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth;
+            SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth;
+            drawRect(left, top, right, bottom, paint);
+        }
+    }
+}
+
+} // namespace android
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 4bd4ac8..b585a27 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -149,16 +149,12 @@
     // Text
     /**
      * drawText: count is of glyphs
-     * totalAdvance is ignored in software renderering, used by hardware renderer for
-     * text decorations (underlines, strikethroughs).
+     * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
      */
     virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
             const SkPaint& paint, float x, float y,
             float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
             float totalAdvance) = 0;
-    /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */
-    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) = 0;
     /** drawTextOnPath: count is of glyphs */
     virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) = 0;
@@ -171,6 +167,9 @@
      * to be added to each glyph's position to get its absolute position.
      */
     virtual bool drawTextAbsolutePos() const = 0;
+
+protected:
+    void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
 };
 
 }; // namespace android
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index f5e5735..759c12a 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -423,18 +423,6 @@
     addDrawOp(op);
 }
 
-void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions,
-        int count, int posCount, const SkPaint& paint) {
-    if (!text || count <= 0) return;
-
-    int bytesCount = 2 * count;
-    positions = refBuffer<float>(positions, count * 2);
-
-    DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount),
-                                             bytesCount, count, positions, refPaint(&paint));
-    addDrawOp(op);
-}
-
 void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions,
         int count, const SkPaint& paint, float x, float y,
         float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
@@ -450,6 +438,7 @@
     DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
             x, y, positions, refPaint(&paint), totalAdvance, bounds);
     addDrawOp(op);
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
 
 void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index 609103b..bf98f79 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -212,8 +212,6 @@
     virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) override;
-    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) override;
     virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) override;
     virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 772aa72..977b53c 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -1278,24 +1278,6 @@
     float mVOffset;
 };
 
-class DrawPosTextOp : public DrawSomeTextOp {
-public:
-    DrawPosTextOp(const char* text, int bytesCount, int count,
-            const float* positions, const SkPaint* paint)
-            : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
-        /* TODO: inherit from DrawBounded and init mLocalBounds */
-    }
-
-    virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override {
-        renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint);
-    }
-
-    virtual const char* name() override { return "DrawPosText"; }
-
-private:
-    const float* mPositions;
-};
-
 class DrawTextOp : public DrawStrokableOp {
 public:
     DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index ccf0b48..5f33cae 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,13 +21,20 @@
 #include "Extensions.h"
 #include "Glop.h"
 #include "GlopBuilder.h"
-#include "OpenGLRenderer.h"
 #include "PixelBuffer.h"
 #include "Rect.h"
 #include "renderstate/RenderState.h"
 #include "utils/Blur.h"
 #include "utils/Timing.h"
 
+
+#if HWUI_NEW_OPS
+#include "BakedOpState.h"
+#include "BakedOpRenderer.h"
+#else
+#include "OpenGLRenderer.h"
+#endif
+
 #include <algorithm>
 #include <cutils/properties.h>
 #include <SkGlyph.h>
@@ -59,14 +66,25 @@
     int transformFlags = pureTranslate
             ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None;
     Glop glop;
+#if HWUI_NEW_OPS
+    GlopBuilder(renderer->renderState(), renderer->caches(), &glop)
+            .setRoundRectClipState(bakedState->roundRectClipState)
+            .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
+            .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha)
+            .setTransform(bakedState->computedState.transform, transformFlags)
+            .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
+            .build();
+    renderer->renderGlop(*bakedState, glop);
+#else
     GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop)
+            .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
             .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount())
             .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha)
             .setTransform(*(renderer->currentSnapshot()), transformFlags)
             .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0))
-            .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState)
             .build();
     renderer->renderGlop(glop);
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -539,7 +557,7 @@
 }
 
 FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
+        int numGlyphs, float radius, const float* positions) {
     checkInit();
 
     DropShadow image;
@@ -558,7 +576,7 @@
     mBounds = nullptr;
 
     Rect bounds;
-    mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
+    mCurrentFont->measure(paint, text, numGlyphs, &bounds, positions);
 
     uint32_t intRadius = Blur::convertRadiusToInt(radius);
     uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
@@ -590,7 +608,7 @@
         // text has non-whitespace, so draw and blur to create the shadow
         // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted
         // TODO: don't draw pure whitespace in the first place, and avoid needing this check
-        mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY,
+        mCurrentFont->render(paint, text, numGlyphs, penX, penY,
                 Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions);
 
         // Unbind any PBO we might have used
@@ -635,15 +653,15 @@
 }
 
 bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
-        const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
+        int numGlyphs, int x, int y, const float* positions,
+        Rect* bounds, TextDrawFunctor* functor, bool forceFinish) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
     }
 
     initRender(clip, bounds, functor);
-    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
+    mCurrentFont->render(paint, text, numGlyphs, x, y, positions);
 
     if (forceFinish) {
         finishRender();
@@ -653,15 +671,15 @@
 }
 
 bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
-        uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
-        float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) {
+        int numGlyphs, const SkPath* path, float hOffset, float vOffset,
+        Rect* bounds, TextDrawFunctor* functor) {
     if (!mCurrentFont) {
         ALOGE("No font set");
         return false;
     }
 
     initRender(clip, bounds, functor);
-    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+    mCurrentFont->render(paint, text, numGlyphs, path, hOffset, vOffset);
     finishRender();
 
     return mDrawn;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 8172312..87cfe7f 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -44,13 +44,28 @@
 namespace android {
 namespace uirenderer {
 
+#if HWUI_NEW_OPS
+class BakedOpState;
+class BakedOpRenderer;
+#else
 class OpenGLRenderer;
+#endif
 
 class TextDrawFunctor {
 public:
-    TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
+    TextDrawFunctor(
+#if HWUI_NEW_OPS
+            BakedOpRenderer* renderer,
+            const BakedOpState* bakedState,
+#else
+            OpenGLRenderer* renderer,
+#endif
+            float x, float y, bool pureTranslate,
             int alpha, SkXfermode::Mode mode, const SkPaint* paint)
         : renderer(renderer)
+#if HWUI_NEW_OPS
+        , bakedState(bakedState)
+#endif
         , x(x)
         , y(y)
         , pureTranslate(pureTranslate)
@@ -61,7 +76,12 @@
 
     void draw(CacheTexture& texture, bool linearFiltering);
 
+#if HWUI_NEW_OPS
+    BakedOpRenderer* renderer;
+    const BakedOpState* bakedState;
+#else
     OpenGLRenderer* renderer;
+#endif
     float x;
     float y;
     bool pureTranslate;
@@ -83,15 +103,13 @@
     void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
     void endPrecaching();
 
-    // bounds is an out parameter
     bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
-            uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
-            Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true);
+            int numGlyphs, int x, int y, const float* positions,
+            Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true);
 
-    // bounds is an out parameter
     bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
-            uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
-            float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor);
+            int numGlyphs, const SkPath* path,
+            float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor);
 
     struct DropShadow {
         uint32_t width;
@@ -103,8 +121,8 @@
 
     // After renderDropShadow returns, the called owns the memory in DropShadow.image
     // and is responsible for releasing it when it's done with it
-    DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
-            uint32_t len, int numGlyphs, float radius, const float* positions);
+    DropShadow renderDropShadow(const SkPaint* paint, const char *text, int numGlyphs,
+            float radius, const float* positions);
 
     void setTextureFiltering(bool linearFiltering) {
         mLinearFiltering = linearFiltering;
diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp
index 96cac7e..5e954ae 100644
--- a/libs/hwui/OpReorderer.cpp
+++ b/libs/hwui/OpReorderer.cpp
@@ -671,6 +671,13 @@
     currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId);
 }
 
+void OpReorderer::onLinesOp(const LinesOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+    currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
+
+}
+
 void OpReorderer::onRectOp(const RectOp& op) {
     BakedOpState* bakedStateOp = tryBakeOpState(op);
     if (!bakedStateOp) return; // quick rejected
@@ -683,6 +690,17 @@
     currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices);
 }
 
+void OpReorderer::onTextOp(const TextOp& op) {
+    BakedOpState* bakedStateOp = tryBakeOpState(op);
+    if (!bakedStateOp) return; // quick rejected
+
+    // TODO: better handling of shader (since we won't care about color then)
+    batchid_t batchId = op.paint->getColor() == SK_ColorBLACK
+            ? OpBatchType::Text : OpBatchType::ColorText;
+    mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor());
+    currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId);
+}
+
 void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight,
         float contentTranslateX, float contentTranslateY,
         const Rect& repaintRect,
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 12c4607..e386b1c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1950,7 +1950,7 @@
 }
 
 void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
-        int bytesCount, int count, const float* positions,
+        int count, const float* positions,
         FontRenderer& fontRenderer, int alpha, float x, float y) {
     mCaches.textureState().activateTexture(0);
 
@@ -1963,7 +1963,7 @@
     //       if shader-based correction is enabled
     mCaches.dropShadowCache.setFontRenderer(fontRenderer);
     ShadowTexture* texture = mCaches.dropShadowCache.get(
-            paint, text, bytesCount, count, textShadow.radius, positions);
+            paint, text, count, textShadow.radius, positions);
     // If the drop shadow exceeds the max texture size or couldn't be
     // allocated, skip drawing
     if (!texture) return;
@@ -1991,57 +1991,6 @@
             && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
 }
 
-void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
-        const float* positions, const SkPaint* paint) {
-    if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) {
-        return;
-    }
-
-    // NOTE: Skia does not support perspective transform on drawPosText yet
-    if (!currentTransform()->isSimple()) {
-        return;
-    }
-
-    mRenderState.scissor().setEnabled(true);
-
-    float x = 0.0f;
-    float y = 0.0f;
-    const bool pureTranslate = currentTransform()->isPureTranslate();
-    if (pureTranslate) {
-        x = floorf(x + currentTransform()->getTranslateX() + 0.5f);
-        y = floorf(y + currentTransform()->getTranslateY() + 0.5f);
-    }
-
-    FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer();
-    fontRenderer.setFont(paint, SkMatrix::I());
-
-    int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
-    SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
-
-    if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
-        drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
-                alpha, 0.0f, 0.0f);
-    }
-
-    // Pick the appropriate texture filtering
-    bool linearFilter = currentTransform()->changesBounds();
-    if (pureTranslate && !linearFilter) {
-        linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
-    }
-    fontRenderer.setTextureFiltering(linearFilter);
-
-    const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip());
-    Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
-
-    TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
-    if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
-            positions, hasLayer() ? &bounds : nullptr, &functor)) {
-        dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
-        mDirty = true;
-    }
-
-}
-
 bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
     if (CC_LIKELY(transform.isPureTranslate())) {
         outMatrix->setIdentity();
@@ -2166,7 +2115,7 @@
 
     if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) {
         fontRenderer.setFont(paint, SkMatrix::I());
-        drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
+        drawTextShadow(paint, text, count, positions, fontRenderer,
                 alpha, oldX, oldY);
     }
 
@@ -2195,17 +2144,22 @@
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+    TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint);
+#else
     TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
+#endif
 
     // don't call issuedrawcommand, do it at end of batch
     bool forceFinish = (drawOpMode != DrawOpMode::kDefer);
     if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
         SkPaint paintCopy(*paint);
         paintCopy.setTextAlign(SkPaint::kLeft_Align);
-        status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
+        status = fontRenderer.renderPosText(&paintCopy, clip, text, count, x, y,
                 positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
     } else {
-        status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+        status = fontRenderer.renderPosText(paint, clip, text, count, x, y,
                 positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish);
     }
 
@@ -2216,8 +2170,6 @@
         dirtyLayerUnchecked(layerBounds, getRegion());
     }
 
-    drawTextDecorations(totalAdvance, oldX, oldY, paint);
-
     mDirty = true;
 }
 
@@ -2236,12 +2188,17 @@
 
     int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha;
     SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint);
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("unsupported");
+    TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint);
+#else
     TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint);
+#endif
 
     const Rect* clip = &writableSnapshot()->getLocalClip();
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
-    if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+    if (fontRenderer.renderTextOnPath(paint, clip, text, count, path,
             hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) {
         dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
         mDirty = true;
@@ -2375,56 +2332,6 @@
     renderGlop(glop);
 }
 
-// Same values used by Skia
-#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
-#define kStdUnderline_Offset    (1.0f / 9.0f)
-#define kStdUnderline_Thickness (1.0f / 18.0f)
-
-void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
-        const SkPaint* paint) {
-    // Handle underline and strike-through
-    uint32_t flags = paint->getFlags();
-    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
-        SkPaint paintCopy(*paint);
-
-        if (CC_LIKELY(underlineWidth > 0.0f)) {
-            const float textSize = paintCopy.getTextSize();
-            const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f);
-
-            const float left = x;
-            float top = 0.0f;
-
-            int linesCount = 0;
-            if (flags & SkPaint::kUnderlineText_Flag) linesCount++;
-            if (flags & SkPaint::kStrikeThruText_Flag) linesCount++;
-
-            const int pointsCount = 4 * linesCount;
-            float points[pointsCount];
-            int currentPoint = 0;
-
-            if (flags & SkPaint::kUnderlineText_Flag) {
-                top = y + textSize * kStdUnderline_Offset;
-                points[currentPoint++] = left;
-                points[currentPoint++] = top;
-                points[currentPoint++] = left + underlineWidth;
-                points[currentPoint++] = top;
-            }
-
-            if (flags & SkPaint::kStrikeThruText_Flag) {
-                top = y + textSize * kStdStrikeThru_Offset;
-                points[currentPoint++] = left;
-                points[currentPoint++] = top;
-                points[currentPoint++] = left + underlineWidth;
-                points[currentPoint++] = top;
-            }
-
-            paintCopy.setStrokeWidth(strokeWidth);
-
-            drawLines(&points[0], pointsCount, &paintCopy);
-        }
-    }
-}
-
 void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
     if (mState.currentlyIgnored()) {
         return;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 400c225..84bc9b0 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -193,8 +193,6 @@
     void drawPoints(const float* points, int count, const SkPaint* paint);
     void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
             float hOffset, float vOffset, const SkPaint* paint);
-    void drawPosText(const char* text, int bytesCount, int count,
-            const float* positions, const SkPaint* paint);
     void drawText(const char* text, int bytesCount, int count, float x, float y,
             const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
             DrawOpMode drawOpMode = DrawOpMode::kImmediate);
@@ -637,24 +635,11 @@
      */
     void drawConvexPath(const SkPath& path, const SkPaint* paint);
 
-    /**
-     * Draws text underline and strike-through if needed.
-     *
-     * @param text The text to decor
-     * @param bytesCount The number of bytes in the text
-     * @param totalAdvance The total advance in pixels, defines underline/strikethrough length
-     * @param x The x coordinate where the text will be drawn
-     * @param y The y coordinate where the text will be drawn
-     * @param paint The paint to draw the text with
-     */
-    void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
-
    /**
      * Draws shadow layer on text (with optional positions).
      *
      * @param paint The paint to draw the shadow with
      * @param text The text to draw
-     * @param bytesCount The number of bytes in the text
      * @param count The number of glyphs in the text
      * @param positions The x, y positions of individual glyphs (or NULL)
      * @param fontRenderer The font renderer object
@@ -662,7 +647,7 @@
      * @param x The x coordinate where the shadow will be drawn
      * @param y The y coordinate where the shadow will be drawn
      */
-    void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+    void drawTextShadow(const SkPaint* paint, const char* text, int count,
             const float* positions, FontRenderer& fontRenderer, int alpha,
             float x, float y);
 
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index ef05367..127dca5 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_HWUI_RECORDED_OP_H
 #define ANDROID_HWUI_RECORDED_OP_H
 
+#include "font/FontUtil.h"
 #include "Matrix.h"
 #include "Rect.h"
 #include "RenderNode.h"
@@ -42,10 +43,12 @@
  */
 #define MAP_OPS(OP_FN) \
         OP_FN(BitmapOp) \
+        OP_FN(LinesOp) \
         OP_FN(RectOp) \
         OP_FN(RenderNodeOp) \
         OP_FN(ShadowOp) \
         OP_FN(SimpleRectsOp) \
+        OP_FN(TextOp) \
         OP_FN(BeginLayerOp) \
         OP_FN(EndLayerOp) \
         OP_FN(LayerOp)
@@ -98,6 +101,10 @@
     bool skipInOrderDraw = false;
 };
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Standard Ops
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
 struct BitmapOp : RecordedOp {
     BitmapOp(BASE_PARAMS, const SkBitmap* bitmap)
             : SUPER(BitmapOp)
@@ -106,6 +113,15 @@
     // TODO: asset atlas/texture id lookup?
 };
 
+struct LinesOp : RecordedOp {
+    LinesOp(BASE_PARAMS, const float* points, const int floatCount)
+            : SUPER(LinesOp)
+            , points(points)
+            , floatCount(floatCount) {}
+    const float* points;
+    const int floatCount;
+};
+
 struct RectOp : RecordedOp {
     RectOp(BASE_PARAMS)
             : SUPER(RectOp) {}
@@ -148,6 +164,27 @@
     const size_t vertexCount;
 };
 
+struct TextOp : RecordedOp {
+    TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount,
+            float x, float y)
+            : SUPER(TextOp)
+            , glyphs(glyphs)
+            , positions(positions)
+            , glyphCount(glyphCount)
+            , x(x)
+            , y(y) {}
+    const glyph_t* glyphs;
+    const float* positions;
+    const int glyphCount;
+    const float x;
+    const float y;
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Layers
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
 /**
  * Stateful operation! denotes the creation of an off-screen layer,
  * and that commands following will render into it.
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 6ab253c..61fa384 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -230,12 +230,9 @@
 
 void RecordingCanvas::drawPaint(const SkPaint& paint) {
     // TODO: more efficient recording?
-    Matrix4 identity;
-    identity.loadIdentity();
-
     addOp(new (alloc()) RectOp(
             mState.getRenderTargetClipBounds(),
-            identity,
+            Matrix4::identity(),
             mState.getRenderTargetClipBounds(),
             refPaint(&paint)));
 }
@@ -244,9 +241,30 @@
 void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
     LOG_ALWAYS_FATAL("TODO!");
 }
-void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
-    LOG_ALWAYS_FATAL("TODO!");
+
+void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
+    if (floatCount < 4) return;
+    floatCount &= ~0x3; // round down to nearest four
+
+    Rect unmappedBounds(points[0], points[1], points[0], points[1]);
+    for (int i = 2; i < floatCount; i += 2) {
+        unmappedBounds.left = std::min(unmappedBounds.left, points[i]);
+        unmappedBounds.right = std::max(unmappedBounds.right, points[i]);
+        unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]);
+        unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]);
+    }
+
+    // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
+    // 1.0 stroke, treat 1.0 as minimum.
+    unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f);
+
+    addOp(new (alloc()) LinesOp(
+            unmappedBounds,
+            *mState.currentSnapshot()->transform,
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint), refBuffer<float>(points, floatCount), floatCount));
 }
+
 void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
     addOp(new (alloc()) RectOp(
             Rect(left, top, right, bottom),
@@ -388,17 +406,24 @@
 }
 
 // Text
-void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count,
+void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) {
-    LOG_ALWAYS_FATAL("TODO!");
+    if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+    glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
+    positions = refBuffer<float>(positions, glyphCount * 2);
+
+    addOp(new (alloc()) TextOp(
+            Rect(boundsLeft, boundsTop, boundsRight, boundsBottom),
+            *(mState.currentSnapshot()->transform),
+            mState.getRenderTargetClipBounds(),
+            refPaint(&paint), glyphs, positions, glyphCount, x, y));
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
-void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) {
-    LOG_ALWAYS_FATAL("TODO!");
-}
+
 void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) {
+    // NOTE: can't use refPaint() directly, since it forces left alignment
     LOG_ALWAYS_FATAL("TODO!");
 }
 
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index f26b0c8..736cc9e 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -178,8 +178,6 @@
     virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
             const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
             float boundsRight, float boundsBottom, float totalAdvance) override;
-    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) override;
     virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) override;
     virtual bool drawTextAbsolutePos() const override { return false; }
@@ -221,6 +219,15 @@
         return cachedPath;
     }
 
+    /**
+     * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping
+     * based on paint generation ID)
+     *
+     * Note that this forces Left_Align, since drawText glyph rendering expects left alignment,
+     * since alignment offsetting has been done at a higher level. This is done to essentially all
+     * copied paints, since the deduping can mean a paint is shared by drawText commands and other
+     * types (which wouldn't care about alignment).
+     */
     inline const SkPaint* refPaint(const SkPaint* paint) {
         if (!paint) return nullptr;
 
@@ -239,10 +246,11 @@
         // In the unlikely event that 2 unique paints have the same hash we do a
         // object equality check to ensure we don't erroneously dedup them.
         if (cachedPaint == nullptr || *cachedPaint != *paint) {
-            cachedPaint = new SkPaint(*paint);
-            std::unique_ptr<const SkPaint> copy(cachedPaint);
-            mDisplayList->paints.push_back(std::move(copy));
+            SkPaint* copy = new SkPaint(*paint);
+            copy->setTextAlign(SkPaint::kLeft_Align);
 
+            cachedPaint = copy;
+            mDisplayList->paints.emplace_back(copy);
             // replaceValueFor() performs an add if the entry doesn't exist
             mPaintMap.replaceValueFor(key, cachedPaint);
             refBitmapsInShader(cachedPaint->getShader());
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 0736a10..472aad7 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -260,13 +260,6 @@
         bottom = std::max(bottom, y);
     }
 
-    void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) {
-        left = std::min(left, otherLeft);
-        top = std::min(top, otherTop);
-        right = std::max(right, otherRight);
-        bottom = std::max(bottom, otherBottom);
-    }
-
     SkRect toSkRect() const {
         return SkRect::MakeLTRB(left, top, right, bottom);
     }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 6d3dfac..96c1a7c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -131,8 +131,6 @@
             const SkPaint& paint, float x, float y,
             float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
             float totalAdvance) override;
-    virtual void drawPosText(const uint16_t* text, const float* positions, int count,
-            int posCount, const SkPaint& paint) override;
     virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
             float hOffset, float vOffset, const SkPaint& paint) override;
 
@@ -152,7 +150,6 @@
 
     void drawPoints(const float* points, int count, const SkPaint& paint,
                     SkCanvas::PointMode mode);
-    void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
 
     SkAutoTUnref<SkCanvas> mCanvas;
     std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
@@ -712,22 +709,7 @@
 
     static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats");
     mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy);
-}
-
-void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount,
-        const SkPaint& paint) {
-    SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL;
-    int indx;
-    for (indx = 0; indx < posCount; indx++) {
-        posPtr[indx].fX = positions[indx << 1];
-        posPtr[indx].fY = positions[(indx << 1) + 1];
-    }
-
-    SkPaint paintCopy(paint);
-    paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding);
-    mCanvas->drawPosText(text, count, posPtr, paintCopy);
-
-    delete[] posPtr;
+    drawTextDecorations(x, y, totalAdvance, paint);
 }
 
 void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path,
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index b7a76ba..996ac8e 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -30,8 +30,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 hash_t ShadowText::hash() const {
-    uint32_t charCount = len / sizeof(char16_t);
-    uint32_t hash = JenkinsHashMix(0, len);
+    uint32_t hash = JenkinsHashMix(0, glyphCount);
     hash = JenkinsHashMix(hash, android::hash_type(radius));
     hash = JenkinsHashMix(hash, android::hash_type(textSize));
     hash = JenkinsHashMix(hash, android::hash_type(typeface));
@@ -40,10 +39,10 @@
     hash = JenkinsHashMix(hash, android::hash_type(scaleX));
     if (text) {
         hash = JenkinsHashMixShorts(
-            hash, reinterpret_cast<const uint16_t*>(text), charCount);
+            hash, reinterpret_cast<const uint16_t*>(text), glyphCount);
     }
     if (positions) {
-        for (uint32_t i = 0; i < charCount * 2; i++) {
+        for (uint32_t i = 0; i < glyphCount * 2; i++) {
             hash = JenkinsHashMix(hash, android::hash_type(positions[i]));
         }
     }
@@ -51,7 +50,7 @@
 }
 
 int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) {
-    int deltaInt = int(lhs.len) - int(rhs.len);
+    int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount);
     if (deltaInt != 0) return deltaInt;
 
     deltaInt = lhs.flags - rhs.flags;
@@ -76,7 +75,7 @@
         if (!lhs.text) return -1;
         if (!rhs.text) return +1;
 
-        deltaInt = memcmp(lhs.text, rhs.text, lhs.len);
+        deltaInt = memcmp(lhs.text, rhs.text, lhs.glyphCount * sizeof(glyph_t));
         if (deltaInt != 0) return deltaInt;
     }
 
@@ -84,7 +83,7 @@
         if (!lhs.positions) return -1;
         if (!rhs.positions) return +1;
 
-        return memcmp(lhs.positions, rhs.positions, lhs.len << 2);
+        return memcmp(lhs.positions, rhs.positions, lhs.glyphCount << 1);
     }
 
     return 0;
@@ -168,16 +167,16 @@
     mCache.clear();
 }
 
-ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
-        int numGlyphs, float radius, const float* positions) {
-    ShadowText entry(paint, radius, len, text, positions);
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* glyphs, int numGlyphs,
+        float radius, const float* positions) {
+    ShadowText entry(paint, radius, numGlyphs * 2, glyphs, positions);
     ShadowTexture* texture = mCache.get(entry);
 
     if (!texture) {
         SkPaint paintCopy(*paint);
         paintCopy.setTextAlign(SkPaint::kLeft_Align);
-        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0,
-                len, numGlyphs, radius, positions);
+        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs,
+                radius, positions);
 
         if (!shadow.image) {
             return nullptr;
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index caf089f..c4f3c5d 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -34,14 +34,14 @@
 class FontRenderer;
 
 struct ShadowText {
-    ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
+    ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr),
             flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) {
     }
 
     // len is the number of bytes in text
-    ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
+    ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText,
             const float* positions):
-            len(len), radius(radius), positions(positions) {
+            glyphCount(glyphCount), radius(radius), positions(positions) {
         // TODO: Propagate this through the API, we should not cast here
         text = (const char16_t*) srcText;
 
@@ -73,17 +73,16 @@
     }
 
     void copyTextLocally() {
-        uint32_t charCount = len / sizeof(char16_t);
-        str.setTo((const char16_t*) text, charCount);
+        str.setTo((const char16_t*) text, glyphCount);
         text = str.string();
         if (positions != nullptr) {
             positionsCopy.clear();
-            positionsCopy.appendArray(positions, charCount * 2);
+            positionsCopy.appendArray(positions, glyphCount * 2);
             positions = positionsCopy.array();
         }
     }
 
-    uint32_t len;
+    uint32_t glyphCount;
     float radius;
     float textSize;
     SkTypeface* typeface;
@@ -136,7 +135,7 @@
      */
     void operator()(ShadowText& text, ShadowTexture*& texture) override;
 
-    ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
+    ShadowTexture* get(const SkPaint* paint, const char* text,
             int numGlyphs, float radius, const float* positions);
 
     /**
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index d680f99..dc82041 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -291,20 +291,18 @@
     return cachedGlyph;
 }
 
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char *text,
             int numGlyphs, int x, int y, const float* positions) {
-    render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr,
+    render(paint, text, numGlyphs, x, y, FRAMEBUFFER, nullptr,
             0, 0, nullptr, positions);
 }
 
-void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
-        int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
-    if (numGlyphs == 0 || text == nullptr || len == 0) {
+void Font::render(const SkPaint* paint, const char *text, int numGlyphs,
+        const SkPath* path, float hOffset, float vOffset) {
+    if (numGlyphs == 0 || text == nullptr) {
         return;
     }
 
-    text += start;
-
     int glyphsCount = 0;
     SkFixed prevRsbDelta = 0;
 
@@ -317,7 +315,7 @@
     float pathLength = SkScalarToFloat(measure.getLength());
 
     if (paint->getTextAlign() != SkPaint::kLeft_Align) {
-        float textWidth = SkScalarToFloat(paint->measureText(text, len));
+        float textWidth = SkScalarToFloat(paint->measureText(text, numGlyphs * 2));
         float pathOffset = pathLength;
         if (paint->getTextAlign() == SkPaint::kCenter_Align) {
             textWidth *= 0.5f;
@@ -347,14 +345,14 @@
     }
 }
 
-void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const char* text,
         int numGlyphs, Rect *bounds, const float* positions) {
     if (bounds == nullptr) {
         ALOGE("No return rectangle provided to measure text");
         return;
     }
     bounds->set(1e6, -1e6, -1e6, 1e6);
-    render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
+    render(paint, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions);
 }
 
 void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
@@ -378,10 +376,10 @@
     }
 }
 
-void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char* text,
         int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
         uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
-    if (numGlyphs == 0 || text == nullptr || len == 0) {
+    if (numGlyphs == 0 || text == nullptr) {
         return;
     }
 
@@ -395,7 +393,6 @@
     };
     RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform];
 
-    text += start;
     int glyphsCount = 0;
 
     while (glyphsCount < numGlyphs) {
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index 3119d73..59518a1 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -82,10 +82,10 @@
 
     ~Font();
 
-    void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char* text,
             int numGlyphs, int x, int y, const float* positions);
 
-    void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char* text,
             int numGlyphs, const SkPath* path, float hOffset, float vOffset);
 
     const Font::FontDescription& getDescription() const {
@@ -113,11 +113,11 @@
 
     void precache(const SkPaint* paint, const char* text, int numGlyphs);
 
-    void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+    void render(const SkPaint* paint, const char *text,
             int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
             uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
 
-    void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+    void measure(const SkPaint* paint, const char* text,
             int numGlyphs, Rect *bounds, const float* positions);
 
     void invalidateTextureCache(CacheTexture* cacheTexture = nullptr);
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index 7a62037..4be1f99 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -23,7 +23,7 @@
 #include "DisplayListCanvas.h"
 #endif
 #include "microbench/MicroBench.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index b24858e..eea0c7f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -21,7 +21,7 @@
 #include "OpReorderer.h"
 #include "RecordedOp.h"
 #include "RecordingCanvas.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
 #include "microbench/MicroBench.h"
 
 #include <vector>
diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h
index e16310e..3f87d7f 100644
--- a/libs/hwui/tests/Benchmark.h
+++ b/libs/hwui/tests/Benchmark.h
@@ -16,6 +16,8 @@
 #ifndef TESTS_BENCHMARK_H
 #define TESTS_BENCHMARK_H
 
+#include "TestScene.h"
+
 #include <string>
 #include <vector>
 
@@ -26,12 +28,17 @@
     int count;
 };
 
-typedef void (*BenchmarkFunctor)(const BenchmarkOptions&);
+typedef test::TestScene* (*CreateScene)(const BenchmarkOptions&);
+
+template <class T>
+test::TestScene* simpleCreateScene(const BenchmarkOptions&) {
+    return new T();
+}
 
 struct BenchmarkInfo {
     std::string name;
     std::string description;
-    BenchmarkFunctor functor;
+    CreateScene createScene;
 };
 
 class Benchmark {
diff --git a/libs/hwui/tests/TestScene.h b/libs/hwui/tests/TestScene.h
new file mode 100644
index 0000000..b5d8954
--- /dev/null
+++ b/libs/hwui/tests/TestScene.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTS_TESTSCENE_H
+#define TESTS_TESTSCENE_H
+
+namespace android {
+namespace uirenderer {
+class RenderNode;
+
+#if HWUI_NEW_OPS
+class RecordingCanvas;
+typedef RecordingCanvas TestCanvas;
+#else
+class DisplayListCanvas;
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+namespace test {
+
+class TestScene {
+public:
+    virtual ~TestScene() {}
+    virtual void createContent(int width, int height, TestCanvas& renderer) = 0;
+    virtual void doFrame(int frameNr) = 0;
+};
+
+} // namespace test
+} // namespace uirenderer
+} // namespace android
+
+#endif /* TESTS_TESTSCENE_H */
diff --git a/libs/hwui/tests/TestSceneRunner.cpp b/libs/hwui/tests/TestSceneRunner.cpp
new file mode 100644
index 0000000..0376e10
--- /dev/null
+++ b/libs/hwui/tests/TestSceneRunner.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "AnimationContext.h"
+#include "Benchmark.h"
+#include "RenderNode.h"
+#include "TestContext.h"
+#include "scenes/TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "renderthread/RenderTask.h"
+
+#include <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+        return new AnimationContext(clock);
+    }
+};
+
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts) {
+    // Switch to the real display
+    gDisplay = getBuiltInDisplay();
+
+    std::unique_ptr<TestScene> scene(info.createScene(opts));
+
+    TestContext testContext;
+
+    // create the native surface
+    const int width = gDisplay.w;
+    const int height = gDisplay.h;
+    sp<Surface> surface = testContext.surface();
+
+    sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height,
+            [&scene, width, height](RenderProperties& props, TestCanvas& canvas) {
+        props.setClipToBounds(false);
+        scene->createContent(width, height, canvas);
+    });
+
+    ContextFactory factory;
+    std::unique_ptr<RenderProxy> proxy(new RenderProxy(false,
+            rootNode.get(), &factory));
+    proxy->loadSystemProperties();
+    proxy->initialize(surface);
+    float lightX = width / 2.0;
+    proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
+    proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
+
+    // Do a few cold runs then reset the stats so that the caches are all hot
+    for (int i = 0; i < 3; i++) {
+        testContext.waitForVsync();
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        proxy->syncAndDrawFrame();
+    }
+    proxy->resetProfileInfo();
+
+    for (int i = 0; i < opts.count; i++) {
+        testContext.waitForVsync();
+
+        ATRACE_NAME("UI-Draw Frame");
+        nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+        UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+        scene->doFrame(i);
+        proxy->syncAndDrawFrame();
+    }
+
+    proxy->dumpProfileInfo(STDOUT_FILENO, 0);
+}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
deleted file mode 100644
index 81bf9ed..0000000
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cutils/log.h>
-#include <gui/Surface.h>
-#include <ui/PixelFormat.h>
-
-#include <AnimationContext.h>
-#include <DisplayListCanvas.h>
-#include <RecordingCanvas.h>
-#include <RenderNode.h>
-#include <renderthread/RenderProxy.h>
-#include <renderthread/RenderTask.h>
-#include <unit_tests/TestUtils.h>
-
-#include "Benchmark.h"
-#include "TestContext.h"
-
-#include "protos/hwui.pb.h"
-
-#include <stdio.h>
-#include <unistd.h>
-#include <getopt.h>
-#include <vector>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-using namespace android::uirenderer::test;
-
-#if HWUI_NEW_OPS
-typedef RecordingCanvas TestCanvas;
-#else
-typedef DisplayListCanvas TestCanvas;
-#endif
-
-
-class ContextFactory : public IContextFactory {
-public:
-    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
-        return new AnimationContext(clock);
-    }
-};
-
-static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
-    TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
-    contentCallback(canvas);
-    node.setStagingDisplayList(canvas.finishRecording());
-}
-
-class TreeContentAnimation {
-public:
-    virtual ~TreeContentAnimation() {}
-    int frameCount = 150;
-    virtual int getFrameCount() { return frameCount; }
-    virtual void setFrameCount(int fc) {
-        if (fc > 0) {
-            frameCount = fc;
-        }
-    }
-    virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
-    virtual void doFrame(int frameNr) = 0;
-
-    template <class T>
-    static void run(const BenchmarkOptions& opts) {
-        // Switch to the real display
-        gDisplay = getBuiltInDisplay();
-
-        T animation;
-        animation.setFrameCount(opts.count);
-
-        TestContext testContext;
-
-        // create the native surface
-        const int width = gDisplay.w;
-        const int height = gDisplay.h;
-        sp<Surface> surface = testContext.surface();
-
-        RenderNode* rootNode = new RenderNode();
-        rootNode->incStrong(nullptr);
-        rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
-        rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        rootNode->mutateStagingProperties().setClipToBounds(false);
-        rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
-
-        ContextFactory factory;
-        std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
-        proxy->loadSystemProperties();
-        proxy->initialize(surface);
-        float lightX = width / 2.0;
-        proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
-        proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
-
-        recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
-            animation.createContent(width, height, &canvas); //TODO: no&
-        });
-
-        // Do a few cold runs then reset the stats so that the caches are all hot
-        for (int i = 0; i < 3; i++) {
-            testContext.waitForVsync();
-            proxy->syncAndDrawFrame();
-        }
-        proxy->resetProfileInfo();
-
-        for (int i = 0; i < animation.getFrameCount(); i++) {
-            testContext.waitForVsync();
-
-            ATRACE_NAME("UI-Draw Frame");
-            nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
-            UiFrameInfoBuilder(proxy->frameInfo())
-                    .setVsync(vsync, vsync);
-            animation.doFrame(i);
-            proxy->syncAndDrawFrame();
-        }
-
-        proxy->dumpProfileInfo(STDOUT_FILENO, 0);
-        rootNode->decStrong(nullptr);
-    }
-};
-
-class ShadowGridAnimation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        recordNode(*node, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _ShadowGrid(BenchmarkInfo{
-    "shadowgrid",
-    "A grid of rounded rects that cast a shadow. Simplified scenario of an "
-    "Android TV-style launcher interface. High CPU/GPU load.",
-    TreeContentAnimation::run<ShadowGridAnimation>
-});
-
-class ShadowGrid2Animation : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
-            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
-                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        for (size_t ci = 0; ci < cards.size(); ci++) {
-            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
-            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
-            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        }
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->mutateStagingProperties().setElevation(dp(16));
-        node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
-        node->mutateStagingProperties().mutableOutline().setShouldClip(true);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
-        recordNode(*node, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _ShadowGrid2(BenchmarkInfo{
-    "shadowgrid2",
-    "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
-    "variant of shadowgrid. Very high CPU load, high GPU load.",
-    TreeContentAnimation::run<ShadowGrid2Animation>
-});
-
-class RectGridAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
-            SkRegion region;
-            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
-                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
-                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
-                }
-            }
-
-            SkPaint paint;
-            paint.setColor(0xff00ffff);
-            canvas.drawRegion(region, paint);
-        });
-        canvas->drawRenderNode(card.get());
-
-        canvas->insertReorderBarrier(false);
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _RectGrid(BenchmarkInfo{
-    "rectgrid",
-    "A dense grid of 1x1 rects that should visually look like a single rect. "
-    "Low CPU/GPU load.",
-    TreeContentAnimation::run<RectGridAnimation>
-});
-
-class OvalAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-        canvas->insertReorderBarrier(true);
-
-        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setColor(0xFF000000);
-            canvas.drawOval(0, 0, 200, 200, paint);
-        });
-        canvas->drawRenderNode(card.get());
-
-        canvas->insertReorderBarrier(false);
-    }
-
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _Oval(BenchmarkInfo{
-    "oval",
-    "Draws 1 oval.",
-    TreeContentAnimation::run<OvalAnimation>
-});
-
-class PartialDamageTest : public TreeContentAnimation {
-public:
-    std::vector< sp<RenderNode> > cards;
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        static SkColor COLORS[] = {
-                0xFFF44336,
-                0xFF9C27B0,
-                0xFF2196F3,
-                0xFF4CAF50,
-        };
-
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-
-        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
-            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
-                sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
-                        COLORS[static_cast<int>((y / dp(116))) % 4]);
-                canvas->drawRenderNode(card.get());
-                cards.push_back(card);
-            }
-        }
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
-        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
-        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
-            canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
-                    SkXfermode::kSrcOver_Mode);
-        });
-    }
-
-    static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
-        int startA = (start >> 24) & 0xff;
-        int startR = (start >> 16) & 0xff;
-        int startG = (start >> 8) & 0xff;
-        int startB = start & 0xff;
-
-        int endA = (end >> 24) & 0xff;
-        int endR = (end >> 16) & 0xff;
-        int endG = (end >> 8) & 0xff;
-        int endB = end & 0xff;
-
-        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
-                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
-                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
-                (int)((startB + (int)(fraction * (endB - startB))));
-    }
-private:
-    sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
-        sp<RenderNode> node = new RenderNode();
-        node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
-        node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
-        recordNode(*node, [color](TestCanvas& canvas) {
-            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
-        });
-        return node;
-    }
-};
-static Benchmark _PartialDamage(BenchmarkInfo{
-    "partialdamage",
-    "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
-    "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
-    "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
-    TreeContentAnimation::run<PartialDamageTest>
-});
-
-
-class SaveLayerAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = new RenderNode();
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-
-        card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-        recordNode(*card, [](TestCanvas& canvas) {
-            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
-            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
-            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
-            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
-            canvas.restore();
-            canvas.restore();
-        });
-
-        canvas->drawRenderNode(card.get());
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _SaveLayer(BenchmarkInfo{
-    "savelayer",
-    "A nested pair of clipped saveLayer operations. "
-    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
-    TreeContentAnimation::run<SaveLayerAnimation>
-});
-
-
-class HwLayerAnimation : public TreeContentAnimation {
-public:
-    sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
-        canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
-    }, TestUtils::getHwLayerSetupCallback());
-    void createContent(int width, int height, TestCanvas* canvas) override {
-        canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-        canvas->drawRenderNode(card.get());
-    }
-    void doFrame(int frameNr) override {
-        int curFrame = frameNr % 150;
-        card->mutateStagingProperties().setTranslationX(curFrame);
-        card->mutateStagingProperties().setTranslationY(curFrame);
-        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-    }
-};
-static Benchmark _HwLayer(BenchmarkInfo{
-    "hwlayer",
-    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
-    "Tests the hardware layer codepath.",
-    TreeContentAnimation::run<HwLayerAnimation>
-});
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index aee84de..48566e8 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -43,6 +43,8 @@
 static int gRepeatCount = 1;
 static std::vector<BenchmarkInfo> gRunTests;
 
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts);
+
 static void printHelp() {
     printf("\
 USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
@@ -186,7 +188,7 @@
     opts.count = gFrameCount;
     for (int i = 0; i < gRepeatCount; i++) {
         for (auto&& test : gRunTests) {
-            test.functor(opts);
+            run(test, opts);
         }
     }
     printf("Success!\n");
diff --git a/libs/hwui/tests/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
new file mode 100644
index 0000000..e316eca
--- /dev/null
+++ b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class HwLayerAnimation;
+
+static Benchmark _HwLayer(BenchmarkInfo{
+    "hwlayer",
+    "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+    "Tests the hardware layer codepath.",
+    simpleCreateScene<HwLayerAnimation>
+});
+
+class HwLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](RenderProperties& props, TestCanvas& canvas) {
+            props.mutateLayerProperties().setType(LayerType::RenderLayer);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+        });
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+        canvas.drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
new file mode 100644
index 0000000..919a53d
--- /dev/null
+++ b/libs/hwui/tests/scenes/OvalAnimation.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class OvalAnimation;
+
+static Benchmark _Oval(BenchmarkInfo{
+    "oval",
+    "Draws 1 oval.",
+    simpleCreateScene<OvalAnimation>
+});
+
+class OvalAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setColor(0xFF000000);
+            canvas.drawOval(0, 0, 200, 200, paint);
+        });
+
+        canvas.drawRenderNode(card.get());
+        canvas.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
new file mode 100644
index 0000000..0fba4eb
--- /dev/null
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class PartialDamageAnimation;
+
+static Benchmark _PartialDamage(BenchmarkInfo{
+    "partialdamage",
+    "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
+    "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
+    "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
+    simpleCreateScene<PartialDamageAnimation>
+});
+
+class PartialDamageAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
+                sp<RenderNode> card = TestUtils::createNode(x, y,
+                        x + dp(100), y + dp(100),
+                        [color](TestCanvas& canvas) {
+                    canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+                });
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+        cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+        cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+        TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+            SkColor color = TestUtils::interpolateColor(
+                    curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
+            canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/RecentsAnimation.cpp b/libs/hwui/tests/scenes/RecentsAnimation.cpp
new file mode 100644
index 0000000..1e38d84
--- /dev/null
+++ b/libs/hwui/tests/scenes/RecentsAnimation.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class RecentsAnimation;
+
+static Benchmark _Recents(BenchmarkInfo{
+    "recents",
+    "A recents-like scrolling list of textures. "
+    "Consists of updating a texture every frame",
+    simpleCreateScene<RecentsAnimation>
+});
+
+class RecentsAnimation : public TestScene {
+public:
+    void createContent(int width, int height, TestCanvas& renderer) override {
+        static SkColor COLORS[] = {
+                0xFFF44336,
+                0xFF9C27B0,
+                0xFF2196F3,
+                0xFF4CAF50,
+        };
+
+        thumbnailSize = std::min(std::min(width, height) / 2, 720);
+        int cardsize = std::min(width, height) - dp(64);
+
+        renderer.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        renderer.insertReorderBarrier(true);
+
+        int x = dp(32);
+        for (int i = 0; i < 4; i++) {
+            int y = (height / 4) * i;
+            SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
+            thumb.eraseColor(COLORS[i]);
+            sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+            card->mutateStagingProperties().setElevation(i * dp(8));
+            renderer.drawRenderNode(card.get());
+            mThumbnail = thumb;
+            mCards.push_back(card);
+        }
+
+        renderer.insertReorderBarrier(false);
+    }
+
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < mCards.size(); ci++) {
+            mCards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            mCards[ci]->setPropertyFieldsDirty(RenderNode::Y);
+        }
+        mThumbnail.eraseColor(TestUtils::interpolateColor(
+                curFrame / 150.0f, 0xFF4CAF50, 0xFFFF5722));
+    }
+
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height,
+            const SkBitmap& thumb) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+            props.mutableOutline().setShouldClip(true);
+
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+            canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
+                    0, 0, width, height, nullptr);
+        });
+    }
+
+    SkBitmap mThumbnail;
+    std::vector< sp<RenderNode> > mCards;
+    int thumbnailSize;
+};
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
new file mode 100644
index 0000000..254f828
--- /dev/null
+++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "TestSceneBase.h"
+
+class RectGridAnimation;
+
+static Benchmark _RectGrid(BenchmarkInfo{
+    "rectgrid",
+    "A dense grid of 1x1 rects that should visually look like a single rect. "
+    "Low CPU/GPU load.",
+    simpleCreateScene<RectGridAnimation>
+});
+
+class RectGridAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        card = TestUtils::createNode(50, 50, 250, 250,
+                [](TestCanvas& canvas) {
+            canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+            SkRegion region;
+            for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+                for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+                    region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+                }
+            }
+
+            SkPaint paint;
+            paint.setColor(0xff00ffff);
+            canvas.drawRegion(region, paint);
+        });
+        canvas.drawRenderNode(card.get());
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
new file mode 100644
index 0000000..c62dd19
--- /dev/null
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class SaveLayerAnimation;
+
+static Benchmark _SaveLayer(BenchmarkInfo{
+    "savelayer",
+    "A nested pair of clipped saveLayer operations. "
+    "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+    simpleCreateScene<SaveLayerAnimation>
+});
+
+class SaveLayerAnimation : public TestScene {
+public:
+    sp<RenderNode> card;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+
+        card = TestUtils::createNode(0, 0, 200, 200,
+                [](TestCanvas& canvas) {
+            canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+            canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+            canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+            canvas.restore();
+            canvas.restore();
+        });
+
+        canvas.drawRenderNode(card.get());
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        card->mutateStagingProperties().setTranslationX(curFrame);
+        card->mutateStagingProperties().setTranslationY(curFrame);
+        card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
new file mode 100644
index 0000000..26c86aa
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGrid2Animation;
+
+static Benchmark _ShadowGrid2(BenchmarkInfo{
+    "shadowgrid2",
+    "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
+    "variant of shadowgrid. Very high CPU load, high GPU load.",
+    simpleCreateScene<ShadowGrid2Animation>
+});
+
+class ShadowGrid2Animation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
+            for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
+                sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
new file mode 100644
index 0000000..ee3c590
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGridAnimation;
+
+static Benchmark _ShadowGrid(BenchmarkInfo{
+    "shadowgrid",
+    "A grid of rounded rects that cast a shadow. Simplified scenario of an "
+    "Android TV-style launcher interface. High CPU/GPU load.",
+    simpleCreateScene<ShadowGridAnimation>
+});
+
+class ShadowGridAnimation : public TestScene {
+public:
+    std::vector< sp<RenderNode> > cards;
+    void createContent(int width, int height, TestCanvas& canvas) override {
+        canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+        canvas.insertReorderBarrier(true);
+
+        for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+            for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+                sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+                canvas.drawRenderNode(card.get());
+                cards.push_back(card);
+            }
+        }
+
+        canvas.insertReorderBarrier(false);
+    }
+    void doFrame(int frameNr) override {
+        int curFrame = frameNr % 150;
+        for (size_t ci = 0; ci < cards.size(); ci++) {
+            cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+            cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+            cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+        }
+    }
+private:
+    sp<RenderNode> createCard(int x, int y, int width, int height) {
+        return TestUtils::createNode(x, y, x + width, y + height,
+                [width, height](RenderProperties& props, TestCanvas& canvas) {
+            props.setElevation(dp(16));
+            props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+            props.mutableOutline().setShouldClip(true);
+            canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+        });
+    }
+};
diff --git a/libs/hwui/tests/scenes/TestSceneBase.h b/libs/hwui/tests/scenes/TestSceneBase.h
new file mode 100644
index 0000000..a208509
--- /dev/null
+++ b/libs/hwui/tests/scenes/TestSceneBase.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TESTS_SCENES_TESTSCENEBASE_H
+#define TESTS_SCENES_TESTSCENEBASE_H
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "tests/Benchmark.h"
+#include "tests/TestContext.h"
+#include "tests/TestScene.h"
+#include "utils/TestUtils.h"
+
+#include <functional>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 4e00fb3..7ad2f9b 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -18,7 +18,7 @@
 
 #include <BakedOpState.h>
 #include <RecordedOp.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index 3ef329a..c6ccf4d 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/FatVector.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index ef205ec..05fd08a 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -19,7 +19,7 @@
 #include <LayerUpdateQueue.h>
 #include <RenderNode.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 0f6b249..0591db6 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <utils/LinearAllocator.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
index ba92157..de86aed 100644
--- a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -17,7 +17,7 @@
 #include <gtest/gtest.h>
 #include <renderstate/OffscreenBufferPool.h>
 
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
 
 using namespace android;
 using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07a1855..d76086c 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>
 
@@ -136,14 +136,14 @@
 }
 
 TEST(OpReorderer, simpleBatching) {
-    static int SIMPLE_BATCHING_LOOPS = 5;
+    const int LOOPS = 5;
     class SimpleBatchingTestRenderer : public TestRendererBase {
     public:
         void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override {
-            EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS);
+            EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects";
         }
         void onRectOp(const RectOp& op, const BakedOpState& state) override {
-            EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS);
+            EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps";
         }
     };
 
@@ -153,7 +153,7 @@
         // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects.
         // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group.
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-        for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) {
+        for (int i = 0; i < LOOPS; i++) {
             canvas.translate(0, 10);
             canvas.drawRect(0, 0, 10, 10, SkPaint());
             canvas.drawBitmap(bitmap, 5, 0, nullptr);
@@ -164,7 +164,35 @@
     OpReorderer reorderer(200, 200, *dl, sLightCenter);
     SimpleBatchingTestRenderer renderer;
     reorderer.replayBakedOps<TestDispatcher>(renderer);
-    EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging)
+    EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+            << "Expect number of ops = 2 * loop count"; // TODO: force no merging
+}
+
+TEST(OpReorderer, textStrikethroughBatching) {
+    const int LOOPS = 5;
+    class TextStrikethroughTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text";
+        }
+        void onTextOp(const TextOp& op, const BakedOpState& state) override {
+            EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects";
+        }
+    };
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) {
+        SkPaint textPaint;
+        textPaint.setAntiAlias(true);
+        textPaint.setTextSize(20);
+        textPaint.setStrikeThruText(true);
+        for (int i = 0; i < LOOPS; i++) {
+            TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1));
+        }
+    });
+    OpReorderer reorderer(200, 2000, *dl, sLightCenter);
+    TextStrikethroughTestRenderer renderer;
+    reorderer.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2 * LOOPS, renderer.getIndex())
+            << "Expect number of ops = 2 * loop count"; // TODO: force no merging
 }
 
 TEST(OpReorderer, renderNode) {
@@ -186,14 +214,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 +249,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 +424,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 +513,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 +534,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 +561,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 +578,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 +602,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 +630,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 +666,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 +706,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 +749,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 +763,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 +776,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 +795,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 +804,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 +813,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 +825,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 +848,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 +883,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 +913,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 +939,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 +961,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 +976,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..c23d47e 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 {
@@ -41,21 +41,110 @@
     playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); });
 }
 
-TEST(RecordingCanvas, testSimpleRectRecord) {
+TEST(RecordingCanvas, drawLines) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setStrokeWidth(20);
+        float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line
+        canvas.drawLines(&points[0], 7, paint);
+    });
+
+    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+    auto op = dl->getOps()[0];
+    ASSERT_EQ(RecordedOpId::LinesOp, op->opId);
+    EXPECT_EQ(4, ((LinesOp*)op)->floatCount)
+            << "float count must be rounded down to closest multiple of 4";
+    EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds)
+            << "unmapped bounds must be size of line, outset by 1/2 stroke width";
+}
+
+TEST(RecordingCanvas, drawRect) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(10, 20, 90, 180, SkPaint());
     });
 
+    ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op";
+    auto op = *(dl->getOps()[0]);
+    ASSERT_EQ(RecordedOpId::RectOp, op.opId);
+    EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
+    EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+}
+
+TEST(RecordingCanvas, drawText) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+    });
+
     int count = 0;
     playbackOps(*dl, [&count](const RecordedOp& op) {
         count++;
-        ASSERT_EQ(RecordedOpId::RectOp, op.opId);
-        ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect);
-        ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds);
+        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+        EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect);
+        EXPECT_TRUE(op.localMatrix.isIdentity());
+        EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25))
+                << "Op expected to be 25+ pixels wide, 10+ pixels tall";
     });
     ASSERT_EQ(1, count);
 }
 
+TEST(RecordingCanvas, drawText_strikeThruAndUnderline) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        for (int i = 0; i < 2; i++) {
+            for (int j = 0; j < 2; j++) {
+                paint.setUnderlineText(i != 0);
+                paint.setStrikeThruText(j != 0);
+                TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+            }
+        }
+    });
+
+    auto ops = dl->getOps();
+    ASSERT_EQ(8u, ops.size());
+
+    int index = 0;
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only
+
+    EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId);
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline
+    EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough
+}
+
+TEST(RecordingCanvas, drawText_forceAlignLeft) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setTextSize(20);
+        paint.setTextAlign(SkPaint::kLeft_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+        paint.setTextAlign(SkPaint::kCenter_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+        paint.setTextAlign(SkPaint::kRight_Align);
+        TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25);
+    });
+
+    int count = 0;
+    playbackOps(*dl, [&count](const RecordedOp& op) {
+        count++;
+        ASSERT_EQ(RecordedOpId::TextOp, op.opId);
+        EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign())
+                << "recorded drawText commands must force kLeft_Align on their paint";
+        EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils
+    });
+    ASSERT_EQ(3, count);
+}
+
 TEST(RecordingCanvas, backgroundAndImage) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) {
         SkBitmap bitmap;
@@ -109,7 +198,7 @@
     ASSERT_EQ(2, count);
 }
 
-TEST(RecordingCanvas, saveLayerSimple) {
+TEST(RecordingCanvas, saveLayer_simple) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag);
         canvas.drawRect(10, 20, 190, 180, SkPaint());
@@ -143,7 +232,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerViewportCrop) {
+TEST(RecordingCanvas, saveLayer_viewportCrop) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         // shouldn't matter, since saveLayer will clip to its bounds
         canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op);
@@ -167,7 +256,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerRotateUnclipped) {
+TEST(RecordingCanvas, saveLayer_rotateUnclipped) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.translate(100, 100);
@@ -193,7 +282,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, saveLayerRotateClipped) {
+TEST(RecordingCanvas, saveLayer_rotateClipped) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
         canvas.translate(100, 100);
@@ -224,7 +313,7 @@
     EXPECT_EQ(3, count);
 }
 
-TEST(RecordingCanvas, testReorderBarrier) {
+TEST(RecordingCanvas, insertReorderBarrier) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.drawRect(0, 0, 400, 400, SkPaint());
         canvas.insertReorderBarrier(true);
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
new file mode 100644
index 0000000..dd6fc36
--- /dev/null
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -0,0 +1,81 @@
+/*
+ * 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))));
+}
+
+void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text,
+        const SkPaint& inPaint, float x, float y) {
+   // copy to force TextEncoding (which JNI layer would have done)
+   SkPaint paint(inPaint);
+   paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+   SkMatrix identity;
+   identity.setIdentity();
+   SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry);
+   SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity);
+
+   float totalAdvance = 0;
+   std::vector<glyph_t> glyphs;
+   std::vector<float> positions;
+   Rect bounds;
+   while (*text != '\0') {
+       SkUnichar unichar = SkUTF8_NextUnichar(&text);
+       glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar);
+       autoCache.getCache()->unicharToGlyph(unichar);
+
+       // push glyph and its relative position
+       glyphs.push_back(glyph);
+       positions.push_back(totalAdvance);
+       positions.push_back(0);
+
+       // compute bounds
+       SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar);
+       Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight);
+       glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop);
+       bounds.unionWith(glyphBounds);
+
+       // advance next character
+       SkScalar skWidth;
+       paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL);
+       totalAdvance += skWidth;
+   }
+   bounds.translate(x, y);
+   canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y,
+           bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/utils/TestUtils.h
similarity index 75%
rename from libs/hwui/unit_tests/TestUtils.h
rename to libs/hwui/utils/TestUtils.h
index 38bafd5..f9fa242 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,12 @@
         TestTask task(rtCallback);
         renderthread::RenderThread::getInstance().queueAndWait(&task);
     }
+
+    static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+
+    static void drawTextToCanvas(TestCanvas* canvas, const char* text,
+            const SkPaint& inPaint, float x, float y);
+
 private:
     static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
         node->syncProperties();
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index d45345e..595928a 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -71,7 +71,7 @@
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
             <intent-filter>
-                <action android:name="android.provider.action.BROWSE_DOCUMENT_ROOT" />
+                <action android:name="android.provider.action.BROWSE" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="vnd.android.document/root" />
             </intent-filter>
diff --git a/packages/DocumentsUI/res/drawable/ic_root_home.xml b/packages/DocumentsUI/res/drawable/ic_root_home.xml
new file mode 100644
index 0000000..0a258ac
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_root_home.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:fillColor="#000000"
+        android:pathData="M20 6h-8l-2-2H4c-1.1 0-1.99 .9 -1.99 2L2 18c0 1.1 .9 2 2 2h16c1.1 0 2-.9
+2-2V8c0-1.1-.9-2-2-2zm-5 3c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4
+8h-8v-1c0-1.33 2.67-2 4-2s4 .67 4 2v1z" />
+    <path
+        android:pathData="M0 0h24v24H0z" />
+</vector>
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/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index e965050..4f4649c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -172,6 +172,11 @@
                     Intent.EXTRA_ALLOW_MULTIPLE, false);
         }
 
+        if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT
+                || state.action == ACTION_CREATE) {
+            state.openableOnly = intent.hasCategory(Intent.CATEGORY_OPENABLE);
+        }
+
         if (state.action == ACTION_PICK_COPY_DESTINATION) {
             state.directoryCopy = intent.getBooleanExtra(
                     Shared.EXTRA_DIRECTORY_COPY, false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index beff196..4c844c4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -297,7 +297,7 @@
 
             for (final RootInfo root : roots) {
                 final RootItem item = new RootItem(root);
-                if (root.isLibrary()) {
+                if (root.isLibrary() || root.isHome()) {
                     libraries.add(item);
                 } else {
                     others.add(item);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 49a1e66..c81d4fb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -52,6 +52,7 @@
     public boolean stackTouched;
     public boolean restored;
     public boolean directoryCopy;
+    public boolean openableOnly;
     /** Transfer mode for file copy/move operations. */
     public int transferMode;
 
@@ -119,6 +120,7 @@
         out.writeMap(dirState);
         out.writeList(selectedDocumentsForCopy);
         out.writeList(excludedAuthorities);
+        out.writeInt(openableOnly ? 1 : 0);
     }
 
     public static final Creator<State> CREATOR = new Creator<State>() {
@@ -142,6 +144,7 @@
             in.readMap(state.dirState, null);
             in.readList(state.selectedDocumentsForCopy, null);
             in.readList(state.excludedAuthorities, null);
+            state.openableOnly = in.readInt() != 0;
             return state;
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 18dd8c8..b0421b0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -660,8 +660,7 @@
                 checkNotNull(cursor, "Cursor cannot be null.");
                 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-                return mTuner.canSelectType(docMimeType)
-                        && mTuner.isDocumentEnabled(docMimeType, docFlags);
+                return mTuner.canSelectType(docMimeType, docFlags);
             }
             return true;
         }
@@ -943,7 +942,6 @@
 
         public void setSelected(boolean selected) {
             itemView.setActivated(selected);
-            itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
         }
 
         @Override
@@ -1080,8 +1078,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 +1134,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 +1154,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 +1162,6 @@
                             summary.setText(root.getDirectoryString());
                             summary.setVisibility(View.VISIBLE);
                             summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
-                            hasLine2 = true;
                         }
                     }
                 }
@@ -1187,48 +1178,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/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index a0ff165..38d3805 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -63,7 +63,7 @@
     // Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
     // A folder is enabled such that it may be double clicked, even in settings
     // when the folder itself cannot be selected. This may also be true of container types.
-    public boolean canSelectType(String docMimeType) {
+    public boolean canSelectType(String docMimeType, int docFlags) {
         return true;
     }
 
@@ -85,31 +85,44 @@
         }
 
         @Override
-        public boolean canSelectType(String docMimeType) {
-            switch (mState.action) {
-                case ACTION_OPEN:
-                case ACTION_CREATE:
-                case ACTION_GET_CONTENT:
-                    return !isDirectory(docMimeType);
-                case ACTION_OPEN_TREE:
-                    // In this case nothing *ever* is selectable...the expected user behavior is
-                    // they navigate *into* a folder, then click a confirmation button indicating
-                    // that the current directory is the directory they are picking.
-                    return false;
+        public boolean canSelectType(String docMimeType, int docFlags) {
+            if (!isDocumentEnabled(docMimeType, docFlags)) {
+                return false;
             }
+
+            if (isDirectory(docMimeType)) {
+                return false;
+            }
+
+            if (mState.action == ACTION_OPEN_TREE) {
+                // In this case nothing *ever* is selectable...the expected user behavior is
+                // they navigate *into* a folder, then click a confirmation button indicating
+                // that the current directory is the directory they are picking.
+                return false;
+            }
+
             return true;
         }
 
         @Override
         public boolean isDocumentEnabled(String docMimeType, int docFlags) {
-            // Directories are always enabled
+            // Directories are always enabled.
             if (isDirectory(docMimeType)) {
                 return true;
             }
 
-            // Read-only files are disabled when creating
-            if (mState.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
-                return false;
+            switch (mState.action) {
+                case ACTION_CREATE:
+                    // Read-only files are disabled when creating.
+                    if ((docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
+                        return false;
+                    }
+                case ACTION_OPEN:
+                case ACTION_GET_CONTENT:
+                    final boolean isVirtual = (docFlags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+                    if (isVirtual && mState.openableOnly) {
+                        return false;
+                    }
             }
 
             return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 723700d..ae5644d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -52,7 +52,7 @@
     public static final int TYPE_DOWNLOADS = 5;
     public static final int TYPE_LOCAL = 6;
     public static final int TYPE_MTP = 7;
-    public static final int TYPE_CLOUD = 8;
+    public static final int TYPE_OTHER = 8;
 
     public String authority;
     public String rootId;
@@ -168,7 +168,10 @@
         derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
 
         // TODO: remove these special case icons
-        if (isExternalStorage()) {
+        if (isHome()) {
+            derivedIcon = R.drawable.ic_root_home;
+            derivedType = TYPE_LOCAL;
+        } else if (isExternalStorage()) {
             derivedIcon = R.drawable.ic_root_sdcard;
             derivedType = TYPE_LOCAL;
         } else if (isDownloads()) {
@@ -188,7 +191,7 @@
         } else if (isMtp()) {
             derivedType = TYPE_MTP;
         } else {
-            derivedType = TYPE_CLOUD;
+            derivedType = TYPE_OTHER;
         }
     }
 
@@ -196,6 +199,13 @@
         return authority == null && rootId == null;
     }
 
+    public boolean isHome() {
+        // Note that "home" is the expected root id for the auto-created
+        // user home directory on external storage. The "home" value should
+        // match ExternalStorageProvider.ROOT_ID_HOME.
+        return isExternalStorage() && "home".equals(rootId);
+    }
+
     public boolean isExternalStorage() {
         return "com.android.externalstorage.documents".equals(authority);
     }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index ba91c83..71d8b34 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -125,6 +125,7 @@
                 "Videos",
                 "Audio",
                 "Downloads",
+                "Home",
                 ROOT_0_ID,
                 ROOT_1_ID);
     }
@@ -136,6 +137,13 @@
         mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
+    public void testRootClickSetsWindowTitle() throws Exception {
+        initTestFiles();
+
+        mBot.openRoot("Home");
+        mBot.assertWindowTitle("Home");
+    }
+
     public void testFilesList_LiveUpdate() throws Exception {
         initTestFiles();
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 5c09794..ecad061 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -16,6 +16,8 @@
 
 package com.android.documentsui;
 
+import static junit.framework.Assert.assertEquals;
+
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -80,6 +82,20 @@
         mDevice.waitForIdle();
     }
 
+    void assertWindowTitle(String expected) {
+        // Turns out the title field on a window does not have
+        // an id associated with it at runtime (which confuses the hell out of me)
+        // In code we address this via "android.R.id.title".
+        UiObject2 o = find(By.text(expected));
+        // It's a bit of a conceit that we then *assert* that the title
+        // is the value that we used to identify the UiObject2.
+        // If the preceeding lookup fails, this'll choke with an NPE.
+        // But given the issue described in the comment above, we're
+        // going to do it anyway. Because we shouldn't be looking up
+        // the uiobject by it's expected content :|
+        assertEquals(expected, o.getText());
+    }
+
     void assertHasRoots(String... labels) throws UiObjectNotFoundException {
         List<String> missing = new ArrayList<>();
         for (String label : labels) {
diff --git a/packages/ExternalStorageProvider/res/values/strings.xml b/packages/ExternalStorageProvider/res/values/strings.xml
index f1c1ade..e48436e 100644
--- a/packages/ExternalStorageProvider/res/values/strings.xml
+++ b/packages/ExternalStorageProvider/res/values/strings.xml
@@ -20,6 +20,6 @@
 
     <!-- Title for documents backend that offers internal storage. [CHAR LIMIT=24] -->
     <string name="root_internal_storage">Internal storage</string>
-    <!-- Title for documents backend that offers documents. [CHAR LIMIT=24] -->
-    <string name="root_documents">Documents</string>
+    <!-- Title for user home dir. [CHAR LIMIT=24] -->
+    <string name="root_home">Home</string>
 </resources>
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index fcd45f2..2cedc72 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -85,9 +85,11 @@
         public String docId;
         public File visiblePath;
         public File path;
+        public boolean reportAvailableBytes = true;
     }
 
     private static final String ROOT_ID_PRIMARY_EMULATED = "primary";
+    private static final String ROOT_ID_HOME = "home";
 
     private StorageManager mStorageManager;
     private Handler mHandler;
@@ -118,6 +120,7 @@
     private void updateVolumesLocked() {
         mRoots.clear();
 
+        VolumeInfo primaryVolume = null;
         final int userId = UserHandle.myUserId();
         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
         for (VolumeInfo volume : volumes) {
@@ -126,6 +129,9 @@
             final String rootId;
             final String title;
             if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
+                // save off the primary volume for subsequent "Home" dir initialization.
+                primaryVolume = volume;
+
                 // We currently only support a single emulated volume mounted at
                 // a time, and it's always considered the primary
                 rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -152,25 +158,58 @@
                 continue;
             }
 
+            final RootInfo root = new RootInfo();
+            mRoots.put(rootId, root);
+
+            root.rootId = rootId;
+            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+                    | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+
+            // Dunno when this would NOT be the case, but never hurts to be correct.
+            if (volume.isMountedWritable()) {
+                root.flags |= Root.FLAG_SUPPORTS_CREATE;
+            }
+            root.title = title;
+            if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
+                root.flags |= Root.FLAG_HAS_SETTINGS;
+            }
+            if (volume.isVisibleForRead(userId)) {
+                root.visiblePath = volume.getPathForUser(userId);
+            } else {
+                root.visiblePath = null;
+            }
+            root.path = volume.getInternalPathForUser(userId);
             try {
-                final RootInfo root = new RootInfo();
-                mRoots.put(rootId, root);
-
-                root.rootId = rootId;
-                root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
-                        | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
-                root.title = title;
-                if (volume.getType() == VolumeInfo.TYPE_PUBLIC) {
-                    root.flags |= Root.FLAG_HAS_SETTINGS;
-                }
-                if (volume.isVisibleForRead(userId)) {
-                    root.visiblePath = volume.getPathForUser(userId);
-                } else {
-                    root.visiblePath = null;
-                }
-                root.path = volume.getInternalPathForUser(userId);
                 root.docId = getDocIdForFile(root.path);
+            } catch (FileNotFoundException e) {
+                throw new IllegalStateException(e);
+            }
+        }
 
+        // Finally, if primary storage is available we add the "Home" directory,
+        // creating it as needed.
+        if (primaryVolume != null && primaryVolume.isVisible()) {
+            final RootInfo root = new RootInfo();
+            root.rootId = ROOT_ID_HOME;
+            mRoots.put(root.rootId, root);
+            root.title = getContext().getString(R.string.root_home);
+
+            // Only report bytes on *volumes*...as a matter of policy.
+            root.reportAvailableBytes = false;
+            root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_SEARCH
+                    | Root.FLAG_SUPPORTS_IS_CHILD;
+
+            // Dunno when this would NOT be the case, but never hurts to be correct.
+            if (primaryVolume.isMountedWritable()) {
+                root.flags |= Root.FLAG_SUPPORTS_CREATE;
+            }
+
+            root.visiblePath = new File(
+                    primaryVolume.getPathForUser(userId), root.rootId);
+            root.path = new File(
+                    primaryVolume.getInternalPathForUser(userId), root.rootId);
+            try {
+                root.docId = getDocIdForFile(root.path);
             } catch (FileNotFoundException e) {
                 throw new IllegalStateException(e);
             }
@@ -312,7 +351,8 @@
                 row.add(Root.COLUMN_FLAGS, root.flags);
                 row.add(Root.COLUMN_TITLE, root.title);
                 row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
-                row.add(Root.COLUMN_AVAILABLE_BYTES, root.path.getFreeSpace());
+                row.add(Root.COLUMN_AVAILABLE_BYTES,
+                        root.reportAvailableBytes ? root.path.getFreeSpace() : -1);
             }
         }
         return result;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index b9a9c24..2e96f18 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -159,6 +159,7 @@
         String key_mgmt = "";
         boolean certUsed = false;
         boolean hasWepKey = false;
+        boolean isEap = false;
         final ArrayList<String> rawLines = new ArrayList<String>();
 
         public static Network readFromStream(BufferedReader in) {
@@ -189,6 +190,9 @@
                 ssid = line;
             } else if (line.startsWith("key_mgmt=")) {
                 key_mgmt = line;
+                if (line.contains("EAP")) {
+                    isEap = true;
+                }
             } else if (line.startsWith("client_cert=")) {
                 certUsed = true;
             } else if (line.startsWith("ca_cert=")) {
@@ -197,6 +201,8 @@
                 certUsed = true;
             } else if (line.startsWith("wep_")) {
                 hasWepKey = true;
+            } else if (line.startsWith("eap=")) {
+                isEap = true;
             }
         }
 
@@ -325,6 +331,13 @@
                                     continue;
                                 }
                             }
+                            // Don't propagate EAP network definitions
+                            if (net.isEap) {
+                                if (DEBUG_BACKUP) {
+                                    Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt);
+                                }
+                                continue;
+                            }
                             if (! mKnownNetworks.contains(net)) {
                                 if (DEBUG_BACKUP) {
                                     Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
@@ -353,6 +366,12 @@
                     continue;
                 }
 
+                if (net.isEap) {
+                    // Similarly, omit EAP network definitions to avoid propagating
+                    // controlled enterprise network definitions.
+                    continue;
+                }
+
                 net.write(w);
             }
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 8b1caf9..3971706 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -904,12 +904,12 @@
             }
         }
 
-        // Enforce what the calling package can mutate the system settings.
-        enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, runAsUserId);
-
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(runAsUserId);
 
+        // Enforce what the calling package can mutate the system settings.
+        enforceRestrictedSystemSettingsMutationForCallingPackage(operation, name, callingUserId);
+
         // Determine the owning user as some profile settings are cloned from the parent.
         final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
 
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/RecentsComponent.java b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
index 9a4cd93..f5f6acf 100644
--- a/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/RecentsComponent.java
@@ -31,5 +31,20 @@
     /**
      * Docks the top-most task and opens recents.
      */
-    void dockTopTask();
+    void dockTopTask(boolean draggingInRecents);
+
+    /**
+     * Called during a drag-from-navbar-in gesture.
+     *
+     * @param distanceFromTop the distance of the current drag in gesture from the top of the
+     *                        screen
+     */
+    void onDraggingInRecents(float distanceFromTop);
+
+    /**
+     * Called when the gesture to drag in recents ended.
+     *
+     * @param velocity the velocity of the finger when releasing it in pixels per second
+     */
+    void onDraggingInRecentsEnded(float velocity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 949efc5..19e299254 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -48,12 +48,12 @@
             com.android.systemui.keyguard.KeyguardViewMediator.class,
             com.android.systemui.recents.Recents.class,
             com.android.systemui.volume.VolumeUI.class,
+            Divider.class,
             com.android.systemui.statusbar.SystemBars.class,
             com.android.systemui.usb.StorageNotification.class,
             com.android.systemui.power.PowerUI.class,
             com.android.systemui.media.RingtonePlayer.class,
             com.android.systemui.keyboard.KeyboardUI.class,
-            Divider.class
     };
 
     /**
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/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
index 79eca30d..7cfe38e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -24,8 +24,10 @@
 oneway interface IRecentsNonSystemUserCallbacks {
     void preloadRecents();
     void cancelPreloadingRecents();
-    void showRecents(boolean triggeredFromAltTab);
+    void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents);
     void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
     void toggleRecents();
     void onConfigurationChanged();
+    void onDraggingInRecents(float distanceFromTop);
+    void onDraggingInRecentsEnded(float velocity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 3806b46..348bd87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -73,6 +73,7 @@
 
     private Handler mHandler;
     private RecentsImpl mImpl;
+    private int mDraggingInRecentsCurrentUser;
 
     // Only For system user, this is the callbacks instance we return to each secondary user
     private RecentsSystemUser mSystemUserCallbacks;
@@ -213,14 +214,14 @@
 
         int currentUser = sSystemServicesProxy.getCurrentUser();
         if (sSystemServicesProxy.isSystemUser(currentUser)) {
-            mImpl.showRecents(triggeredFromAltTab);
+            mImpl.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
         } else {
             if (mSystemUserCallbacks != null) {
                 IRecentsNonSystemUserCallbacks callbacks =
                         mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
                 if (callbacks != null) {
                     try {
-                        callbacks.showRecents(triggeredFromAltTab);
+                        callbacks.showRecents(triggeredFromAltTab, false /* draggingInRecents */);
                     } catch (RemoteException e) {
                         Log.e(TAG, "Callback failed", e);
                     }
@@ -361,8 +362,57 @@
     }
 
     @Override
-    public void dockTopTask() {
-        mImpl.dockTopTask();
+    public void dockTopTask(boolean draggingInRecents) {
+        mImpl.dockTopTask(draggingInRecents);
+        if (draggingInRecents) {
+            mDraggingInRecentsCurrentUser = sSystemServicesProxy.getCurrentUser();
+        }
+    }
+
+    @Override
+    public void onDraggingInRecents(float distanceFromTop) {
+        if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+            mImpl.onDraggingInRecents(distanceFromTop);
+        } else {
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+                                mDraggingInRecentsCurrentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onDraggingInRecents(distanceFromTop);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: "
+                            + mDraggingInRecentsCurrentUser);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onDraggingInRecentsEnded(float velocity) {
+        if (sSystemServicesProxy.isSystemUser(mDraggingInRecentsCurrentUser)) {
+            mImpl.onDraggingInRecentsEnded(velocity);
+        } else {
+            if (mSystemUserCallbacks != null) {
+                IRecentsNonSystemUserCallbacks callbacks =
+                        mSystemUserCallbacks.getNonSystemUserRecentsForUser(
+                                mDraggingInRecentsCurrentUser);
+                if (callbacks != null) {
+                    try {
+                        callbacks.onDraggingInRecentsEnded(velocity);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Callback failed", e);
+                    }
+                } else {
+                    Log.e(TAG, "No SystemUI callbacks found for user: "
+                            + mDraggingInRecentsCurrentUser);
+                }
+            }
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 85a2eda..618eb6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -46,6 +46,8 @@
 import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
 import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.ForegroundThread;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -140,6 +142,7 @@
     RecentsAppWidgetHost mAppWidgetHost;
     boolean mBootCompleted;
     boolean mCanReuseTaskStackViews = true;
+    boolean mDraggingInRecents;
 
     // Task launching
     Rect mSearchBarBounds = new Rect();
@@ -164,14 +167,13 @@
         public void run() {
             // When this fires, then the user has not released alt-tab for at least
             // FAST_ALT_TAB_DELAY_MS milliseconds
-            showRecents(mTriggeredFromAltTab);
+            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */);
         }
     });
 
     Bitmap mThumbnailTransitionBitmapCache;
     Task mThumbnailTransitionBitmapCacheKey;
 
-
     public RecentsImpl(Context context) {
         mContext = context;
         mHandler = new Handler();
@@ -248,8 +250,9 @@
     }
 
     @Override
-    public void showRecents(boolean triggeredFromAltTab) {
+    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents) {
         mTriggeredFromAltTab = triggeredFromAltTab;
+        mDraggingInRecents = draggingInRecents;
         if (mFastAltTabTrigger.hasTriggered()) {
             // We are calling this from the doze trigger, so just fall through to show Recents
             mFastAltTabTrigger.resetTrigger();
@@ -315,6 +318,7 @@
             return;
         }
 
+        mDraggingInRecents = false;
         mTriggeredFromAltTab = false;
 
         try {
@@ -385,6 +389,16 @@
         // Do nothing
     }
 
+    @Override
+    public void onDraggingInRecents(float distanceFromTop) {
+        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
+    }
+
+    @Override
+    public void onDraggingInRecentsEnded(float velocity) {
+        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
+    }
+
     /**
      * Transitions to the next recent task in the stack.
      */
@@ -521,13 +535,13 @@
         showRelativeAffiliatedTask(false);
     }
 
-    public void dockTopTask() {
+    public void dockTopTask(boolean draggingInRecents) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
         if (topTask != null && !SystemServicesProxy.isHomeStack(topTask.stackId)) {
             ssp.startTaskInDockedMode(topTask.id,
                     ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
-            showRecents(false /* triggeredFromAltTab */);
+            showRecents(false /* triggeredFromAltTab */, draggingInRecents);
         }
     }
 
@@ -856,7 +870,8 @@
         launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
         launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
         launchState.launchedHasConfigurationChanged = false;
-        launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID;
+        launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
+                || mDraggingInRecents;
 
         Intent intent = new Intent();
         intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index b091f05..d4d13f0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -502,6 +502,19 @@
         queueEvent(event);
     }
 
+    /**
+     * If this method is called from the main thread, it will be handled directly. If this method
+     * is not called from the main thread, it will be posted onto the main thread.
+     */
+    public void sendOntoMainThread(Event event) {
+        long callingThreadId = Thread.currentThread().getId();
+        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+            post(event);
+        } else {
+            send(event);
+        }
+    }
+
     /** Prevent post()ing an InterprocessEvent */
     @Deprecated
     public void post(InterprocessEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
new file mode 100644
index 0000000..9be8eb1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEndedEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user finished dragging in recents.
+ */
+public class DraggingInRecentsEndedEvent extends Event {
+
+    public final float velocity;
+
+    public DraggingInRecentsEndedEvent(float velocity) {
+        this.velocity = velocity;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
new file mode 100644
index 0000000..5e8bfd4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/DraggingInRecentsEvent.java
@@ -0,0 +1,15 @@
+package com.android.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus.Event;
+
+/**
+ * This event is sent when the user changed how far they are dragging in recents.
+ */
+public class DraggingInRecentsEvent extends Event {
+
+    public final float distanceFromTop;
+
+    public DraggingInRecentsEvent(float distanceFromTop) {
+        this.distanceFromTop = distanceFromTop;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e37b7dc..d18389f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -41,6 +41,8 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
+import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -542,6 +544,15 @@
         }
     }
 
+    public final void onBusEvent(DraggingInRecentsEvent event) {
+        setStackViewVisibility(View.VISIBLE);
+        setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
+    }
+
+    public final void onBusEvent(DraggingInRecentsEndedEvent event) {
+        animate().translationY(0f);
+    }
+
     /**
      * Updates the dock region to match the specified dock state.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index dd894ce..50e010f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -32,6 +32,7 @@
     private static final String TAG = "Divider";
     private int mDividerWindowWidth;
     private DividerWindowManager mWindowManager;
+    private DividerView mView;
 
     @Override
     public void start() {
@@ -39,6 +40,7 @@
         mDividerWindowWidth = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_thickness);
         update(mContext.getResources().getConfiguration());
+        putComponent(Divider.class, this);
     }
 
     @Override
@@ -47,14 +49,18 @@
         update(newConfig);
     }
 
+    public DividerView getView() {
+        return mView;
+    }
+
     private void addDivider(Configuration configuration) {
-        DividerView view = (DividerView)
+        mView = (DividerView)
                 LayoutInflater.from(mContext).inflate(R.layout.docked_stack_divider, null);
         final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
         final int width = landscape ? mDividerWindowWidth : MATCH_PARENT;
         final int height = landscape ? MATCH_PARENT : mDividerWindowWidth;
-        mWindowManager.add(view, width, height);
-        view.setWindowManager(mWindowManager);
+        mWindowManager.add(mView, width, height);
+        mView.setWindowManager(mWindowManager);
     }
 
     private void removeDivider() {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
index 5f983c5..69e90cc 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerSnapAlgorithm.java
@@ -98,10 +98,10 @@
 
         // TODO: Better calculation
         targets.add(new SnapTarget(-mDividerSize, SnapTarget.FLAG_DISMISS_START));
-        targets.add(new SnapTarget((int) (0.35f * dividerMax) - mDividerSize / 2,
+        targets.add(new SnapTarget((int) (0.38f * dividerMax) - mDividerSize / 2,
                 SnapTarget.FLAG_NONE));
         targets.add(new SnapTarget(dividerMax / 2 - mDividerSize / 2, SnapTarget.FLAG_NONE));
-        targets.add(new SnapTarget((int) (0.65f * dividerMax) - mDividerSize / 2,
+        targets.add(new SnapTarget((int) (0.62f * dividerMax) - mDividerSize / 2,
                 SnapTarget.FLAG_NONE));
         targets.add(new SnapTarget(dividerMax, SnapTarget.FLAG_DISMISS_END));
         return targets;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index a520a33..3e317ff 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -127,6 +127,28 @@
         mWindowManager = windowManager;
     }
 
+    public WindowManagerProxy getWindowManagerProxy() {
+        return mWindowManagerProxy;
+    }
+
+    public boolean startDragging() {
+        mDockSide = mWindowManagerProxy.getDockSide();
+        if (mDockSide != WindowManager.DOCKED_INVALID) {
+            mWindowManagerProxy.setResizing(true);
+            mWindowManager.setSlippery(false);
+            liftBackground();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void stopDragging(int position, float velocity) {
+        fling(position, velocity);
+        mWindowManager.setSlippery(true);
+        releaseBackground();
+    }
+
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         convertToScreenCoordinates(event);
@@ -138,20 +160,13 @@
                 mStartX = (int) event.getX();
                 mStartY = (int) event.getY();
                 getLocationOnScreen(mTempInt2);
-                mDockSide = mWindowManagerProxy.getDockSide();
+                boolean result = startDragging();
                 if (isHorizontalDivision()) {
                     mStartPosition = mTempInt2[1] + mDividerInsets;
                 } else {
                     mStartPosition = mTempInt2[0] + mDividerInsets;
                 }
-                if (mDockSide != WindowManager.DOCKED_INVALID) {
-                    mWindowManagerProxy.setResizing(true);
-                    mWindowManager.setSlippery(false);
-                    liftBackground();
-                    return true;
-                } else {
-                    return false;
-                }
+                return result;
             case MotionEvent.ACTION_MOVE:
                 mVelocityTracker.addMovement(event);
                 int x = (int) event.getX();
@@ -168,10 +183,9 @@
                 y = (int) event.getRawY();
 
                 mVelocityTracker.computeCurrentVelocity(1000);
-                fling(x, y, mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
-                mWindowManager.setSlippery(true);
-                releaseBackground();
+                int position = calculatePosition(x, y);
+                stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
+                        : mVelocityTracker.getXVelocity());
                 break;
         }
         return true;
@@ -181,9 +195,7 @@
         event.setLocation(event.getRawX(), event.getRawY());
     }
 
-    private void fling(int x, int y, float xVelocity, float yVelocity) {
-        int position = calculatePosition(x, y);
-        float velocity = isHorizontalDivision() ? yVelocity : xVelocity;
+    private void fling(int position, float velocity) {
         final SnapTarget snapTarget = new DividerSnapAlgorithm(getContext(), mFlingAnimationUtils,
                 mDividerSize, isHorizontalDivision()).calculateSnapTarget(position, velocity);
 
@@ -277,9 +289,8 @@
         return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
     }
 
-    private boolean isHorizontalDivision() {
-        return mDockSide == WindowManager.DOCKED_TOP
-                || mDockSide == WindowManager.DOCKED_BOTTOM;
+    public boolean isHorizontalDivision() {
+        return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
     }
 
     private int calculateXPosition(int touchX) {
@@ -290,7 +301,7 @@
         return mStartPosition + touchY - mStartY;
     }
 
-    private void resizeStack(int position) {
+    public void resizeStack(int position) {
         mTmpRect.set(0, 0, mDisplayWidth, mDisplayHeight);
         switch (mDockSide) {
             case WindowManager.DOCKED_LEFT:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
new file mode 100644
index 0000000..0466c14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemProperties;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.BaseStatusBar;
+
+import static android.view.WindowManager.*;
+
+/**
+ * Class to detect gestures on the navigation bar.
+ */
+public class NavigationBarGestureHelper extends GestureDetector.SimpleOnGestureListener {
+
+    private static final String DOCK_WINDOW_GESTURE_ENABLED_PROP = "persist.dock_gesture_enabled";
+
+    /**
+     * When dragging from the navigation bar, we drag in recents.
+     */
+    private static final int DRAG_MODE_RECENTS = 0;
+
+    /**
+     * When dragging from the navigation bar, we drag the divider.
+     */
+    private static final int DRAG_MODE_DIVIDER = 1;
+
+    private RecentsComponent mRecentsComponent;
+    private Divider mDivider;
+    private boolean mIsVertical;
+    private boolean mIsRTL;
+
+    private final GestureDetector mTaskSwitcherDetector;
+    private final int mScrollTouchSlop;
+    private final int mTouchSlop;
+    private final int mMinFlingVelocity;
+    private int mTouchDownX;
+    private int mTouchDownY;
+    private VelocityTracker mVelocityTracker;
+
+    private boolean mDockWindowEnabled;
+    private boolean mDockWindowTouchSlopExceeded;
+    private int mDragMode;
+
+    public NavigationBarGestureHelper(Context context) {
+        ViewConfiguration configuration = ViewConfiguration.get(context);
+        Resources r = context.getResources();
+        mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+        mTaskSwitcherDetector = new GestureDetector(context, this);
+        mDockWindowEnabled = SystemProperties.getBoolean(DOCK_WINDOW_GESTURE_ENABLED_PROP, false);
+    }
+
+    public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+        mRecentsComponent = recentsComponent;
+        mDivider = divider;
+    }
+
+    public void setBarState(boolean isVertical, boolean isRTL) {
+        mIsVertical = isVertical;
+        mIsRTL = isRTL;
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        // If we move more than a fixed amount, then start capturing for the
+        // task switcher detector
+        mTaskSwitcherDetector.onTouchEvent(event);
+        int action = event.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN: {
+                mTouchDownX = (int) event.getX();
+                mTouchDownY = (int) event.getY();
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                int x = (int) event.getX();
+                int y = (int) event.getY();
+                int xDiff = Math.abs(x - mTouchDownX);
+                int yDiff = Math.abs(y - mTouchDownY);
+                boolean exceededTouchSlop = !mIsVertical
+                        ? xDiff > mScrollTouchSlop && xDiff > yDiff
+                        : yDiff > mScrollTouchSlop && yDiff > xDiff;
+                if (exceededTouchSlop) {
+                    return true;
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                break;
+        }
+        return mDockWindowEnabled && interceptDockWindowEvent(event);
+    }
+
+    private boolean interceptDockWindowEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                handleDragActionDownEvent(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                return handleDragActionMoveEvent(event);
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                handleDragActionUpEvent(event);
+                break;
+        }
+        return false;
+    }
+
+    private boolean handleDockWindowEvent(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                handleDragActionDownEvent(event);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                handleDragActionMoveEvent(event);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                handleDragActionUpEvent(event);
+                break;
+        }
+        return true;
+    }
+
+    private void handleDragActionDownEvent(MotionEvent event) {
+        mVelocityTracker = VelocityTracker.obtain();
+        mVelocityTracker.addMovement(event);
+        mDockWindowTouchSlopExceeded = false;
+        mTouchDownX = (int) event.getX();
+        mTouchDownY = (int) event.getY();
+    }
+
+    private boolean handleDragActionMoveEvent(MotionEvent event) {
+        mVelocityTracker.addMovement(event);
+        int x = (int) event.getX();
+        int y = (int) event.getY();
+        int xDiff = Math.abs(x - mTouchDownX);
+        int yDiff = Math.abs(y - mTouchDownY);
+        if (!mDockWindowTouchSlopExceeded) {
+            boolean touchSlopExceeded = !mIsVertical
+                    ? yDiff > mTouchSlop && yDiff > xDiff
+                    : xDiff > mTouchSlop && xDiff > yDiff;
+            if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
+                    == DOCKED_INVALID) {
+                mDragMode = calculateDragMode();
+                mRecentsComponent.dockTopTask(mDragMode == DRAG_MODE_RECENTS);
+                if (mDragMode == DRAG_MODE_DIVIDER) {
+                    mDivider.getView().startDragging();
+                }
+                mDockWindowTouchSlopExceeded = true;
+                return true;
+            }
+        } else {
+            if (mDragMode == DRAG_MODE_DIVIDER) {
+                mDivider.getView().resizeStack(
+                        !mIsVertical ? (int) event.getRawY() : (int) event.getRawX());
+            } else if (mDragMode == DRAG_MODE_RECENTS) {
+                mRecentsComponent.onDraggingInRecents(event.getRawY());
+            }
+        }
+        return false;
+    }
+
+    private void handleDragActionUpEvent(MotionEvent event) {
+        mVelocityTracker.addMovement(event);
+        mVelocityTracker.computeCurrentVelocity(1000);
+        if (mDockWindowTouchSlopExceeded) {
+            if (mDragMode == DRAG_MODE_DIVIDER) {
+                mDivider.getView().stopDragging(!mIsVertical
+                                ? (int) event.getRawY()
+                                : (int) event.getRawX(),
+                        !mIsVertical
+                                ? mVelocityTracker.getXVelocity()
+                                : mVelocityTracker.getYVelocity());
+            } else if (mDragMode == DRAG_MODE_RECENTS) {
+                mRecentsComponent.onDraggingInRecentsEnded(mVelocityTracker.getYVelocity());
+            }
+        }
+        mVelocityTracker.recycle();
+        mVelocityTracker = null;
+    }
+
+    private int calculateDragMode() {
+        if (mIsVertical && !mDivider.getView().isHorizontalDivision()) {
+            return DRAG_MODE_DIVIDER;
+        }
+        if (!mIsVertical && mDivider.getView().isHorizontalDivision()) {
+            return DRAG_MODE_DIVIDER;
+        }
+        return DRAG_MODE_RECENTS;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+        if (mDockWindowEnabled) {
+            result |= handleDockWindowEvent(event);
+        }
+        return result;
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        float absVelX = Math.abs(velocityX);
+        float absVelY = Math.abs(velocityY);
+        boolean isValidFling = absVelX > mMinFlingVelocity &&
+                mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
+        if (isValidFling) {
+            boolean showNext;
+            if (!mIsRTL) {
+                showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
+            } else {
+                // In RTL, vertical is still the same, but horizontal is flipped
+                showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
+            }
+            if (showNext) {
+                mRecentsComponent.showNextAffiliatedTask();
+            } else {
+                mRecentsComponent.showPrevAffiliatedTask();
+            }
+        }
+        return true;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 33e514d..e1aec6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -40,7 +40,6 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewRootImpl;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
@@ -48,6 +47,8 @@
 import android.widget.LinearLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.RecentsComponent;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonView;
 
@@ -78,7 +79,7 @@
     private Drawable mRecentIcon;
     private Drawable mRecentLandIcon;
 
-    private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
+    private NavigationBarGestureHelper mGestureHelper;
     private DeadZone mDeadZone;
     private final NavigationBarTransitions mBarTransitions;
 
@@ -180,7 +181,7 @@
         mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
         mVertical = false;
         mShowMenu = false;
-        mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context);
+        mGestureHelper = new NavigationBarGestureHelper(context);
 
         getIcons(res);
 
@@ -191,8 +192,8 @@
         return mBarTransitions;
     }
 
-    public void setBar(PhoneStatusBar phoneStatusBar) {
-        mTaskSwitchHelper.setBar(phoneStatusBar);
+    public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+        mGestureHelper.setComponents(recentsComponent, divider);
     }
 
     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
@@ -202,7 +203,7 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (mTaskSwitchHelper.onTouchEvent(event)) {
+        if (mGestureHelper.onTouchEvent(event)) {
             return true;
         }
         if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
@@ -213,7 +214,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        return mTaskSwitchHelper.onInterceptTouchEvent(event);
+        return mGestureHelper.onInterceptTouchEvent(event);
     }
 
     public void abortCurrentGesture() {
@@ -488,7 +489,7 @@
 
     private void updateTaskSwitchHelper() {
         boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
-        mTaskSwitchHelper.setBarState(mVertical, isRtl);
+        mGestureHelper.setBarState(mVertical, isRtl);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
deleted file mode 100644
index fdfcdfb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarViewTaskSwitchHelper.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.GestureDetector;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.BaseStatusBar;
-
-public class NavigationBarViewTaskSwitchHelper extends GestureDetector.SimpleOnGestureListener {
-
-    private BaseStatusBar mBar;
-    private boolean mIsVertical;
-    private boolean mIsRTL;
-
-    private final GestureDetector mTaskSwitcherDetector;
-    private final int mScrollTouchSlop;
-    private final int mMinFlingVelocity;
-    private int mTouchDownX;
-    private int mTouchDownY;
-
-    public NavigationBarViewTaskSwitchHelper(Context context) {
-        ViewConfiguration configuration = ViewConfiguration.get(context);
-        Resources r = context.getResources();
-        mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
-        mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
-        mTaskSwitcherDetector = new GestureDetector(context, this);
-    }
-
-    public void setBar(BaseStatusBar phoneStatusBar) {
-        mBar = phoneStatusBar;
-    }
-
-    public void setBarState(boolean isVertical, boolean isRTL) {
-        mIsVertical = isVertical;
-        mIsRTL = isRTL;
-    }
-
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        // If we move more than a fixed amount, then start capturing for the
-        // task switcher detector
-        mTaskSwitcherDetector.onTouchEvent(event);
-        int action = event.getAction();
-        boolean intercepted = false;
-        switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN: {
-                mTouchDownX = (int) event.getX();
-                mTouchDownY = (int) event.getY();
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int xDiff = Math.abs(x - mTouchDownX);
-                int yDiff = Math.abs(y - mTouchDownY);
-                boolean exceededTouchSlop = !mIsVertical
-                        ? xDiff > mScrollTouchSlop && xDiff > yDiff
-                        : yDiff > mScrollTouchSlop && yDiff > xDiff;
-                if (exceededTouchSlop) {
-                    return true;
-                }
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                break;
-        }
-        return intercepted;
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        return mTaskSwitcherDetector.onTouchEvent(event);
-    }
-
-    @Override
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-        float absVelX = Math.abs(velocityX);
-        float absVelY = Math.abs(velocityY);
-        boolean isValidFling = absVelX > mMinFlingVelocity &&
-                mIsVertical ? (absVelY > absVelX) : (absVelX > absVelY);
-        if (isValidFling) {
-            boolean showNext;
-            if (!mIsRTL) {
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX < 0);
-            } else {
-                // In RTL, vertical is still the same, but horizontal is flipped
-                showNext = mIsVertical ? (velocityY < 0) : (velocityX > 0);
-            }
-            if (showNext) {
-                mBar.showNextAffiliatedTask();
-            } else {
-                mBar.showPreviousAffiliatedTask();
-            }
-        }
-        return true;
-    }
-}
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..781d651 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -117,6 +117,7 @@
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.recents.ScreenPinningRequest;
+import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.ActivatableNotificationView;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.BaseStatusBar;
@@ -739,7 +740,7 @@
                             context, R.layout.navigation_bar, null);
                 }
                 mNavigationBarView.setDisabledFlags(mDisabled1);
-                mNavigationBarView.setBar(this);
+                mNavigationBarView.setComponents(mRecents, getComponent(Divider.class));
                 mNavigationBarView.setOnVerticalChangedListener(
                         new NavigationBarView.OnVerticalChangedListener() {
                     @Override
@@ -927,7 +928,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());
@@ -1135,7 +1136,7 @@
         @Override
         public boolean onLongClick(View v) {
             if (mRecents != null) {
-                mRecents.dockTopTask();
+                mRecents.dockTopTask(false /* draggingInRecents */);
                 return true;
             }
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index fa9c4bb..83edc96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -383,6 +383,12 @@
                 @Override
                 public void onUserSwitching(int newUserId, IRemoteCallback reply) {
                     mUserInfoController.reloadUserInfo();
+                    if (reply != null) {
+                        try {
+                            reply.sendResult(null);
+                        } catch (RemoteException e) {
+                        }
+                    }
                 }
 
                 @Override
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..79c0957 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
@@ -3559,11 +3560,6 @@
 org.apache.harmony.security.asn1.BerOutputStream
 org.apache.harmony.security.asn1.DerInputStream
 org.apache.harmony.security.asn1.DerOutputStream
-org.apache.harmony.security.fortress.Engine
-org.apache.harmony.security.fortress.Engine$ServiceCacheEntry
-org.apache.harmony.security.fortress.Engine$SpiAndProvider
-org.apache.harmony.security.fortress.SecurityAccess
-org.apache.harmony.security.fortress.Services
 org.apache.harmony.security.provider.crypto.CryptoProvider
 org.apache.harmony.security.utils.AlgNameMapper
 org.apache.harmony.security.utils.AlgNameMapperSource
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 9f1dc0a..3d358ec 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -64,21 +64,17 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Pools.Pool;
-import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.InputDevice;
-import android.view.InputEventConsistencyVerifier;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -146,8 +142,6 @@
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
-    private static final int MAX_POOL_SIZE = 10;
-
     private static final int WINDOW_ID_UNKNOWN = -1;
 
     private static int sIdCounter = 0;
@@ -158,9 +152,6 @@
 
     private final Object mLock = new Object();
 
-    private final Pool<PendingEvent> mPendingEventPool =
-            new SimplePool<>(MAX_POOL_SIZE);
-
     private final SimpleStringSplitter mStringColonSplitter =
             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
@@ -193,6 +184,8 @@
 
     private boolean mHasInputFilter;
 
+    private KeyEventDispatcher mKeyEventDispatcher;
+
     private final Set<ComponentName> mTempComponentNameSet = new HashSet<>();
 
     private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
@@ -756,12 +749,11 @@
 
     boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
         synchronized (mLock) {
-            KeyEvent localClone = KeyEvent.obtain(event);
-            boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
-            if (!handled) {
-                handled = notifyKeyEventLocked(localClone, policyFlags, true);
+            List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
+            if (boundServices.isEmpty()) {
+                return false;
             }
-            return handled;
+            return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices);
         }
     }
 
@@ -935,31 +927,6 @@
         return false;
     }
 
-    private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
-        // TODO: Now we are giving the key events to the last enabled
-        //       service that can handle them Ideally, the user should
-        //       make the call which service handles key events. However,
-        //       only one service should handle key events to avoid user
-        //       frustration when different behavior is observed from
-        //       different combinations of enabled accessibility services.
-        UserState state = getCurrentUserStateLocked();
-        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
-            Service service = state.mBoundServices.get(i);
-            // Key events are handled only by services that declared
-            // this capability and requested to filter key events.
-            if (!service.mRequestFilterKeyEvents ||
-                    (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
-                            .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
-                continue;
-            }
-            if (service.mIsDefault == isDefault) {
-                service.notifyKeyEvent(event, policyFlags);
-                return true;
-            }
-        }
-        return false;
-    }
-
     private void notifyClearAccessibilityCacheLocked() {
         UserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
@@ -1754,6 +1721,14 @@
         return null;
     }
 
+    private KeyEventDispatcher getKeyEventDispatcher() {
+        if (mKeyEventDispatcher == null) {
+            mKeyEventDispatcher = new KeyEventDispatcher(
+                    mMainHandler, MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER, mLock);
+        }
+        return mKeyEventDispatcher;
+    }
+
     @Override
     public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
@@ -1954,22 +1929,6 @@
         }
     }
 
-    private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
-        PendingEvent pendingEvent = mPendingEventPool.acquire();
-        if (pendingEvent == null) {
-            pendingEvent = new PendingEvent();
-        }
-        pendingEvent.event = event;
-        pendingEvent.policyFlags = policyFlags;
-        pendingEvent.sequence = sequence;
-        return pendingEvent;
-    }
-
-    private void recyclePendingEventLocked(PendingEvent pendingEvent) {
-        pendingEvent.clear();
-        mPendingEventPool.release(pendingEvent);
-    }
-
     private int findWindowIdLocked(IBinder token) {
         final int globalIndex = mGlobalWindowTokens.indexOfValue(token);
         if (globalIndex >= 0) {
@@ -2082,8 +2041,6 @@
         final SparseArray<AccessibilityEvent> mPendingEvents =
             new SparseArray<>();
 
-        final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
-
         boolean mWasConnectedAndDied;
 
         // Handler only for dispatching accessibility events since we use event
@@ -2195,7 +2152,7 @@
                 return false;
             }
             UserState userState = getUserStateLocked(mUserId);
-            mKeyEventDispatcher.flush();
+            getKeyEventDispatcher().flush(this);
             if (!mIsAutomation) {
                 mContext.unbindService(this);
             } else {
@@ -2212,7 +2169,7 @@
 
         @Override
         public void setOnKeyEventResult(boolean handled, int sequence) {
-            mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+            getKeyEventDispatcher().setOnKeyEventResult(this, handled, sequence);
         }
 
         @Override
@@ -2926,7 +2883,7 @@
                     return;
                 }
                 mWasConnectedAndDied = true;
-                mKeyEventDispatcher.flush();
+                getKeyEventDispatcher().flush(this);
                 UserState userState = getUserStateLocked(mUserId);
                 // The death recipient is unregistered in removeServiceLocked
                 removeServiceLocked(this, userState);
@@ -3035,11 +2992,6 @@
                     gestureId, 0).sendToTarget();
         }
 
-        public void notifyKeyEvent(KeyEvent event, int policyFlags) {
-            mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
-                    policyFlags, 0, event).sendToTarget();
-        }
-
         public void notifyClearAccessibilityNodeInfoCache() {
             mInvocationHandler.sendEmptyMessage(
                     InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
@@ -3084,10 +3036,6 @@
             }
         }
 
-        private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
-            mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
-        }
-
         private void notifyClearAccessibilityCacheInternal() {
             final IAccessibilityServiceClient listener;
             synchronized (mLock) {
@@ -3205,9 +3153,7 @@
 
         private final class InvocationHandler extends Handler {
             public static final int MSG_ON_GESTURE = 1;
-            public static final int MSG_ON_KEY_EVENT = 2;
-            public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 3;
-            public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+            public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
 
             private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
 
@@ -3226,21 +3172,10 @@
                         notifyGestureInternal(gestureId);
                     } break;
 
-                    case MSG_ON_KEY_EVENT: {
-                        KeyEvent event = (KeyEvent) message.obj;
-                        final int policyFlags = message.arg1;
-                        notifyKeyEventInternal(event, policyFlags);
-                    } break;
-
                     case MSG_CLEAR_ACCESSIBILITY_CACHE: {
                         notifyClearAccessibilityCacheInternal();
                     } break;
 
-                    case MSG_ON_KEY_EVENT_TIMEOUT: {
-                        PendingEvent eventState = (PendingEvent) message.obj;
-                        setOnKeyEventResult(false, eventState.sequence);
-                    } break;
-
                     case MSG_ON_MAGNIFICATION_CHANGED: {
                         final SomeArgs args = (SomeArgs) message.obj;
                         final Region region = (Region) args.arg1;
@@ -3278,140 +3213,6 @@
             }
         }
 
-        private final class KeyEventDispatcher {
-
-            private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
-
-            private PendingEvent mPendingEvents;
-
-            private final InputEventConsistencyVerifier mSentEventsVerifier =
-                    InputEventConsistencyVerifier.isInstrumentationEnabled()
-                            ? new InputEventConsistencyVerifier(
-                                    this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
-
-            public void notifyKeyEvent(KeyEvent event, int policyFlags) {
-                final PendingEvent pendingEvent;
-
-                synchronized (mLock) {
-                    pendingEvent = addPendingEventLocked(event, policyFlags);
-                }
-
-                Message message = mInvocationHandler.obtainMessage(
-                        InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
-                mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
-
-                try {
-                    // Accessibility services are exclusively not in the system
-                    // process, therefore no need to clone the motion event to
-                    // prevent tampering. It will be cloned in the IPC call.
-                    mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
-                } catch (RemoteException re) {
-                    setOnKeyEventResult(false, pendingEvent.sequence);
-                }
-            }
-
-            public void setOnKeyEventResult(boolean handled, int sequence) {
-                synchronized (mLock) {
-                    PendingEvent pendingEvent = removePendingEventLocked(sequence);
-                    if (pendingEvent != null) {
-                        mInvocationHandler.removeMessages(
-                                InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
-                                pendingEvent);
-                        pendingEvent.handled = handled;
-                        finishPendingEventLocked(pendingEvent);
-                    }
-                }
-            }
-
-            public void flush() {
-                synchronized (mLock) {
-                    cancelAllPendingEventsLocked();
-                    if (mSentEventsVerifier != null) {
-                        mSentEventsVerifier.reset();
-                    }
-                }
-            }
-
-            private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
-                final int sequence = event.getSequenceNumber();
-                PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
-                pendingEvent.next = mPendingEvents;
-                mPendingEvents = pendingEvent;
-                return pendingEvent;
-            }
-
-            private PendingEvent removePendingEventLocked(int sequence) {
-                PendingEvent previous = null;
-                PendingEvent current = mPendingEvents;
-
-                while (current != null) {
-                    if (current.sequence == sequence) {
-                        if (previous != null) {
-                            previous.next = current.next;
-                        } else {
-                            mPendingEvents = current.next;
-                        }
-                        current.next = null;
-                        return current;
-                    }
-                    previous = current;
-                    current = current.next;
-                }
-                return null;
-            }
-
-            private void finishPendingEventLocked(PendingEvent pendingEvent) {
-                if (!pendingEvent.handled) {
-                    sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
-                }
-                // Nullify the event since we do not want it to be
-                // recycled yet. It will be sent to the input filter.
-                pendingEvent.event = null;
-                recyclePendingEventLocked(pendingEvent);
-            }
-
-            private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
-                if (DEBUG) {
-                    Slog.i(LOG_TAG, "Injecting event: " + event);
-                }
-                if (mSentEventsVerifier != null) {
-                    mSentEventsVerifier.onKeyEvent(event, 0);
-                }
-                policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
-                mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
-                        policyFlags, 0, event).sendToTarget();
-            }
-
-            private void cancelAllPendingEventsLocked() {
-                while (mPendingEvents != null) {
-                    PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
-                    pendingEvent.handled = false;
-                    mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
-                            pendingEvent);
-                    finishPendingEventLocked(pendingEvent);
-                }
-            }
-        }
-    }
-
-    private static final class PendingEvent {
-        PendingEvent next;
-
-        KeyEvent event;
-        int policyFlags;
-        int sequence;
-        boolean handled;
-
-        public void clear() {
-            if (event != null) {
-                event.recycle();
-                event = null;
-            }
-            next = null;
-            policyFlags = 0;
-            sequence = 0;
-            handled = false;
-        }
     }
 
     final class WindowsForAccessibilityCallback implements
diff --git a/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
new file mode 100644
index 0000000..3469565
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java
@@ -0,0 +1,285 @@
+/*
+ ** 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.server.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Pools;
+import android.util.Pools.Pool;
+import android.util.Slog;
+import android.view.InputEventConsistencyVerifier;
+import android.view.KeyEvent;
+import android.view.WindowManagerPolicy;
+
+import com.android.server.accessibility.AccessibilityManagerService.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dispatcher to send KeyEvents to all accessibility services that are able to process them.
+ * Events that are handled by one or more services are consumed. Events that are not processed
+ * by any service (or time out before a service reports them as handled) are passed along to
+ * the rest of the system.
+ *
+ * The class assumes that services report their return values in order, which is valid because
+ * they process each call to {@code AccessibilityService.onKeyEvent} on a single thread, and so
+ * don't see the N+1th event until they have processed the Nth event.
+ */
+public class KeyEventDispatcher {
+    // Debugging
+    private static final String LOG_TAG = "KeyEventDispatcher";
+    private static final boolean DEBUG = false;
+    /* KeyEvents must be processed in this time interval */
+    private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+    private static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
+    private static final int MAX_POOL_SIZE = 10;
+
+    private final Pool<PendingKeyEvent> mPendingEventPool = new Pools.SimplePool<>(MAX_POOL_SIZE);
+    private final Object mLock;
+
+    /*
+     * Track events sent to each service. If a KeyEvent is to be sent to at least one service,
+     * a corresponding PendingKeyEvent is created for it. This PendingKeyEvent is placed in
+     * the list for each service its KeyEvent is sent to. It is removed from the list when
+     * the service calls setOnKeyEventResult, or when we time out waiting for the service to
+     * respond.
+     */
+    private final Map<Service, ArrayList<PendingKeyEvent>> mPendingEventsMap = new ArrayMap<>();
+
+    private final InputEventConsistencyVerifier mSentEventsVerifier;
+    private final Handler mHandlerToSendKeyEventsToInputFilter;
+    private final int mMessageTypeForSendKeyEvent;
+    private final Handler mKeyEventTimeoutHandler;
+
+    /**
+     * @param handlerToSendKeyEventsToInputFilter The handler to which to post {@code KeyEvent}s
+     * that have not been handled by any accessibility service.
+     * @param messageTypeForSendKeyEvent The field to populate {@code message.what} for the
+     * message that carries a {@code KeyEvent} to be sent to the input filter
+     * @param lock The lock used for all synchronization in this package. This lock must be held
+     * when calling {@code notifyKeyEventLocked}
+     */
+    public KeyEventDispatcher(Handler handlerToSendKeyEventsToInputFilter,
+                              int messageTypeForSendKeyEvent, Object lock) {
+        if (InputEventConsistencyVerifier.isInstrumentationEnabled()) {
+            mSentEventsVerifier = new InputEventConsistencyVerifier(
+                    this, 0, KeyEventDispatcher.class.getSimpleName());
+        } else {
+            mSentEventsVerifier = null;
+        }
+        mHandlerToSendKeyEventsToInputFilter = handlerToSendKeyEventsToInputFilter;
+        mMessageTypeForSendKeyEvent = messageTypeForSendKeyEvent;
+        mKeyEventTimeoutHandler =
+                new Handler(mHandlerToSendKeyEventsToInputFilter.getLooper(), new Callback());
+        mLock = lock;
+    }
+
+    /**
+     * Notify that a new KeyEvent is available to accessibility services. Must be called with the
+     * lock used to construct this object held. The boundServices list must also be protected
+     * by a lock.
+     *
+     * @param event The new key event
+     * @param policyFlags Flags for the event
+     * @param boundServices A list of currently bound AccessibilityServices
+     *
+     * @return {@code true} if the event was passed to at least one AccessibilityService,
+     * {@code false} otherwise.
+     */
+    // TODO: The locking policy for boundServices needs some thought.
+    public boolean notifyKeyEventLocked(
+            KeyEvent event, int policyFlags, List<Service> boundServices) {
+        PendingKeyEvent pendingKeyEvent = null;
+        KeyEvent localClone = KeyEvent.obtain(event);
+        for (int i = 0; i < boundServices.size(); i++) {
+            Service service = boundServices.get(i);
+            // Key events are handled only by services that declared
+            // this capability and requested to filter key events.
+            if (!service.mRequestFilterKeyEvents) {
+                continue;
+            }
+            int filterKeyEventBit = service.mAccessibilityServiceInfo.getCapabilities()
+                    & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+            if (filterKeyEventBit == 0) {
+                continue;
+            }
+
+            try {
+                // The event will be cloned in the IPC call, so it doesn't need to be here.
+                service.mServiceInterface.onKeyEvent(localClone, localClone.getSequenceNumber());
+            } catch (RemoteException re) {
+                continue;
+            }
+
+            if (pendingKeyEvent == null) {
+                pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
+            }
+            ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(service);
+            if (pendingEventList == null) {
+                pendingEventList = new ArrayList<>();
+                mPendingEventsMap.put(service, pendingEventList);
+            }
+            pendingEventList.add(pendingKeyEvent);
+            pendingKeyEvent.referenceCount++;
+        }
+
+        if (pendingKeyEvent == null) {
+            localClone.recycle();
+            return false;
+        }
+
+        Message message = mKeyEventTimeoutHandler.obtainMessage(
+                MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
+        mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+        return true;
+    }
+
+    /**
+     * Set the result from onKeyEvent from one service.
+     *
+     * @param service The service setting the result
+     * @param handled {@code true} if the service handled the {@code KeyEvent}
+     * @param sequence The sequence number of the {@code KeyEvent}
+     */
+    public void setOnKeyEventResult(Service service, boolean handled, int sequence) {
+        synchronized (mLock) {
+            PendingKeyEvent pendingEvent =
+                    removeEventFromListLocked(mPendingEventsMap.get(service), sequence);
+            if (pendingEvent != null) {
+                pendingEvent.handled |= handled;
+                removeReferenceToPendingEventLocked(pendingEvent);
+            }
+        }
+    }
+
+    /**
+     * Flush all pending key events for a service, treating all of them as unhandled
+     *
+     * @param service The service for which to flush events
+     */
+    public void flush(Service service) {
+        synchronized (mLock) {
+            List<PendingKeyEvent> pendingEvents = mPendingEventsMap.get(service);
+            if (pendingEvents != null) {
+                for (int i = 0; i < pendingEvents.size(); i++) {
+                    PendingKeyEvent pendingEvent = pendingEvents.get(i);
+                    removeReferenceToPendingEventLocked(pendingEvent);
+                }
+                mPendingEventsMap.remove(service);
+            }
+        }
+    }
+
+    private PendingKeyEvent obtainPendingEventLocked(KeyEvent event, int policyFlags) {
+        PendingKeyEvent pendingEvent = mPendingEventPool.acquire();
+        if (pendingEvent == null) {
+            pendingEvent = new PendingKeyEvent();
+        }
+        pendingEvent.event = event;
+        pendingEvent.policyFlags = policyFlags;
+        pendingEvent.referenceCount = 0;
+        pendingEvent.handled = false;
+        return pendingEvent;
+    }
+
+    private static PendingKeyEvent removeEventFromListLocked(
+            List<PendingKeyEvent> listOfEvents, int sequence) {
+        /* In normal operation, the event should be first */
+        for (int i = 0; i < listOfEvents.size(); i++) {
+            PendingKeyEvent pendingKeyEvent = listOfEvents.get(i);
+            if (pendingKeyEvent.event.getSequenceNumber() == sequence) {
+                    /*
+                     * Removing the first element of the ArrayList can be slow if there are a lot
+                     * of events backed up, but for a handful of events it's better than incurring
+                     * the fixed overhead of LinkedList. An ArrayList optimized for removing the
+                     * first element (by treating the underlying array as a circular buffer) would
+                     * be ideal.
+                     */
+                listOfEvents.remove(pendingKeyEvent);
+                return pendingKeyEvent;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param pendingEvent The event whose reference count should be decreased
+     * @return {@code true} if the event was release, {@code false} if not.
+     */
+    private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
+        if (--pendingEvent.referenceCount > 0) {
+            return false;
+        }
+        mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+        if (!pendingEvent.handled) {
+                /* Pass event to input filter */
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "Injecting event: " + pendingEvent.event);
+            }
+            if (mSentEventsVerifier != null) {
+                mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
+            }
+            int policyFlags = pendingEvent.policyFlags | WindowManagerPolicy.FLAG_PASS_TO_USER;
+            mHandlerToSendKeyEventsToInputFilter
+                    .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0, pendingEvent.event)
+                            .sendToTarget();
+        } else {
+            pendingEvent.event.recycle();
+        }
+        mPendingEventPool.release(pendingEvent);
+        return true;
+    }
+
+    private static final class PendingKeyEvent {
+        /* Event and policyFlag provided in notifyKeyEventLocked */
+        KeyEvent event;
+        int policyFlags;
+        /*
+         * The referenceCount optimizes the process of determining the number of services
+         * still holding a KeyEvent. It must be equal to the number of times the PendingEvent
+         * appears in mPendingEventsMap, or PendingEvents will leak.
+         */
+        int referenceCount;
+        /* Whether or not at least one service had handled this event */
+        boolean handled;
+    }
+
+    private class Callback implements Handler.Callback {
+        @Override
+        public boolean handleMessage(Message message) {
+            if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
+                throw new IllegalArgumentException("Unknown message: " + message.what);
+            }
+            PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
+            synchronized (mLock) {
+                for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
+                    if (listForService.remove(pendingKeyEvent)) {
+                        if(removeReferenceToPendingEventLocked(pendingKeyEvent)) {
+                            break;
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 504a7ef..c5ce2fd 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2769,7 +2769,9 @@
         }
 
         @Override public void onUidIdle(int uid) throws RemoteException {
-            removeForStoppedLocked(uid);
+            synchronized (mLock) {
+                removeForStoppedLocked(uid);
+            }
         }
     };
 
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 40f7c5c..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;
@@ -1199,6 +1206,9 @@
                 if (mCurClient == cs) {
                     mCurClient = null;
                 }
+                if (mCurFocusedWindowClient == cs) {
+                    mCurFocusedWindowClient = null;
+                }
             }
         }
     }
@@ -2194,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?
@@ -3705,6 +3716,7 @@
 
         IInputMethod method;
         ClientState client;
+        ClientState focusedWindowClient;
 
         final Printer p = new PrintWriterPrinter(pw);
 
@@ -3729,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);
@@ -3760,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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3a0d80b..29173a5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -28,6 +28,7 @@
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.ProcessStats;
+import com.android.internal.app.SystemUserHomeActivity;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.os.IResultReceiver;
@@ -192,6 +193,7 @@
 import android.util.AtomicFile;
 import android.util.DebugUtils;
 import android.util.EventLog;
+import android.util.LocaleList;
 import android.util.Log;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
@@ -2445,7 +2447,7 @@
         mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
 
         mConfiguration.setToDefaults();
-        mConfiguration.setLocale(Locale.getDefault());
+        mConfiguration.setLocales(LocaleList.getDefault());
 
         mConfigurationSeq = mConfiguration.seq = 1;
         mProcessCpuTracker.init();
@@ -12268,6 +12270,17 @@
 
             // Start up initial activity.
             mBooting = true;
+            // Enable home activity for system user, so that the system can always boot
+            if (UserManager.isSplitSystemUser()) {
+                ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
+                try {
+                    AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
+                            UserHandle.USER_SYSTEM);
+                } catch (RemoteException e) {
+                    e.rethrowAsRuntimeException();
+                }
+            }
             startHomeActivityLocked(currentUserId, "systemReady");
 
             try {
@@ -15455,6 +15468,10 @@
                 pw.print(" Lost RAM: "); pw.println(stringifyKBSize(memInfo.getTotalSizeKb()
                         - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                         - memInfo.getKernelUsedSizeKb()));
+            } else {
+                pw.print("lostram,"); pw.println(memInfo.getTotalSizeKb()
+                        - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+                        - memInfo.getKernelUsedSizeKb());
             }
             if (!brief) {
                 if (memInfo.getZramTotalSizeKb() != 0) {
@@ -17709,6 +17726,9 @@
                 UserHandle.USER_NULL);
     }
 
+    // To cache the list of supported system locales
+    private String[] mSupportedSystemLocales = null;
+
     /**
      * Do either or both things: (1) change the current configuration, and (2)
      * make sure the given activity is running with the (now) current
@@ -17732,11 +17752,14 @@
 
                 EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
 
-                if (!initLocale && values.locale != null && values.userSetLocale) {
-                    final String languageTag = values.locale.toLanguageTag();
-                    SystemProperties.set("persist.sys.locale", languageTag);
+                if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
+                    if (mSupportedSystemLocales == null) {
+                        mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
+                    }
+                    final Locale locale = values.getLocales().getBestMatch(mSupportedSystemLocales);
+                    SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
                     mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
-                            values.locale));
+                            locale));
                 }
 
                 mConfigurationSeq++;
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 3fc6846..02cf87a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1277,7 +1277,7 @@
             }
 
             final Configuration serviceConfig = mService.mConfiguration;
-            mOverrideConfig = new Configuration(serviceConfig);
+            mOverrideConfig = new Configuration(Configuration.EMPTY);
             // TODO(multidisplay): Update Dp to that of display stack is on.
             final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
             mOverrideConfig.screenWidthDp =
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e04f138..4b2a55f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -522,6 +522,9 @@
                 }
 
                 if (uss.mState == UserState.STATE_BOOTING) {
+                    // Let user manager propagate user restrictions to other services.
+                    getUserManager().onBeforeStartUser(userId);
+
                     // Booting up a new user, need to tell system services about it.
                     // Note that this is on the same handler as scheduling of broadcasts,
                     // which is important because it needs to go first.
@@ -671,7 +674,7 @@
 
     void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mService) {
-            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
         }
     }
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index ec7c1c4..103ed0a 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -1139,6 +1139,12 @@
                     public void onUserSwitching(int newUserId, IRemoteCallback reply) {
                         mHandler.obtainMessage(MSG_USER_SWITCHING, newUserId, 0 /* unused */)
                                 .sendToTarget();
+                        if (reply != null) {
+                            try {
+                                reply.sendResult(null);
+                            } catch (RemoteException e) {
+                            }
+                        }
                     }
                     @Override
                     public void onUserSwitchComplete(int newUserId) throws RemoteException {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4424838..946fbb1 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1211,29 +1211,36 @@
         }
 
         @Override
-        public void setPackagePriority(String pkg, int uid, int priority) {
+        public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
             checkCallerIsSystem();
-            mRankingHelper.setPackagePriority(pkg, uid, priority);
+            return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
+        }
+
+        @Override
+        public void setTopicPriority(String pkg, int uid, Notification.Topic topic, int priority) {
+            checkCallerIsSystem();
+            mRankingHelper.setTopicPriority(pkg, uid, topic, priority);
             savePolicyFile();
         }
 
         @Override
-        public int getPackagePriority(String pkg, int uid) {
+        public int getTopicPriority(String pkg, int uid, Notification.Topic topic) {
             checkCallerIsSystem();
-            return mRankingHelper.getPackagePriority(pkg, uid);
+            return mRankingHelper.getTopicPriority(pkg, uid, topic);
         }
 
         @Override
-        public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
+        public void setTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic,
+                int visibility) {
             checkCallerIsSystem();
-            mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
+            mRankingHelper.setTopicVisibilityOverride(pkg, uid, topic, visibility);
             savePolicyFile();
         }
 
         @Override
-        public int getPackageVisibilityOverride(String pkg, int uid) {
+        public int getTopicVisibilityOverride(String pkg, int uid, Notification.Topic topic) {
             checkCallerIsSystem();
-            return mRankingHelper.getPackageVisibilityOverride(pkg, uid);
+            return mRankingHelper.getTopicVisibilityOverride(pkg, uid, topic);
         }
 
         /**
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..7ee29e4 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -15,12 +15,20 @@
  */
 package com.android.server.notification;
 
+import android.app.Notification;
+
+import java.util.List;
+
 public interface RankingConfig {
-    int getPackagePriority(String packageName, int uid);
 
-    void setPackagePriority(String packageName, int uid, int priority);
+    List<Notification.Topic> getTopics(String packageName, int uid);
 
-    int getPackageVisibilityOverride(String packageName, int uid);
+    int getTopicPriority(String packageName, int uid, Notification.Topic topic);
 
-    void setPackageVisibilityOverride(String packageName, int uid, int visibility);
+    void setTopicPriority(String packageName, int uid, Notification.Topic topic, int priority);
+
+    int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic);
+
+    void setTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic,
+            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 f8b661f..4d33248 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -27,6 +27,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.R;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -35,6 +37,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 public class RankingHelper implements RankingConfig {
@@ -45,11 +49,14 @@
     private static final String TAG_RANKING = "ranking";
     private static final String TAG_PACKAGE = "package";
     private static final String ATT_VERSION = "version";
+    private static final String TAG_TOPIC = "topic";
 
     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_VISIBILITY = "visibility";
+    private static final String ATT_TOPIC_ID = "id";
+    private static final String ATT_TOPIC_LABEL = "label";
 
     private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
     private static final int DEFAULT_VISIBILITY =
@@ -161,12 +168,14 @@
                         } else {
                             r = getOrCreateRecord(name, uid);
                         }
-                        if (priority != DEFAULT_PRIORITY) {
-                            r.priority = priority;
-                        }
-                        if (vis != DEFAULT_VISIBILITY) {
-                            r.visibility = vis;
-                        }
+
+                        // Migrate package level settings to the default topic.
+                        // Might be overwritten by parseTopics.
+                        Topic defaultTopic = r.topics.get(Notification.TOPIC_DEFAULT);
+                        defaultTopic.priority = priority;
+                        defaultTopic.visibility = vis;
+
+                        parseTopics(r, parser);
                     }
                 }
             }
@@ -174,6 +183,38 @@
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
+    public void parseTopics(Record r, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        final int innerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (TAG_TOPIC.equals(tagName)) {
+                int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+                int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+                String id = parser.getAttributeValue(null, ATT_TOPIC_ID);
+                CharSequence label = parser.getAttributeValue(null, ATT_TOPIC_LABEL);
+
+                if (!TextUtils.isEmpty(id)) {
+                    Topic topic = new Topic(new Notification.Topic(id, label));
+
+                    if (priority != DEFAULT_PRIORITY) {
+                        topic.priority = priority;
+                    }
+                    if (vis != DEFAULT_VISIBILITY) {
+                        topic.visibility = vis;
+                    }
+                    r.topics.put(id, topic);
+                }
+            }
+        }
+    }
+
     private static String recordKey(String pkg, int uid) {
         return pkg + "|" + uid;
     }
@@ -185,21 +226,14 @@
             r = new Record();
             r.pkg = pkg;
             r.uid = uid;
+            r.topics.put(Notification.TOPIC_DEFAULT,
+                    new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
+                            mContext.getString(R.string.default_notification_topic_label))));
             mRecords.put(key, r);
         }
         return r;
     }
 
-    private void removeDefaultRecords() {
-        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.visibility == DEFAULT_VISIBILITY) {
-                mRecords.remove(i);
-            }
-        }
-    }
-
     public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
         out.startTag(null, TAG_RANKING);
         out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
@@ -213,20 +247,32 @@
             }
             out.startTag(null, TAG_PACKAGE);
             out.attribute(null, ATT_NAME, r.pkg);
-            if (r.priority != DEFAULT_PRIORITY) {
-                out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
-            }
-            if (r.visibility != DEFAULT_VISIBILITY) {
-                out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
-            }
+
             if (!forBackup) {
                 out.attribute(null, ATT_UID, Integer.toString(r.uid));
             }
+
+            writeTopicsXml(out, r);
             out.endTag(null, TAG_PACKAGE);
         }
         out.endTag(null, TAG_RANKING);
     }
 
+    public void writeTopicsXml(XmlSerializer out, Record r) throws IOException {
+        for (Topic t : r.topics.values()) {
+            out.startTag(null, TAG_TOPIC);
+            out.attribute(null, ATT_TOPIC_ID, t.topic.getId());
+            out.attribute(null, ATT_TOPIC_LABEL, t.topic.getLabel().toString());
+            if (t.priority != DEFAULT_PRIORITY) {
+                out.attribute(null, ATT_PRIORITY, Integer.toString(t.priority));
+            }
+            if (t.visibility != DEFAULT_VISIBILITY) {
+                out.attribute(null, ATT_VISIBILITY, Integer.toString(t.visibility));
+            }
+            out.endTag(null, TAG_TOPIC);
+        }
+    }
+
     private void updateConfig() {
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
@@ -322,37 +368,54 @@
     }
 
     @Override
-    public int getPackagePriority(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.priority : DEFAULT_PRIORITY;
+    public List<Notification.Topic> getTopics(String packageName, int uid) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        List<Notification.Topic> topics = new ArrayList<>();
+        for (Topic t :  r.topics.values()) {
+            topics.add(t.topic);
+        }
+        return topics;
     }
 
     @Override
-    public void setPackagePriority(String packageName, int uid, int priority) {
-        if (priority == getPackagePriority(packageName, uid)) {
-            return;
-        }
-        getOrCreateRecord(packageName, uid).priority = priority;
-        removeDefaultRecords();
+    public int getTopicPriority(String packageName, int uid, Notification.Topic topic) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        return getOrCreateTopic(r, topic).priority;
+    }
+
+    @Override
+    public void setTopicPriority(String packageName, int uid, Notification.Topic topic,
+            int priority) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        getOrCreateTopic(r, topic).priority = priority;
         updateConfig();
     }
 
     @Override
-    public int getPackageVisibilityOverride(String packageName, int uid) {
-        final Record r = mRecords.get(recordKey(packageName, uid));
-        return r != null ? r.visibility : DEFAULT_VISIBILITY;
+    public int getTopicVisibilityOverride(String packageName, int uid, Notification.Topic topic) {
+        final Record r = getOrCreateRecord(packageName, uid);
+        return getOrCreateTopic(r, topic).visibility;
     }
 
     @Override
-    public void setPackageVisibilityOverride(String packageName, int uid, int visibility) {
-        if (visibility == getPackageVisibilityOverride(packageName, uid)) {
-            return;
-        }
-        getOrCreateRecord(packageName, uid).visibility = visibility;
-        removeDefaultRecords();
+    public void setTopicVisibilityOverride(String pkgName, int uid, Notification.Topic topic,
+        int visibility) {
+        final Record r = getOrCreateRecord(pkgName, uid);
+        getOrCreateTopic(r, topic).visibility = visibility;
         updateConfig();
     }
 
+    private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+        Topic t = r.topics.get(topic.getId());
+        if (t != null) {
+            return t;
+        } else {
+            t = new Topic(topic);
+            r.topics.put(topic.getId(), t);
+            return t;
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
         if (filter == null) {
             final int N = mSignalExtractors.length;
@@ -385,15 +448,22 @@
                 pw.print(" (");
                 pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
                 pw.print(')');
-                if (r.priority != DEFAULT_PRIORITY) {
-                    pw.print(" priority=");
-                    pw.print(Notification.priorityToString(r.priority));
-                }
-                if (r.visibility != DEFAULT_VISIBILITY) {
-                    pw.print(" visibility=");
-                    pw.print(Notification.visibilityToString(r.visibility));
-                }
                 pw.println();
+                for (Topic t : r.topics.values()) {
+                    pw.print(prefix);
+                    pw.print("  ");
+                    pw.print("  ");
+                    pw.print(t.topic.getId());
+                    if (t.priority != DEFAULT_PRIORITY) {
+                        pw.print(" priority=");
+                        pw.print(Notification.priorityToString(t.priority));
+                    }
+                    if (t.visibility != DEFAULT_VISIBILITY) {
+                        pw.print(" visibility=");
+                        pw.print(Notification.visibilityToString(t.visibility));
+                    }
+                    pw.println();
+                }
             }
         }
     }
@@ -429,8 +499,16 @@
 
         String pkg;
         int uid = UNKNOWN_UID;
+        Map<String, Topic> topics = new ArrayMap<>();
+   }
+
+    private static class Topic {
+        Notification.Topic topic;
         int priority = DEFAULT_PRIORITY;
         int visibility = DEFAULT_VISIBILITY;
-    }
 
+        public Topic(Notification.Topic topic) {
+            this.topic = topic;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
similarity index 79%
rename from services/core/java/com/android/server/notification/PackagePriorityExtractor.java
rename to services/core/java/com/android/server/notification/TopicPriorityExtractor.java
index 6beed9c..5bf989ae 100644
--- a/services/core/java/com/android/server/notification/PackagePriorityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicPriorityExtractor.java
@@ -18,8 +18,11 @@
 import android.content.Context;
 import android.util.Slog;
 
-public class PackagePriorityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantPackageExtractor";
+/**
+ * Determines if the given notification can bypass Do Not Disturb.
+ */
+public class TopicPriorityExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "ImportantTopicExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
@@ -39,8 +42,8 @@
             return null;
         }
 
-        final int packagePriority = mConfig.getPackagePriority(
-                record.sbn.getPackageName(), record.sbn.getUid());
+        final int packagePriority = mConfig.getTopicPriority(record.sbn.getPackageName(),
+                record.sbn.getUid(), record.sbn.getNotification().getTopic());
         record.setPackagePriority(packagePriority);
 
         return null;
diff --git a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
similarity index 80%
rename from services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
rename to services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
index af99db7..e053382 100644
--- a/services/core/java/com/android/server/notification/PackageVisibilityExtractor.java
+++ b/services/core/java/com/android/server/notification/TopicVisibilityExtractor.java
@@ -18,8 +18,11 @@
 import android.content.Context;
 import android.util.Slog;
 
-public class PackageVisibilityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "PackageVisibilityExtractor";
+/**
+ * Determines if the given notification can display sensitive content on the lockscreen.
+ */
+public class TopicVisibilityExtractor implements NotificationSignalExtractor {
+    private static final String TAG = "TopicVisibilityExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
@@ -39,8 +42,9 @@
             return null;
         }
 
-        final int packageVisibility = mConfig.getPackageVisibilityOverride(
-                record.sbn.getPackageName(), record.sbn.getUid());
+        final int packageVisibility = mConfig.getTopicVisibilityOverride(
+                record.sbn.getPackageName(), record.sbn.getUid(),
+                record.sbn.getNotification().getTopic());
         record.setPackageVisibilityOverride(packageVisibility);
 
         return null;
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 e41a976..21a4206 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,12 +601,25 @@
     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>();
 
     final DefaultPermissionGrantPolicy mDefaultPermissionPolicy =
             new DefaultPermissionGrantPolicy(this);
 
+    // List of packages names to keep cached, even if they are uninstalled for all users
+    private List<String> mKeepUninstalledPackages;
+
     private static class IFVerificationParams {
         PackageParser.Package pkg;
         boolean replacing;
@@ -2346,6 +2362,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 +2527,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);
@@ -4271,8 +4397,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) {
@@ -7770,6 +7985,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;
@@ -9326,7 +9565,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>() {
@@ -12758,7 +13018,7 @@
         final boolean deleteAllUsers = (flags & PackageManager.DELETE_ALL_USERS) != 0;
         final int[] users = deleteAllUsers ? sUserManager.getUserIds() : new int[]{ userId };
         if (UserHandle.getUserId(uid) != userId || (deleteAllUsers && users.length > 1)) {
-            mContext.enforceCallingPermission(
+            mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     "deletePackage for user " + userId);
         }
@@ -12833,6 +13093,10 @@
         return false;
     }
 
+    private boolean shouldKeepUninstalledPackageLPr(String packageName) {
+        return mKeepUninstalledPackages != null && mKeepUninstalledPackages.contains(packageName);
+    }
+
     /**
      *  This method is an internal method that could be get invoked either
      *  to delete an installed package or to clean up a failed installation.
@@ -13255,7 +13519,9 @@
                         false, // blockUninstall
                         ps.readUserState(userId).domainVerificationStatus, 0);
                 if (!isSystemApp(ps)) {
-                    if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+                    // Do not uninstall the APK if an app should be cached
+                    boolean keepUninstalledPackage = shouldKeepUninstalledPackageLPr(packageName);
+                    if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
                         // Other user still have this package installed, so all
                         // we need to do is clear this user's data and save that
                         // it is uninstalled.
@@ -16429,15 +16695,21 @@
             if (DEBUG_CLEAN_APKS) {
                 Slog.i(TAG, "Checking package " + packageName);
             }
-            boolean keep = false;
-            for (int i = 0; i < users.length; i++) {
-                if (users[i] != userHandle && ps.getInstalled(users[i])) {
-                    keep = true;
-                    if (DEBUG_CLEAN_APKS) {
-                        Slog.i(TAG, "  Keeping package " + packageName + " for user "
-                                + users[i]);
+            boolean keep = shouldKeepUninstalledPackageLPr(packageName);
+            if (keep) {
+                if (DEBUG_CLEAN_APKS) {
+                    Slog.i(TAG, "  Keeping package " + packageName + " - requested by DO");
+                }
+            } else {
+                for (int i = 0; i < users.length; i++) {
+                    if (users[i] != userHandle && ps.getInstalled(users[i])) {
+                        keep = true;
+                        if (DEBUG_CLEAN_APKS) {
+                            Slog.i(TAG, "  Keeping package " + packageName + " for user "
+                                    + users[i]);
+                        }
+                        break;
                     }
-                    break;
                 }
             }
             if (!keep) {
@@ -16639,6 +16911,23 @@
         }
     }
 
+    private void deletePackageIfUnusedLPr(final String packageName) {
+        PackageSetting ps = mSettings.mPackages.get(packageName);
+        if (ps == null) {
+            return;
+        }
+        if (!ps.isAnyInstalled(sUserManager.getUserIds())) {
+            // TODO Implement atomic delete if package is unused
+            // It is currently possible that the package will be deleted even if it is installed
+            // after this method returns.
+            mHandler.post(new Runnable() {
+                public void run() {
+                    deletePackageX(packageName, 0, PackageManager.DELETE_ALL_USERS);
+                }
+            });
+        }
+    }
+
     /**
      * Check and throw if the given before/after packages would be considered a
      * downgrade.
@@ -16876,6 +17165,34 @@
                         packageName, userId);
             }
         }
+
+        @Override
+        public void setKeepUninstalledPackages(final List<String> packageList) {
+            Preconditions.checkNotNull(packageList);
+            List<String> removedFromList = null;
+            synchronized (mPackages) {
+                if (mKeepUninstalledPackages != null) {
+                    final int packagesCount = mKeepUninstalledPackages.size();
+                    for (int i = 0; i < packagesCount; i++) {
+                        String oldPackage = mKeepUninstalledPackages.get(i);
+                        if (packageList != null && packageList.contains(oldPackage)) {
+                            continue;
+                        }
+                        if (removedFromList == null) {
+                            removedFromList = new ArrayList<>();
+                        }
+                        removedFromList.add(oldPackage);
+                    }
+                }
+                mKeepUninstalledPackages = new ArrayList<>(packageList);
+                if (removedFromList != null) {
+                    final int removedCount = removedFromList.size();
+                    for (int i = 0; i < removedCount; i++) {
+                        deletePackageIfUnusedLPr(removedFromList.get(i));
+                    }
+                }
+            }
+        }
     }
 
     @Override
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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index baeccb4..b3c40d3 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,6 @@
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
-import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -36,6 +35,7 @@
 import android.graphics.Bitmap;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -48,7 +48,6 @@
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.ShellCommand;
-import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -104,7 +103,8 @@
  */
 public class UserManagerService extends IUserManager.Stub {
     private static final String LOG_TAG = "UserManagerService";
-    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+    private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE
 
     private static final String TAG_NAME = "name";
     private static final String ATTR_FLAGS = "flags";
@@ -123,6 +123,7 @@
     private static final String TAG_USERS = "users";
     private static final String TAG_USER = "user";
     private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
     private static final String TAG_ENTRY = "entry";
     private static final String TAG_VALUE = "value";
     private static final String ATTR_KEY = "key";
@@ -206,13 +207,27 @@
     private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
 
     /**
-     * User restrictions that have already been applied in {@link #applyUserRestrictionsLR}.  We
-     * use it to detect restrictions that have changed since the last
-     * {@link #applyUserRestrictionsLR} call.
+     * User restrictions that have already been applied in
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)}.  We use it to detect restrictions
+     * that have changed since the last
+     * {@link #updateUserRestrictionsInternalLR(Bundle, int)} call.
      */
     @GuardedBy("mRestrictionsLock")
     private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
 
+    /**
+     * User restrictions set by {@link DevicePolicyManager} that should be applied to all users,
+     * including guests.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private Bundle mDevicePolicyGlobalUserRestrictions;
+
+    /**
+     * User restrictions set by {@link DevicePolicyManager} for each user.
+     */
+    @GuardedBy("mRestrictionsLock")
+    private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
+
     @GuardedBy("mGuestRestrictions")
     private final Bundle mGuestRestrictions = new Bundle();
 
@@ -302,16 +317,14 @@
                     + " (name=" + ui.name + ")");
             removeUserState(ui.id);
         }
+
         onUserForeground(UserHandle.USER_SYSTEM);
+
         mAppOpsService = IAppOpsService.Stub.asInterface(
                 ServiceManager.getService(Context.APP_OPS_SERVICE));
-        for (int i = 0; i < mUserIds.length; ++i) {
-            final int userId = mUserIds[i];
-            try {
-                mAppOpsService.setUserRestrictions(getEffectiveUserRestrictions(userId), userId);
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-            }
+
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -661,19 +674,72 @@
         }
     }
 
+    /**
+     * See {@link UserManagerInternal#setDevicePolicyUserRestrictions(int, Bundle, Bundle)}
+     */
+    void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle local,
+            @Nullable Bundle global) {
+        Preconditions.checkNotNull(local);
+        boolean globalChanged = false;
+        boolean localChanged;
+        synchronized (mRestrictionsLock) {
+            if (global != null) {
+                // Update global.
+                globalChanged = !UserRestrictionsUtils.areEqual(
+                        mDevicePolicyGlobalUserRestrictions, global);
+                if (globalChanged) {
+                    mDevicePolicyGlobalUserRestrictions = global;
+                }
+            }
+            {
+                // Update local.
+                final Bundle prev = mDevicePolicyLocalUserRestrictions.get(userId);
+                localChanged = !UserRestrictionsUtils.areEqual(prev, local);
+                if (localChanged) {
+                    mDevicePolicyLocalUserRestrictions.put(userId, local);
+                }
+            }
+        }
+        if (DBG) {
+            Log.d(LOG_TAG, "setDevicePolicyUserRestrictions: userId=" + userId
+                            + " global=" + global + (globalChanged ? " (changed)" : "")
+                            + " local=" + local + (localChanged ? " (changed)" : "")
+            );
+        }
+        // Don't call them within the mRestrictionsLock.
+        synchronized (mPackagesLock) {
+            if (globalChanged) {
+                writeUserListLP();
+            }
+            if (localChanged) {
+                writeUserLP(getUserInfoNoChecks(userId));
+            }
+        }
+
+        synchronized (mRestrictionsLock) {
+            if (globalChanged) {
+                applyUserRestrictionsForAllUsersLR();
+            } else if (localChanged) {
+                applyUserRestrictionsLR(userId);
+            }
+        }
+    }
+
     @GuardedBy("mRestrictionsLock")
     private Bundle computeEffectiveUserRestrictionsLR(int userId) {
-        final DevicePolicyManagerInternal dpmi =
-                LocalServices.getService(DevicePolicyManagerInternal.class);
-        final Bundle systemRestrictions = mBaseUserRestrictions.get(userId);
+        final Bundle baseRestrictions =
+                UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
+        final Bundle global = mDevicePolicyGlobalUserRestrictions;
+        final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
 
-        final Bundle effective;
-        if (dpmi == null) {
-            // TODO Make sure it's because DPMS is disabled and not because we called it too early.
-            effective = systemRestrictions;
-        } else {
-            effective = dpmi.getComposedUserRestrictions(userId, systemRestrictions);
+        if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
+            // Common case first.
+            return baseRestrictions;
         }
+        final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
+        UserRestrictionsUtils.merge(effective, global);
+        UserRestrictionsUtils.merge(effective, local);
+
         return effective;
     }
 
@@ -709,14 +775,13 @@
      */
     @Override
     public Bundle getUserRestrictions(int userId) {
-        Bundle restrictions = getEffectiveUserRestrictions(userId);
-        return restrictions != null ? new Bundle(restrictions) : new Bundle();
+        return UserRestrictionsUtils.clone(getEffectiveUserRestrictions(userId));
     }
 
     @Override
     public void setUserRestriction(String key, boolean value, int userId) {
         checkManageUsersPermission("setUserRestriction");
-        if (!UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(key)) {
+        if (!UserRestrictionsUtils.isSystemControlled(key)) {
             setUserRestrictionNoCheck(key, value, userId);
         }
     }
@@ -731,8 +796,8 @@
         synchronized (mRestrictionsLock) {
             // Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
             // a copy.
-            final Bundle newRestrictions = new Bundle();
-            UserRestrictionsUtils.merge(newRestrictions, mBaseUserRestrictions.get(userId));
+            final Bundle newRestrictions = UserRestrictionsUtils.clone(
+                    mBaseUserRestrictions.get(userId));
             newRestrictions.putBoolean(key, value);
 
             updateUserRestrictionsInternalLR(newRestrictions, userId);
@@ -740,75 +805,70 @@
     }
 
     /**
-     * Optionally updating user restrictions, calculate the effective user restrictions by
-     * consulting {@link com.android.server.devicepolicy.DevicePolicyManagerService} and also
-     * apply it to {@link com.android.server.AppOpsService}.
-     * TODO applyUserRestrictionsLocked() should also apply to system settings.
+     * Optionally updating user restrictions, calculate the effective user restrictions and also
+     * propagate to other services and system settings.
      *
-     * @param newRestrictions User restrictions to set.  If null, only the effective restrictions
-     *     will be updated.  Note don't pass an existing Bundle in {@link #mBaseUserRestrictions}
-     *     or {@link #mCachedEffectiveUserRestrictions}; that'll most likely cause a sub
+     * @param newRestrictions User restrictions to set.
+     *      If null, will not update user restrictions and only does the propagation.
      * @param userId target user ID.
      */
     @GuardedBy("mRestrictionsLock")
     private void updateUserRestrictionsInternalLR(
             @Nullable Bundle newRestrictions, int userId) {
-        if (DBG) {
-            Log.d(LOG_TAG, "updateUserRestrictionsInternalLocked userId=" + userId
-                    + " bundle=" + newRestrictions);
-        }
-        // Update system restrictions.
+
+        final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
+                mAppliedUserRestrictions.get(userId));
+
+        // Update base restrictions.
         if (newRestrictions != null) {
             // If newRestrictions == the current one, it's probably a bug.
-            Preconditions.checkState(mBaseUserRestrictions.get(userId) != newRestrictions);
+            final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
+
+            Preconditions.checkState(prevBaseRestrictions != newRestrictions);
             Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
                     != newRestrictions);
-            mBaseUserRestrictions.put(userId, newRestrictions);
-            scheduleWriteUser(getUserInfoNoChecks(userId));
+
+            if (!UserRestrictionsUtils.areEqual(prevBaseRestrictions, newRestrictions)) {
+                mBaseUserRestrictions.put(userId, newRestrictions);
+                scheduleWriteUser(getUserInfoNoChecks(userId));
+            }
         }
 
         final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
 
         mCachedEffectiveUserRestrictions.put(userId, effective);
 
-        applyUserRestrictionsLR(userId, effective);
-    }
-
-    @GuardedBy("mRestrictionsLock")
-    private void applyUserRestrictionsLR(int userId, Bundle newRestrictions) {
-        if (newRestrictions == null) {
-            newRestrictions = Bundle.EMPTY;
-        }
-
-        Bundle prevRestrictions = mAppliedUserRestrictions.get(userId);
-        if (prevRestrictions == null) {
-            prevRestrictions = Bundle.EMPTY;
-        }
-
+        // Apply the new restrictions.
         if (DBG) {
-            Log.d(LOG_TAG, "applyUserRestrictionsRL userId=" + userId
-                    + " new=" + newRestrictions + " prev=" + prevRestrictions);
+            debug("Applying user restrictions: userId=" + userId
+                    + " new=" + effective + " prev=" + prevAppliedRestrictions);
         }
 
-        final long token = Binder.clearCallingIdentity();
-        try {
-            mAppOpsService.setUserRestrictions(newRestrictions, userId);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-        } finally {
-            Binder.restoreCallingIdentity(token);
+        if (mAppOpsService != null) { // We skip it until system-ready.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mAppOpsService.setUserRestrictions(effective, userId);
+            } catch (RemoteException e) {
+                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
-        UserRestrictionsUtils.applyUserRestrictionsLR(
-                mContext, userId, newRestrictions, prevRestrictions);
+        propagateUserRestrictionsLR(userId, effective, prevAppliedRestrictions);
 
-        notifyUserRestrictionsListeners(userId, newRestrictions, prevRestrictions);
-
-        mAppliedUserRestrictions.put(userId, new Bundle(newRestrictions));
+        mAppliedUserRestrictions.put(userId, new Bundle(effective));
     }
 
-    private void notifyUserRestrictionsListeners(final int userId,
+    private void propagateUserRestrictionsLR(final int userId,
             Bundle newRestrictions, Bundle prevRestrictions) {
+        // Note this method doesn't touch any state, meaning it doesn't require mRestrictionsLock
+        // actually, but we still need some kind of synchronization otherwise we might end up
+        // calling listeners out-of-order, thus "LR".
+
+        if (UserRestrictionsUtils.areEqual(newRestrictions, prevRestrictions)) {
+            return;
+        }
 
         final Bundle newRestrictionsFinal = new Bundle(newRestrictions);
         final Bundle prevRestrictionsFinal = new Bundle(prevRestrictions);
@@ -816,6 +876,11 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
+                synchronized (mRestrictionsLock) {
+                    UserRestrictionsUtils.applyUserRestrictionsLR(
+                            mContext, userId, newRestrictionsFinal, prevRestrictionsFinal);
+                }
+
                 final UserRestrictionsListener[] listeners;
                 synchronized (mUserRestrictionsListeners) {
                     listeners = new UserRestrictionsListener[mUserRestrictionsListeners.size()];
@@ -829,13 +894,17 @@
         });
     }
 
-    @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsLR(int userId) {
+    // Package private for the inner class.
+    void applyUserRestrictionsLR(int userId) {
         updateUserRestrictionsInternalLR(null, userId);
     }
 
     @GuardedBy("mRestrictionsLock")
-    private void updateEffectiveUserRestrictionsForAllUsersLR() {
+    // Package private for the inner class.
+    void applyUserRestrictionsForAllUsersLR() {
+        if (DBG) {
+            debug("applyUserRestrictionsForAllUsersLR");
+        }
         // First, invalidate all cached values.
         mCachedEffectiveUserRestrictions.clear();
 
@@ -856,10 +925,9 @@
                 // It's okay if a new user has started after the getRunningUserIds() call,
                 // because we'll do the same thing (re-calculate the restrictions and apply)
                 // when we start a user.
-                // TODO: "Apply restrictions upon user start hasn't been implemented.  Implement it.
                 synchronized (mRestrictionsLock) {
                     for (int i = 0; i < runningUsers.length; i++) {
-                        updateUserRestrictionsInternalLR(null, runningUsers[i]);
+                        applyUserRestrictionsLR(runningUsers[i]);
                     }
                 }
             }
@@ -1020,6 +1088,8 @@
                 }
             }
 
+            final Bundle newDevicePolicyGlobalUserRestrictions = new Bundle();
+
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
                 if (type == XmlPullParser.START_TAG) {
                     final String name = parser.getName();
@@ -1044,6 +1114,10 @@
                                         UserRestrictionsUtils
                                                 .readRestrictions(parser, mGuestRestrictions);
                                     }
+                                } else if (parser.getName().equals(TAG_DEVICE_POLICY_RESTRICTIONS)
+                                        ) {
+                                    UserRestrictionsUtils.readRestrictions(parser,
+                                            newDevicePolicyGlobalUserRestrictions);
                                 }
                                 break;
                             }
@@ -1051,6 +1125,9 @@
                     }
                 }
             }
+            synchronized (mRestrictionsLock) {
+                mDevicePolicyGlobalUserRestrictions = newDevicePolicyGlobalUserRestrictions;
+            }
             updateUserIds();
             upgradeIfNecessaryLP();
         } catch (IOException | XmlPullParserException e) {
@@ -1064,6 +1141,7 @@
      * Upgrade steps between versions, either for fixing bugs or changing the data format.
      */
     private void upgradeIfNecessaryLP() {
+        final int originalVersion = mUserVersion;
         int userVersion = mUserVersion;
         if (userVersion < 1) {
             // Assign a proper name for the owner, if not initialized correctly before
@@ -1116,7 +1194,10 @@
                     + USER_VERSION);
         } else {
             mUserVersion = userVersion;
-            writeUserListLP();
+
+            if (originalVersion < mUserVersion) {
+                writeUserListLP();
+            }
         }
     }
 
@@ -1150,6 +1231,9 @@
     }
 
     private void scheduleWriteUser(UserInfo userInfo) {
+        if (DBG) {
+            debug("scheduleWriteUser");
+        }
         // No need to wrap it within a lock -- worst case, we'll just post the same message
         // twice.
         if (!mHandler.hasMessages(WRITE_USER_MSG, userInfo)) {
@@ -1166,6 +1250,9 @@
      * </user>
      */
     private void writeUserLP(UserInfo userInfo) {
+        if (DBG) {
+            debug("writeUserLP " + userInfo);
+        }
         FileOutputStream fos = null;
         AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
         try {
@@ -1205,13 +1292,12 @@
             serializer.startTag(null, TAG_NAME);
             serializer.text(userInfo.name);
             serializer.endTag(null, TAG_NAME);
-            Bundle restrictions;
             synchronized (mRestrictionsLock) {
-                restrictions = mBaseUserRestrictions.get(userInfo.id);
-            }
-            if (restrictions != null) {
-                UserRestrictionsUtils
-                        .writeRestrictions(serializer, restrictions, TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+                        TAG_DEVICE_POLICY_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_USER);
 
@@ -1232,6 +1318,9 @@
      * </users>
      */
     private void writeUserListLP() {
+        if (DBG) {
+            debug("writeUserList");
+        }
         FileOutputStream fos = null;
         AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
@@ -1254,6 +1343,10 @@
                         .writeRestrictions(serializer, mGuestRestrictions, TAG_RESTRICTIONS);
             }
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils.writeRestrictions(serializer,
+                        mDevicePolicyGlobalUserRestrictions, TAG_DEVICE_POLICY_RESTRICTIONS);
+            }
             int[] userIdsToWrite;
             synchronized (mUsersLock) {
                 userIdsToWrite = new int[mUsers.size()];
@@ -1289,7 +1382,8 @@
         int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
         boolean partial = false;
         boolean guestToRemove = false;
-        Bundle restrictions = new Bundle();
+        Bundle baseRestrictions = new Bundle();
+        Bundle localRestrictions = new Bundle();
 
         FileInputStream fis = null;
         try {
@@ -1346,7 +1440,9 @@
                             name = parser.getText();
                         }
                     } else if (TAG_RESTRICTIONS.equals(tag)) {
-                        UserRestrictionsUtils.readRestrictions(parser, restrictions);
+                        UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+                    } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+                        UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
                     }
                 }
             }
@@ -1360,7 +1456,8 @@
             userInfo.profileGroupId = profileGroupId;
             userInfo.restrictedProfileParentId = restrictedProfileParentId;
             synchronized (mRestrictionsLock) {
-                mBaseUserRestrictions.append(id, restrictions);
+                mBaseUserRestrictions.put(id, baseRestrictions);
+                mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
             }
             return userInfo;
 
@@ -2118,6 +2215,15 @@
     }
 
     /**
+     * Called right before a user starts.  This will not be called for the system user.
+     */
+    public void onBeforeStartUser(int userId) {
+        synchronized (mRestrictionsLock) {
+            applyUserRestrictionsLR(userId);
+        }
+    }
+
+    /**
      * Make a note of the last started time of a user and do some cleanup.
      * @param userId the user that was just foregrounded
      */
@@ -2313,16 +2419,25 @@
                     synchronized (mRestrictionsLock) {
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mBaseUserRestrictions.get(user.id));
+                        pw.println("    Device policy local restrictions:");
+                        UserRestrictionsUtils.dumpRestrictions(
+                                pw, "      ", mDevicePolicyLocalUserRestrictions.get(user.id));
                         pw.println("    Effective restrictions:");
                         UserRestrictionsUtils.dumpRestrictions(
                                 pw, "      ", mCachedEffectiveUserRestrictions.get(user.id));
                     }
+                    pw.println();
                 }
             }
+            pw.println("  Device policy global restrictions:");
+            synchronized (mRestrictionsLock) {
+                UserRestrictionsUtils
+                        .dumpRestrictions(pw, "    ", mDevicePolicyGlobalUserRestrictions);
+            }
             pw.println();
-            pw.println("Guest restrictions:");
+            pw.println("  Guest restrictions:");
             synchronized (mGuestRestrictions) {
-                UserRestrictionsUtils.dumpRestrictions(pw, "  ", mGuestRestrictions);
+                UserRestrictionsUtils.dumpRestrictions(pw, "    ", mGuestRestrictions);
             }
         }
     }
@@ -2354,22 +2469,11 @@
     }
 
     private class LocalService extends UserManagerInternal {
-
         @Override
-        public Object getUserRestrictionsLock() {
-            return mRestrictionsLock;
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsLR(int userId) {
-            UserManagerService.this.updateEffectiveUserRestrictionsLR(userId);
-        }
-
-        @Override
-        @GuardedBy("mRestrictionsLock")
-        public void updateEffectiveUserRestrictionsForAllUsersLR() {
-            UserManagerService.this.updateEffectiveUserRestrictionsForAllUsersLR();
+        public void setDevicePolicyUserRestrictions(int userId, @NonNull Bundle localRestrictions,
+                @Nullable Bundle globalRestrictions) {
+            UserManagerService.this.setDevicePolicyUserRestrictions(userId, localRestrictions,
+                    globalRestrictions);
         }
 
         @Override
@@ -2434,4 +2538,9 @@
             pw.println("    Prints all users on the system.");
         }
     }
+
+    private static void debug(String message) {
+        Log.d(LOG_TAG, message +
+                (DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, "  ") : ""));
+    }
 }
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 56e8b3e..129cbd3 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -18,6 +18,10 @@
 
 import com.google.android.collect.Sets;
 
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.net.Uri;
@@ -26,6 +30,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
@@ -79,22 +84,64 @@
             UserManager.DISALLOW_SAFE_BOOT,
             UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
             UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_CAMERA,
     };
 
     /**
      * Set of user restrictions, which can only be enforced by the system.
      */
     public static final Set<String> SYSTEM_CONTROLLED_USER_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO);
+            UserManager.DISALLOW_RECORD_AUDIO
+    );
 
     /**
      * Set of user restriction which we don't want to persist.
      */
-    public static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
-            UserManager.DISALLOW_RECORD_AUDIO);
+    private static final Set<String> NON_PERSIST_USER_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO
+    );
 
-    public static void writeRestrictions(XmlSerializer serializer, Bundle restrictions,
-            String tag) throws IOException {
+    /**
+     * User restrictions that can not be set by profile owners.
+     */
+    private static final Set<String> DEVICE_OWNER_ONLY_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_USB_FILE_TRANSFER,
+            UserManager.DISALLOW_CONFIG_TETHERING,
+            UserManager.DISALLOW_NETWORK_RESET,
+            UserManager.DISALLOW_FACTORY_RESET,
+            UserManager.DISALLOW_ADD_USER,
+            UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+            UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+            UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_FUN,
+            UserManager.DISALLOW_SAFE_BOOT,
+            UserManager.DISALLOW_CREATE_WINDOWS
+    );
+
+    /**
+     * User restrictions that can't be changed by device owner or profile owner.
+     */
+    private static final Set<String> IMMUTABLE_BY_OWNERS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO,
+            UserManager.DISALLOW_WALLPAPER
+    );
+
+    /**
+     * Special user restrictions that can be applied to a user as well as to all users globally,
+     * depending on callers.  When device owner sets them, they'll be applied to all users.
+     */
+    private static final Set<String> GLOBAL_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_ADJUST_VOLUME,
+            UserManager.DISALLOW_UNMUTE_MICROPHONE
+    );
+
+    public static void writeRestrictions(@NonNull XmlSerializer serializer,
+            @Nullable Bundle restrictions, @NonNull String tag) throws IOException {
+        if (restrictions == null) {
+            return;
+        }
+
         serializer.startTag(null, tag);
         for (String key : USER_RESTRICTIONS) {
             if (restrictions.getBoolean(key)
@@ -115,7 +162,31 @@
         }
     }
 
-    public static void merge(Bundle dest, Bundle in) {
+    /**
+     * @return {@code in} itself when it's not null, or an empty bundle (which can writable).
+     */
+    public static Bundle nonNull(@Nullable Bundle in) {
+        return in != null ? in : new Bundle();
+    }
+
+    public static boolean isEmpty(@Nullable Bundle in) {
+        return (in == null) || (in.size() == 0);
+    }
+
+    /**
+     * Creates a copy of the {@code in} Bundle.  If {@code in} is null, it'll return an empty
+     * bundle.
+     *
+     * <p>The resulting {@link Bundle} is always writable. (i.e. it won't return
+     * {@link Bundle#EMPTY})
+     */
+    public static @NonNull Bundle clone(@Nullable Bundle in) {
+        return (in != null) ? new Bundle(in) : new Bundle();
+    }
+
+    public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
+        Preconditions.checkNotNull(dest);
+        Preconditions.checkArgument(dest != in);
         if (in == null) {
             return;
         }
@@ -127,6 +198,77 @@
     }
 
     /**
+     * @return true if a restriction is "system controlled"; i.e. can not be overwritten via
+     * {@link UserManager#setUserRestriction}.
+     */
+    public static boolean isSystemControlled(String restriction) {
+        return SYSTEM_CONTROLLED_USER_RESTRICTIONS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by device owner.
+     */
+    public static boolean canDeviceOwnerChange(String restriction) {
+        return !IMMUTABLE_BY_OWNERS.contains(restriction);
+    }
+
+    /**
+     * @return true if a restriction is settable by profile owner.
+     */
+    public static boolean canProfileOwnerChange(String restriction) {
+        return !(IMMUTABLE_BY_OWNERS.contains(restriction)
+                || DEVICE_OWNER_ONLY_RESTRICTIONS.contains(restriction));
+    }
+
+    /**
+     * Takes restrictions that can be set by device owner, and sort them into what should be applied
+     * globally and what should be applied only on the current user.
+     */
+    public static void sortToGlobalAndLocal(@Nullable Bundle in, @NonNull Bundle global,
+            @NonNull Bundle local) {
+        if (in == null || in.size() == 0) {
+            return;
+        }
+        for (String key : in.keySet()) {
+            if (!in.getBoolean(key)) {
+                continue;
+            }
+            if (DEVICE_OWNER_ONLY_RESTRICTIONS.contains(key) || GLOBAL_RESTRICTIONS.contains(key)) {
+                global.putBoolean(key, true);
+            } else {
+                local.putBoolean(key, true);
+            }
+        }
+    }
+
+    /**
+     * @return true if two Bundles contain the same user restriction.
+     * A null bundle and an empty bundle are considered to be equal.
+     */
+    public static boolean areEqual(@Nullable Bundle a, @Nullable Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (isEmpty(a)) {
+            return isEmpty(b);
+        }
+        if (isEmpty(b)) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        for (String key : b.keySet()) {
+            if (a.getBoolean(key) != b.getBoolean(key)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Takes a new use restriction set and the previous set, and apply the restrictions that have
      * changed.
      *
@@ -145,7 +287,7 @@
             }
         }
     }
-
+    
     /**
      * Apply each user restriction.
      *
@@ -155,6 +297,10 @@
      */
     private static void applyUserRestrictionLR(Context context, int userId, String key,
             boolean newValue) {
+        if (UserManagerService.DBG) {
+            Log.d(TAG, "Applying user restriction: userId=" + userId
+                    + " key=" + key + " value=" + newValue);
+        }
         // When certain restrictions are cleared, we don't update the system settings,
         // because these settings are changeable on the Settings UI and we don't know the original
         // value -- for example LOCATION_MODE might have been off already when the restriction was
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 8fc979c..cc25c8c 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -17,6 +17,7 @@
 package com.android.server.updates;
 
 import com.android.server.EventLogTags;
+import com.android.internal.util.HexDump;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -155,7 +156,7 @@
         try {
             MessageDigest dgst = MessageDigest.getInstance("SHA512");
             byte[] fingerprint = dgst.digest(content);
-            return IntegralToString.bytesToHexString(fingerprint, false);
+            return HexDump.toHexString(fingerprint, false);
         } catch (NoSuchAlgorithmException e) {
             throw new AssertionError(e);
         }
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index dfd01ef..b32ec2d 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -228,7 +228,8 @@
 
     private void stepThumbnailAnimation(long currentTime) {
         thumbnailTransformation.clear();
-        thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation);
+        final long animationFrameTime = getAnimationFrameTime(thumbnailAnimation, currentTime);
+        thumbnailAnimation.getTransformation(animationFrameTime, thumbnailTransformation);
         thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
 
         ScreenRotationAnimation screenRotationAnimation =
@@ -265,16 +266,26 @@
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
     }
 
+    /**
+     * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
+     * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
+     * and keep producing the first frame of the animation.
+     */
+    private long getAnimationFrameTime(Animation animation, long currentTime) {
+        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
+            animation.setStartTime(currentTime);
+            return currentTime + 1;
+        }
+        return currentTime;
+    }
+
     private boolean stepAnimation(long currentTime) {
         if (animation == null) {
             return false;
         }
         transformation.clear();
-        if (mProlongAnimation == PROLONG_ANIMATION_AT_START) {
-            animation.setStartTime(currentTime);
-            currentTime += 1;
-        }
-        boolean hasMoreFrames = animation.getTransformation(currentTime, transformation);
+        final long animationFrameTime = getAnimationFrameTime(animation, currentTime);
+        boolean hasMoreFrames = animation.getTransformation(animationFrameTime, transformation);
         if (!hasMoreFrames) {
             if (deferThumbnailDestruction && !deferFinalFrameCleanup) {
                 // We are deferring the thumbnail destruction, so extend the animation for one more
@@ -290,10 +301,10 @@
                     hasMoreFrames = true;
                 } else {
                     animation = null;
+                    clearThumbnail();
+                    if (DEBUG_ANIM) Slog.v(TAG, "Finished animation in " + mAppToken + " @ "
+                            + currentTime);
                 }
-                clearThumbnail();
-                if (DEBUG_ANIM) Slog.v(TAG,
-                        "Finished animation in " + mAppToken + " @ " + currentTime);
             }
         }
         hasTransformation = hasMoreFrames;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 4926352..74750a9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -16,6 +16,14 @@
 
 package com.android.server.wm;
 
+import android.graphics.Matrix;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputWindowHandle;
 import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
@@ -45,6 +53,8 @@
  * Drag/drop state
  */
 class DragState {
+    private static final long ANIMATION_DURATION_MS = 500;
+
     final WindowManagerService mService;
     IBinder mToken;
     SurfaceControl mSurfaceControl;
@@ -55,6 +65,8 @@
     ClipData mData;
     ClipDescription mDataDescription;
     boolean mDragResult;
+    float mOriginalAlpha;
+    float mOriginalX, mOriginalY;
     float mCurrentX, mCurrentY;
     float mThumbOffsetX, mThumbOffsetY;
     InputChannel mServerChannel, mClientChannel;
@@ -69,6 +81,10 @@
     private final Region mTmpRegion = new Region();
     private final Rect mTmpRect = new Rect();
 
+    private Animation mAnimation;
+    final Transformation mTransformation = new Transformation();
+    private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
+
     DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
             int flags, IBinder localWin) {
         mService = service;
@@ -184,6 +200,9 @@
     /* call out to each visible window/session informing it about the drag
      */
     void broadcastDragStartedLw(final float touchX, final float touchY) {
+        mOriginalX = mCurrentX = touchX;
+        mOriginalY = mCurrentY = touchY;
+
         // Cache a base-class instance of the clip metadata so that parceling
         // works correctly in calling out to the apps.
         mDataDescription = (mData != null) ? mData.getDescription() : null;
@@ -294,19 +313,32 @@
     }
 
     void endDragLw() {
-        mService.mDragState.broadcastDragEndedLw();
+        if (!mDragResult) {
+            mAnimation = createReturnAnimationLocked();
+            mService.scheduleAnimationLocked();
+            return;  // Will call cleanUpDragLw when the animation is done.
+        }
+        cleanUpDragLw();
+    }
+
+
+    void cleanUpDragLw() {
+        broadcastDragEndedLw();
 
         // stop intercepting input
-        mService.mDragState.unregister();
+        unregister();
 
         // free our resources and drop all the object references
-        mService.mDragState.reset();
+        reset();
         mService.mDragState = null;
 
         mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
     }
 
     void notifyMoveLw(float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+
         final int myPid = Process.myPid();
 
         // Move the surface to the given touch
@@ -378,6 +410,9 @@
     // result from the recipient.
     boolean notifyDropLw(WindowState touchedWin, DropPermissionHolder dropPermissionHolder,
             float x, float y) {
+        mCurrentX = x;
+        mCurrentY = y;
+
         if (touchedWin == null) {
             // "drop" outside a valid window -- no recipient to apply a
             // timeout to, and we can send the drag-ended message immediately.
@@ -469,4 +504,38 @@
         return DragEvent.obtain(action, winX, winY, localState, description, data,
                 dropPermissionHolder, result);
     }
+
+    boolean stepAnimationLocked(long currentTimeMs) {
+        if (mAnimation == null) {
+            return false;
+        }
+
+        mTransformation.clear();
+        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
+            cleanUpDragLw();
+            return false;
+        }
+
+        final float tmpFloats[] = mService.mTmpFloats;
+        mTransformation.getMatrix().getValues(tmpFloats);
+        mSurfaceControl.setPosition(
+                tmpFloats[Matrix.MTRANS_X] - mThumbOffsetX,
+                tmpFloats[Matrix.MTRANS_Y] - mThumbOffsetY);
+        mSurfaceControl.setAlpha(mTransformation.getAlpha());
+        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+        return true;
+    }
+
+    private Animation createReturnAnimationLocked() {
+        final AnimationSet set = new AnimationSet(false);
+        set.addAnimation(new TranslateAnimation(
+                mCurrentX, mOriginalX, mCurrentY, mOriginalY));
+        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
+        set.setDuration(ANIMATION_DURATION_MS);
+        set.setInterpolator(mCubicEaseOutInterpolator);
+        set.initialize(0, 0, 0, 0);
+        set.start();  // Will start on the first call to getTransformation.
+        return set;
+    }
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b85a6923..f23fcdb 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -325,8 +325,6 @@
             }
 
             mService.mDragState.mData = data;
-            mService.mDragState.mCurrentX = touchX;
-            mService.mDragState.mCurrentY = touchY;
             mService.mDragState.broadcastDragStartedLw(touchX, touchY);
 
             // remember the thumb offsets for later
@@ -401,22 +399,24 @@
         }
     }
 
-    public void cancelDrag(IBinder dragToken) {
+    public void cancelDragAndDrop(IBinder dragToken) {
         if (WindowManagerService.DEBUG_DRAG) {
-            Slog.d(WindowManagerService.TAG, "cancel drag");
+            Slog.d(WindowManagerService.TAG, "cancelDragAndDrop");
         }
 
         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()");
+                    Slog.w(WindowManagerService.TAG, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
                 }
 
                 if (mService.mDragState.mToken != dragToken) {
-                    Slog.w(WindowManagerService.TAG, "cancelDrag() does not match prepareDrag()");
-                    throw new IllegalStateException("cancelDrag() does not match prepareDrag()");
+                    Slog.w(WindowManagerService.TAG,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
                 }
 
                 mService.mDragState.mDragResult = false;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 8085f13..04ab544 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -515,10 +515,11 @@
         for (int i = 0; i < count; i++) {
             final TaskStack otherStack = mService.mStackIdToStack.valueAt(i);
             final int otherStackId = otherStack.mStackId;
-            if (StackId.isResizeableByDockedStack(otherStackId)) {
+            if (StackId.isResizeableByDockedStack(otherStackId)
+                    && !otherStack.mBounds.equals(bounds)) {
                 mService.mH.sendMessage(
                         mService.mH.obtainMessage(RESIZE_STACK, otherStackId,
-                                1 /*allowResizeInDockedMode*/, bounds));
+                                1 /*allowResizeInDockedMode*/, fullscreen ? null : bounds));
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a96bd2c..46fab2a 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -708,6 +708,10 @@
                 }
             }
 
+            if (mService.mDragState != null) {
+                mAnimating |= mService.mDragState.stepAnimationLocked(mCurrentTime);
+            }
+
             if (mAnimating) {
                 mService.scheduleAnimationLocked();
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 253fdad..e741c45 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -309,6 +309,8 @@
     // trying to apply a new one.
     private static final boolean ALWAYS_KEEP_CURRENT = true;
 
+    private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
+
     final private KeyguardDisableHandler mKeyguardDisableHandler;
 
     final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -753,9 +755,6 @@
     private boolean completeDropLw(float x, float y) {
         WindowState dropTargetWin = mDragState.getDropTargetWinLw(x, y);
 
-        mDragState.mCurrentX = x;
-        mDragState.mCurrentY = y;
-
         DropPermissionHolder dropPermissionHolder = null;
         if (dropTargetWin != null &&
                 (mDragState.mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
@@ -2863,7 +2862,11 @@
         // notifying the client to render to with an offset from the surface's top-left.
         if (win.isDragResizeChanged()) {
             win.setDragResizing();
-            if (win.mHasSurface) {
+            // We can only change top level windows to the full-screen surface when
+            // resizing (as we only have one full-screen surface). So there is no need
+            // to preserve and destroy windows which are attached to another, they 
+            // will keep their surface and its size may change over time.
+            if (win.mHasSurface && win.mAttachedWindow == null) {
                 winAnimator.preserveSurfaceLocked();
                 result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
             }
@@ -7171,9 +7174,11 @@
                         SurfaceControl surface = new SurfaceControl(session, "drag surface",
                                 width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
                         surface.setLayerStack(display.getLayerStack());
+                        float alpha = 1;
                         if ((flags & View.DRAG_FLAG_OPAQUE) == 0) {
-                            surface.setAlpha(.7071f);
+                            alpha = DRAG_SHADOW_ALPHA_TRANSPARENT;
                         }
+                        surface.setAlpha(alpha);
 
                         if (SHOW_TRANSACTIONS) Slog.i(TAG, "  DRAG "
                                 + surface + ": CREATE");
@@ -7183,6 +7188,7 @@
                         mDragState = new DragState(this, token, surface, flags, winBinder);
                         mDragState.mPid = callerPid;
                         mDragState.mUid = callerUid;
+                        mDragState.mOriginalAlpha = alpha;
                         token = mDragState.mToken = new Binder();
 
                         // 5 second timeout for this window to actually begin the drag
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6faf3a7..004307e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -735,7 +735,12 @@
 
         if (mSurfaceController != null) {
             int i = mWin.mChildWindows.size();
-            while (i > 0) {
+            // When destroying a surface we want to make sure child windows
+            // are hidden. If we are preserving the surface until redraw though
+            // we intend to swap it out with another surface for resizing. In this case
+            // the window always remains visible to the user and the child windows
+            // should likewise remain visable.
+            while (!mDestroyPreservedSurfaceUponRedraw && i > 0) {
                 i--;
                 WindowState c = mWin.mChildWindows.get(i);
                 c.mAttachedHidden = true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 844cca5..4c15809 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -60,6 +60,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
@@ -201,31 +202,6 @@
     private static final int STATUS_BAR_DISABLE2_MASK =
             StatusBarManager.DISABLE2_QUICK_SETTINGS;
 
-    private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
-    static {
-        DEVICE_OWNER_USER_RESTRICTIONS = new ArraySet<>();
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_USB_FILE_TRANSFER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_TETHERING);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_NETWORK_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FACTORY_RESET);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_ADD_USER);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_CELL_BROADCASTS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SMS);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_FUN);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_SAFE_BOOT);
-        DEVICE_OWNER_USER_RESTRICTIONS.add(UserManager.DISALLOW_CREATE_WINDOWS);
-    }
-
-    // The following user restrictions cannot be changed by any active admin, including device
-    // owner and profile owner.
-    private static final Set<String> IMMUTABLE_USER_RESTRICTIONS;
-    static {
-        IMMUTABLE_USER_RESTRICTIONS = new ArraySet<>();
-        IMMUTABLE_USER_RESTRICTIONS.add(UserManager.DISALLOW_WALLPAPER);
-    }
-
     private static final Set<String> SECURE_SETTINGS_WHITELIST;
     private static final Set<String> SECURE_SETTINGS_DEVICEOWNER_WHITELIST;
     private static final Set<String> GLOBAL_SETTINGS_WHITELIST;
@@ -306,6 +282,11 @@
         public void onBootPhase(int phase) {
             mService.systemReady(phase);
         }
+
+        @Override
+        public void onStartUser(int userHandle) {
+            mService.onStartUser(userHandle);
+        }
     }
 
     public static class DevicePolicyData {
@@ -436,7 +417,7 @@
                 "cross-profile-widget-providers";
         private static final String TAG_PROVIDER = "provider";
         private static final String TAG_PACKAGE_LIST_ITEM  = "item";
-
+        private static final String TAG_KEEP_UNINSTALLED_PACKAGES  = "keep-uninstalled-packages";
         private static final String TAG_USER_RESTRICTIONS = "user-restrictions";
 
         final DeviceAdminInfo info;
@@ -509,6 +490,9 @@
         // allowed.
         List<String> permittedInputMethods;
 
+        // List of package names to keep cached.
+        List<String> keepUninstalledPackages;
+
         // TODO: review implementation decisions with frameworks team
         boolean specifiesGlobalProxy = false;
         String globalProxySpec = null;
@@ -694,6 +678,7 @@
             writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
                     permittedAccessiblityServices);
             writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+            writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
             if (hasUserRestrictions()) {
                 UserRestrictionsUtils.writeRestrictions(
                         out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -807,6 +792,8 @@
                     permittedAccessiblityServices = readPackageList(parser, tag);
                 } else if (TAG_PERMITTED_IMES.equals(tag)) {
                     permittedInputMethods = readPackageList(parser, tag);
+                } else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
+                    keepUninstalledPackages = readPackageList(parser, tag);
                 } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
                     UserRestrictionsUtils.readRestrictions(parser, ensureUserRestrictions());
                 } else {
@@ -1001,20 +988,23 @@
                     pw.println(disabledKeyguardFeatures);
             pw.print(prefix); pw.print("crossProfileWidgetProviders=");
                     pw.println(crossProfileWidgetProviders);
-            if (!(permittedAccessiblityServices == null)) {
+            if (permittedAccessiblityServices != null) {
                 pw.print(prefix); pw.print("permittedAccessibilityServices=");
-                        pw.println(permittedAccessiblityServices.toString());
+                    pw.println(permittedAccessiblityServices);
             }
-            if (!(permittedInputMethods == null)) {
+            if (permittedInputMethods != null) {
                 pw.print(prefix); pw.print("permittedInputMethods=");
-                        pw.println(permittedInputMethods.toString());
+                    pw.println(permittedInputMethods);
+            }
+            if (keepUninstalledPackages != null) {
+                pw.print(prefix); pw.print("keepUninstalledPackages=");
+                    pw.println(keepUninstalledPackages);
             }
             pw.print(prefix); pw.println("userRestrictions:");
             UserRestrictionsUtils.dumpRestrictions(pw, prefix + "  ", userRestrictions);
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     private void handlePackagesChanged(String packageName, int userHandle) {
         boolean removed = false;
         if (VERBOSE_LOG) Slog.d(LOG_TAG, "Handling package changes for user " + userHandle);
@@ -1060,11 +1050,8 @@
             }
         }
         if (removed) {
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                synchronized (DevicePolicyManagerService.this) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
+            // The removed admin might have disabled camera, so update user restrictions.
+            pushUserRestrictions(userHandle);
         }
     }
 
@@ -1092,6 +1079,10 @@
             return LocalServices.getService(UserManagerInternal.class);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -1350,8 +1341,6 @@
             // TODO PO may not have a class name either due to b/17652534.  Address that too.
 
             updateDeviceOwnerLocked();
-
-            // TODO Notify UM to update restrictions (?)
         }
     }
 
@@ -1403,6 +1392,9 @@
             migrateUserRestrictionsForUser(UserHandle.SYSTEM, deviceOwnerAdmin,
                     /* exceptionList =*/ UserRestrictionsUtils.SYSTEM_CONTROLLED_USER_RESTRICTIONS);
 
+            // Push DO user restrictions to user manager.
+            pushUserRestrictions(UserHandle.USER_SYSTEM);
+
             mOwners.setDeviceOwnerUserRestrictionsMigrated();
         }
 
@@ -1432,6 +1424,13 @@
 
                     migrateUserRestrictionsForUser(ui.getUserHandle(), profileOwnerAdmin,
                             exceptionList);
+
+                    // Note if a secondary user has no PO but has a DA that disables camera, we
+                    // don't get here and won't push the camera user restriction to UserManager
+                    // here.  That's okay because we'll push user restrictions anyway when a user
+                    // starts.  But we still do it because we want to let user manager persist
+                    // upon migration.
+                    pushUserRestrictions(userId);
                 }
 
                 mOwners.setProfileOwnerUserRestrictionsMigrated(userId);
@@ -1709,12 +1708,9 @@
                                 updateMaximumTimeToLockLocked(policy);
                                 policy.mRemovingAdmins.remove(adminReceiver);
                             }
-                            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                                synchronized (DevicePolicyManagerService.this) {
-                                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(
-                                            userHandle);
-                                }
-                            }
+                            // The removed admin might have disabled camera, so update user
+                            // restrictions.
+                            pushUserRestrictions(userHandle);
                         }
                     });
         }
@@ -2113,18 +2109,20 @@
         getUserData(UserHandle.USER_SYSTEM);
         loadOwners();
         cleanUpOldUsers();
+
+        onStartUser(UserHandle.USER_SYSTEM);
+
         // Register an observer for watching for user setup complete.
         new SetupContentObserver(mHandler).register(mContext.getContentResolver());
         // Initialize the user setup state, to handle the upgrade case.
         updateUserSetupComplete();
 
-        // Update the screen capture disabled cache in the window manager
-        List<UserInfo> users = mUserManager.getUsers(true);
-        final int N = users.size();
-        for (int i = 0; i < N; i++) {
-            int userHandle = users.get(i).id;
-            updateScreenCaptureDisabledInWindowManager(userHandle,
-                    getScreenCaptureDisabled(null, userHandle));
+        List<String> packageList;
+        synchronized (this) {
+            packageList = getKeepUninstalledPackagesLocked();
+        }
+        if (packageList != null) {
+            mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
         }
     }
 
@@ -2147,6 +2145,12 @@
         }
     }
 
+    private void onStartUser(int userId) {
+        updateScreenCaptureDisabledInWindowManager(userId,
+                getScreenCaptureDisabled(null, userId));
+        pushUserRestrictions(userId);
+    }
+
     private void cleanUpOldUsers() {
         // This is needed in case the broadcast {@link Intent.ACTION_USER_REMOVED} was not handled
         // before reboot
@@ -4307,15 +4311,18 @@
         }
     }
 
-    private void updateScreenCaptureDisabledInWindowManager(int userHandle, boolean disabled) {
-        long ident = mInjector.binderClearCallingIdentity();
-        try {
-            mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
-        } catch (RemoteException e) {
-            Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
-        } finally {
-            mInjector.binderRestoreCallingIdentity(ident);
-        }
+    private void updateScreenCaptureDisabledInWindowManager(final int userHandle,
+            final boolean disabled) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mInjector.getIWindowManager().setScreenCaptureDisabled(userHandle, disabled);
+                } catch (RemoteException e) {
+                    Log.w(LOG_TAG, "Unable to notify WindowManager.", e);
+                }
+            }
+        });
     }
 
     /**
@@ -4381,15 +4388,7 @@
             }
         }
         // Tell the user manager that the restrictions have changed.
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                if (isDeviceOwner(who, userHandle)) {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                } else {
-                    mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                }
-            }
-        }
+        pushUserRestrictions(userHandle);
     }
 
     /**
@@ -4398,6 +4397,11 @@
      */
     @Override
     public boolean getCameraDisabled(ComponentName who, int userHandle) {
+        return getCameraDisabled(who, userHandle, /* mergeDeviceOwnerRestriction= */ true);
+    }
+
+    private boolean getCameraDisabled(ComponentName who, int userHandle,
+            boolean mergeDeviceOwnerRestriction) {
         if (!mHasFeature) {
             return false;
         }
@@ -4407,9 +4411,11 @@
                 return (admin != null) ? admin.disableCamera : false;
             }
             // First, see if DO has set it.  If so, it's device-wide.
-            final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-            if (deviceOwner != null && deviceOwner.disableCamera) {
-                return true;
+            if (mergeDeviceOwnerRestriction) {
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner != null && deviceOwner.disableCamera) {
+                    return true;
+                }
             }
 
             // Then check each device admin on the user.
@@ -4507,6 +4513,42 @@
     }
 
     @Override
+    public void setKeepUninstalledPackages(ComponentName who, List<String> packageList) {
+        if (!mHasFeature) {
+            return;
+        }
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(packageList, "packageList is null");
+        final int userHandle = UserHandle.getCallingUserId();
+        synchronized (this) {
+            ActiveAdmin admin = getActiveAdminForCallerLocked(who,
+                    DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            admin.keepUninstalledPackages = packageList;
+            saveSettingsLocked(userHandle);
+            mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
+        }
+    }
+
+    @Override
+    public List<String> getKeepUninstalledPackages(ComponentName who) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        if (!mHasFeature) {
+            return null;
+        }
+        // TODO In split system user mode, allow apps on user 0 to query the list
+        synchronized (this) {
+            // Check if this is the device owner who is calling
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            return getKeepUninstalledPackagesLocked();
+        }
+    }
+
+    private List<String> getKeepUninstalledPackagesLocked() {
+        ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+        return (deviceOwner != null) ? deviceOwner.keepUninstalledPackages : null;
+    }
+
+    @Override
     public boolean setDeviceOwner(ComponentName admin, String ownerName, int userId) {
         if (!mHasFeature) {
             return false;
@@ -4602,6 +4644,7 @@
                 return admin;
             }
         }
+        Slog.wtf(LOG_TAG, "Active admin for device owner not found. component=" + component);
         return null;
     }
 
@@ -4622,6 +4665,11 @@
             throw new SecurityException("clearDeviceOwner can only be called by the device owner");
         }
         synchronized (this) {
+            final ActiveAdmin admin = getDeviceOwnerAdminLocked();
+            if (admin != null) {
+                admin.disableCamera = false;
+                admin.userRestrictions = null;
+            }
             clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
 
             mOwners.clearDeviceOwner();
@@ -4667,6 +4715,7 @@
         final ActiveAdmin admin =
                 getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         synchronized (this) {
+            admin.disableCamera = false;
             admin.userRestrictions = null;
             clearUserPoliciesLocked(callingUser);
             final int userId = callingUser.getIdentifier();
@@ -4713,9 +4762,7 @@
             mIPackageManager.updatePermissionFlagsForAllApps(
                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
                     0  /* flagValues */, userHandle.getIdentifier());
-            synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-                mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle.getIdentifier());
-            }
+            pushUserRestrictions(userHandle.getIdentifier());
         } catch (RemoteException re) {
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
@@ -5004,31 +5051,30 @@
             return;
         }
 
-        final Printer p = new PrintWriterPrinter(pw);
-
         synchronized (this) {
-            p.println("Current Device Policy Manager state:");
+            pw.println("Current Device Policy Manager state:");
             mOwners.dump("  ", pw);
             int userCount = mUserData.size();
             for (int u = 0; u < userCount; u++) {
                 DevicePolicyData policy = getUserData(mUserData.keyAt(u));
-                p.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
+                pw.println();
+                pw.println("  Enabled Device Admins (User " + policy.mUserHandle + "):");
                 final int N = policy.mAdminList.size();
                 for (int i=0; i<N; i++) {
                     ActiveAdmin ap = policy.mAdminList.get(i);
                     if (ap != null) {
-                        pw.print("  "); pw.print(ap.info.getComponent().flattenToShortString());
+                        pw.print("    "); pw.print(ap.info.getComponent().flattenToShortString());
                                 pw.println(":");
-                        ap.dump("    ", pw);
+                        ap.dump("      ", pw);
                     }
                 }
                 if (!policy.mRemovingAdmins.isEmpty()) {
-                    p.println("  Removing Device Admins (User " + policy.mUserHandle + "): "
+                    pw.println("    Removing Device Admins (User " + policy.mUserHandle + "): "
                             + policy.mRemovingAdmins);
                 }
 
                 pw.println(" ");
-                pw.print("  mPasswordOwner="); pw.println(policy.mPasswordOwner);
+                pw.print("    mPasswordOwner="); pw.println(policy.mPasswordOwner);
             }
         }
     }
@@ -5666,56 +5712,83 @@
         }
     }
 
-    // DO NOT call it while taking the "this" lock, which could cause a dead lock.
     @Override
     public void setUserRestriction(ComponentName who, String key, boolean enabledFromThisOwner) {
         Preconditions.checkNotNull(who, "ComponentName is null");
         final int userHandle = mInjector.userHandleGetCallingUserId();
-        synchronized (mUserManagerInternal.getUserRestrictionsLock()) {
-            synchronized (this) {
-                ActiveAdmin activeAdmin =
-                        getActiveAdminForCallerLocked(who,
-                                DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-                final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
-                if (!isDeviceOwner && DEVICE_OWNER_USER_RESTRICTIONS.contains(key)) {
-                    throw new SecurityException(
-                            "Profile owners cannot set user restriction " + key);
+        synchronized (this) {
+            ActiveAdmin activeAdmin =
+                    getActiveAdminForCallerLocked(who,
+                            DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final boolean isDeviceOwner = isDeviceOwner(who, userHandle);
+            if (isDeviceOwner) {
+                if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+                    throw new SecurityException("Device owner cannot set user restriction " + key);
                 }
-                if (IMMUTABLE_USER_RESTRICTIONS.contains(key)) {
-                    throw new SecurityException("User restriction " + key + " cannot be changed");
+            } else { // profile owner
+                if (!UserRestrictionsUtils.canProfileOwnerChange(key)) {
+                    throw new SecurityException("Profile owner cannot set user restriction " + key);
                 }
-
-                final long id = mInjector.binderClearCallingIdentity();
-                try {
-                    // Save the restriction to ActiveAdmin.
-                    // TODO When DO sets a restriction, it'll always be treated as device-wide.
-                    // If there'll be a policy that can be set by both, we'll need scoping support,
-                    // and need to have another Bundle in DO active admin to hold restrictions as
-                    // PO.
-                    activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
-                    saveSettingsLocked(userHandle);
-
-                    // Tell UserManager the new value.
-                    if (isDeviceOwner) {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsForAllUsersLR();
-                    } else {
-                        mUserManagerInternal.updateEffectiveUserRestrictionsLR(userHandle);
-                    }
-                } finally {
-                    mInjector.binderRestoreCallingIdentity(id);
-                }
-
-                sendChangedNotification(userHandle);
             }
+
+            // Save the restriction to ActiveAdmin.
+            activeAdmin.ensureUserRestrictions().putBoolean(key, enabledFromThisOwner);
+            saveSettingsLocked(userHandle);
+
+            pushUserRestrictions(userHandle);
+
+            sendChangedNotification(userHandle);
+        }
+    }
+
+    private void pushUserRestrictions(int userId) {
+        synchronized (this) {
+            final Bundle global;
+            final Bundle local = new Bundle();
+            if (mOwners.isDeviceOwnerUserId(userId)) {
+                global = new Bundle();
+
+                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner == null) {
+                    return; // Shouldn't happen.
+                }
+
+                UserRestrictionsUtils.sortToGlobalAndLocal(deviceOwner.userRestrictions,
+                        global, local);
+                // DO can disable camera globally.
+                if (deviceOwner.disableCamera) {
+                    global.putBoolean(UserManager.DISALLOW_CAMERA, true);
+                }
+            } else {
+                global = null;
+
+                ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+                if (profileOwner != null) {
+                    UserRestrictionsUtils.merge(local, profileOwner.userRestrictions);
+                }
+            }
+            // Also merge in *local* camera restriction.
+            if (getCameraDisabled(/* who= */ null,
+                    userId, /* mergeDeviceOwnerRestriction= */ false)) {
+                local.putBoolean(UserManager.DISALLOW_CAMERA, true);
+            }
+            mUserManagerInternal.setDevicePolicyUserRestrictions(userId, local, global);
         }
     }
 
     @Override
-    public Bundle getUserRestrictions(ComponentName who) {
+    public Bundle getUserRestrictions(ComponentName who, int userHandle) {
         Preconditions.checkNotNull(who, "ComponentName is null");
+        enforceCrossUserPermission(userHandle);
         synchronized (this) {
-            final ActiveAdmin activeAdmin =
-                    getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            ActiveAdmin activeAdmin = getActiveAdminUncheckedLocked(who, userHandle);
+            if (activeAdmin == null) {
+                throw new SecurityException("No active admin: " + activeAdmin);
+            }
+            if (activeAdmin.getUid() != mInjector.binderGetCallingUid()) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, null);
+            }
             return activeAdmin.userRestrictions;
         }
     }
@@ -6448,37 +6521,6 @@
             }
         }
 
-        @Override
-        public Bundle getComposedUserRestrictions(int userId, Bundle inBundle) {
-            synchronized (DevicePolicyManagerService.this) {
-                final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
-                final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
-
-                final Bundle deviceOwnerRestrictions =
-                        deviceOwner == null ? null : deviceOwner.userRestrictions;
-                final Bundle profileOwnerRestrictions =
-                        profileOwner == null ? null : profileOwner.userRestrictions;
-                final boolean cameraDisabled = getCameraDisabled(null, userId);
-
-                if (deviceOwnerRestrictions == null && profileOwnerRestrictions == null
-                        && !cameraDisabled) {
-                    // No restrictions to merge.
-                    return inBundle;
-                }
-
-                final Bundle composed = new Bundle(inBundle);
-                UserRestrictionsUtils.merge(composed, deviceOwnerRestrictions);
-                UserRestrictionsUtils.merge(composed, profileOwnerRestrictions);
-
-                // Also merge in the camera restriction.
-                if (cameraDisabled) {
-                    composed.putBoolean(UserManager.DISALLOW_CAMERA, true);
-                }
-
-                return composed;
-            }
-        }
-
         private void notifyCrossProfileProvidersChanged(int userId, List<String> packages) {
             final List<OnCrossProfileWidgetProvidersChangeListener> listeners;
             synchronized (DevicePolicyManagerService.this) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index ded4422..435de7a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -221,6 +221,10 @@
         return mDeviceOwner != null;
     }
 
+    boolean isDeviceOwnerUserId(int userId) {
+        return mDeviceOwner != null && mDeviceOwnerUserId == userId;
+    }
+
     boolean hasProfileOwner(int userId) {
         return getProfileOwnerComponent(userId) != null;
     }
@@ -625,20 +629,30 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
+        boolean needBlank = false;
         if (mDeviceOwner != null) {
             pw.println(prefix + "Device Owner: ");
             mDeviceOwner.dump(prefix + "  ", pw);
             pw.println(prefix + "  User ID: " + mDeviceOwnerUserId);
-            pw.println();
+            needBlank = true;
         }
         if (mSystemUpdatePolicy != null) {
+            if (needBlank) {
+                needBlank = false;
+                pw.println();
+            }
             pw.println(prefix + "System Update Policy: " + mSystemUpdatePolicy);
-            pw.println();
+            needBlank = true;
         }
         if (mProfileOwners != null) {
             for (Map.Entry<Integer, OwnerInfo> entry : mProfileOwners.entrySet()) {
+                if (needBlank) {
+                    needBlank = false;
+                    pw.println();
+                }
                 pw.println(prefix + "Profile Owner (User " + entry.getKey() + "): ");
                 entry.getValue().dump(prefix + "  ", pw);
+                needBlank = true;
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 2c01b8a..56d6fc0 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -20,8 +20,8 @@
 import android.app.IActivityManager;
 import android.app.NotificationManager;
 import android.app.backup.IBackupManager;
-import android.content.Context;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.media.IAudioService;
 import android.os.Looper;
 import android.os.PowerManagerInternal;
@@ -113,6 +113,11 @@
         }
 
         @Override
+        PackageManagerInternal getPackageManagerInternal() {
+            return context.packageManagerInternal;
+        }
+
+        @Override
         PowerManagerInternal getPowerManagerInternal() {
             return context.powerManagerInternal;
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 36980e3..0bd4896 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,12 +25,8 @@
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.os.Bundle;
-import android.content.pm.PackageInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Pair;
@@ -50,6 +46,7 @@
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -768,21 +765,42 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.addUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+                );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpms.getDeviceOwnerAdminLocked().ensureUserRestrictions()
         );
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
-                        UserManager.DISALLOW_SMS, UserManager.DISALLOW_OUTGOING_CALLS),
+                        UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_OUTGOING_CALLS),
                 dpm.getUserRestrictions(admin1)
         );
 
-        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_SMS);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
@@ -794,6 +812,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions()
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -804,7 +828,68 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE are PO restrictions, but when
+        // DO sets them, the scope is global.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        dpm.clearUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+
+
+        // More tests.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_FUN);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                MockUtils.checkUserRestrictions(),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_CAMERA, UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
+
+        // Set up another DA and let it disable camera.  Now DISALLOW_CAMERA will only be applied
+        // locally.
+        dpm.setCameraDisabled(admin1, false);
+        reset(mContext.userManagerInternal);
+
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        dpm.setActiveAdmin(admin2, /* replace =*/ false, UserHandle.USER_SYSTEM);
+        dpm.setCameraDisabled(admin2, true);
+
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(UserHandle.USER_SYSTEM),
+                // DISALLOW_CAMERA will be applied to both local and global.
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_FUN,
+                        UserManager.DISALLOW_ADD_USER)
+        );
+        reset(mContext.userManagerInternal);
         // TODO Make sure restrictions are written to the file.
     }
 
@@ -818,7 +903,21 @@
         );
 
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         dpm.addUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+                        UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -837,7 +936,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
-
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_OUTGOING_CALLS),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(
@@ -854,6 +958,12 @@
         );
 
         dpm.clearUserRestriction(admin1, UserManager.DISALLOW_OUTGOING_CALLS);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
 
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
@@ -865,69 +975,29 @@
                 dpm.getUserRestrictions(admin1)
         );
 
-        // TODO Check inner calls.
+        // DISALLOW_ADJUST_VOLUME and DISALLOW_UNMUTE_MICROPHONE can be set by PO too, even
+        // though when DO sets them they'll be applied globally.
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADJUST_VOLUME);
+        reset(mContext.userManagerInternal);
+        dpm.addUserRestriction(admin1, UserManager.DISALLOW_UNMUTE_MICROPHONE);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
+        dpm.setCameraDisabled(admin1, true);
+        verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+                eq(DpmMockContext.CALLER_USER_HANDLE),
+                MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA,
+                        UserManager.DISALLOW_ADJUST_VOLUME,
+                        UserManager.DISALLOW_UNMUTE_MICROPHONE),
+                isNull(Bundle.class)
+        );
+        reset(mContext.userManagerInternal);
+
         // TODO Make sure restrictions are written to the file.
     }
-
-    public void testGetComposedUserRestrictions_noDoNoPo() throws Exception {
-        final Bundle in = DpmTestUtils.newRestrictions(UserManager.DISALLOW_OUTGOING_CALLS);
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        assertTrue(in == actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        assertTrue(in == actual);
-    }
-
-    public void testGetComposedUserRestrictions() throws Exception {
-        mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
-        mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
-        mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
-
-        // First, set DO.
-
-        // Call from a process on the system user.
-        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
-
-        // Make sure admin1 is installed on system user.
-        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
-
-        // Call.
-        dpm.setActiveAdmin(admin1, /* replace =*/ false, UserHandle.USER_SYSTEM);
-        assertTrue(dpm.setDeviceOwner(admin1, "owner-name",
-                UserHandle.USER_SYSTEM));
-
-        dpm.addUserRestriction(admin1, "rest1");
-        dpm.addUserRestriction(admin1, "rest2");
-
-        // Set PO on CALLER_USER_HANDLE.
-        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
-        setAsProfileOwner(admin2);
-
-        dpm.addUserRestriction(admin2, "restA");
-        dpm.addUserRestriction(admin2, "restB");
-
-        final Bundle in = DpmTestUtils.newRestrictions("abc");
-
-        Bundle actual = dpms.mLocalService.getComposedUserRestrictions(
-                UserHandle.USER_SYSTEM, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2", "restA", "restB"),
-                actual);
-
-        actual = dpms.mLocalService.getComposedUserRestrictions(
-                DpmMockContext.CALLER_USER_HANDLE + 1, in);
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions("abc", "rest1", "rest2"),
-                actual);
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index cc337b0..bb1e06d 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.media.IAudioService;
 import android.os.Bundle;
@@ -205,6 +206,7 @@
     public final SystemPropertiesForMock systemProperties;
     public final UserManager userManager;
     public final UserManagerInternal userManagerInternal;
+    public final PackageManagerInternal packageManagerInternal;
     public final UserManagerForMock userManagerForMock;
     public final PowerManagerForMock powerManager;
     public final PowerManagerInternal powerManagerInternal;
@@ -237,6 +239,7 @@
         userManager = mock(UserManager.class);
         userManagerInternal = mock(UserManagerInternal.class);
         userManagerForMock = mock(UserManagerForMock.class);
+        packageManagerInternal = mock(PackageManagerInternal.class);
         powerManager = mock(PowerManagerForMock.class);
         powerManagerInternal = mock(PowerManagerInternal.class);
         notificationManager = mock(NotificationManager.class);
@@ -260,9 +263,6 @@
 
         // System user is always running.
         setUserRunning(UserHandle.USER_SYSTEM, true);
-
-        // This method must return an object.
-        when(userManagerInternal.getUserRestrictionsLock()).thenReturn(new Object());
     }
 
     public File addUser(int userId, int flags) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 5008fbf..58db192 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -17,8 +17,12 @@
 
 import com.google.common.base.Objects;
 
+import com.android.internal.util.Preconditions;
+import com.android.server.pm.UserRestrictionsUtils;
+
 import android.content.ComponentName;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.UserHandle;
 
 import org.hamcrest.BaseMatcher;
@@ -77,4 +81,37 @@
         };
         return Mockito.argThat(m);
     }
+
+    public static Bundle checkUserRestrictions(String... keys) {
+        final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
+        final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) return false;
+                return UserRestrictionsUtils.areEqual((Bundle) item, expected);
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("User restrictions=" + getRestrictionsAsString(expected));
+            }
+        };
+        return Mockito.argThat(m);
+    }
+
+    private static String getRestrictionsAsString(Bundle b) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("[");
+
+        if (b != null) {
+            String sep = "";
+            for (String key : b.keySet()) {
+                sb.append(sep);
+                sep = ",";
+                sb.append(key);
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 4e11762..423c4d5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -19,21 +19,7 @@
 import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
 
 import android.content.ComponentName;
-import android.content.pm.UserInfo;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-
-import static org.mockito.Mockito.when;
 
 /**
  * Tests for the DeviceOwner object that saves & loads device and policy owner information.
diff --git a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
index b6742a1..d798518 100644
--- a/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
+++ b/services/tests/servicestests/src/com/android/server/updates/CertPinInstallReceiverTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.updates;
 
+import com.android.internal.util.HexDump;
+
 import android.content.Context;
 import android.content.Intent;
 import android.test.AndroidTestCase;
@@ -128,7 +130,7 @@
         MessageDigest dgst = MessageDigest.getInstance("SHA512");
         byte[] encoded = content.getBytes();
         byte[] fingerprint = dgst.digest(encoded);
-        return IntegralToString.bytesToHexString(fingerprint, false);
+        return HexDump.toHexString(fingerprint, false);
     }
 
     private static String getHashOfCurrentContent() throws Exception {
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index 572cc6f..a183de5 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -59,6 +59,9 @@
     public static final int PHONE_TYPE_SIP = RILConstants.SIP_PHONE;
     public static final int PHONE_TYPE_THIRD_PARTY = RILConstants.THIRD_PARTY_PHONE;
     public static final int PHONE_TYPE_IMS = RILConstants.IMS_PHONE;
+    // Currently this is used only to differentiate CDMA and CDMALTE Phone in GsmCdma* files. For
+    // anything outside of that, a cdma + lte phone is still CDMA_PHONE
+    public static final int PHONE_TYPE_CDMA_LTE = RILConstants.CDMA_LTE_PHONE;
 
     // Modes for LTE_ON_CDMA
     public static final int LTE_ON_CDMA_UNKNOWN = RILConstants.LTE_ON_CDMA_UNKNOWN;
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 7088be8..3c4c04b 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -118,6 +118,7 @@
     int SIP_PHONE  = 3;
     int THIRD_PARTY_PHONE = 4;
     int IMS_PHONE = 5;
+    int CDMA_LTE_PHONE = 6;
 
     int LTE_ON_CDMA_UNKNOWN = -1;
     int LTE_ON_CDMA_FALSE = 0;
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index b12795c..68bde35 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -20,7 +20,7 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_JAVA_LIBRARIES := core-libart core-junit framework
+LOCAL_JAVA_LIBRARIES := core-oj core-libart core-junit framework
 LOCAL_STATIC_JAVA_LIBRARIES := junit-runner
 
 LOCAL_MODULE:= android.test.runner
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 67b9d77..8eb30d2 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -160,6 +160,36 @@
             }
         },
 
+        new Test("with topic Hello") {
+            public void run() {
+                Notification n = new Notification.Builder(NotificationTestList.this)
+                        .setSmallIcon(R.drawable.icon1)
+                        .setWhen(mActivityCreateTime)
+                        .setContentTitle("hihi")
+                        .setContentText("This is a notification!!!")
+                        .setContentIntent(makeIntent2())
+                        .setTopic(new Notification.Topic("hello", "Hello"))
+                        .build();
+
+                mNM.notify(999, n);
+            }
+        },
+
+        new Test("with topic GoodBye") {
+            public void run() {
+                Notification n = new Notification.Builder(NotificationTestList.this)
+                        .setSmallIcon(R.drawable.icon1)
+                        .setWhen(mActivityCreateTime)
+                        .setContentTitle("byebye")
+                        .setContentText("This is a notification!!!")
+                        .setContentIntent(makeIntent2())
+                        .setTopic(new Notification.Topic("bye", "Goodbye"))
+                        .build();
+
+                mNM.notify(9999, n);
+            }
+        },
+
         new Test("Whens") {
             public void run()
             {
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index ec29c38..d8e0aac 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -58,8 +58,9 @@
 	ResourceValues.cpp \
 	SdkConstants.cpp \
 	StringPool.cpp \
-	XmlDom.cpp \
-	XmlPullParser.cpp
+	xml/XmlDom.cpp \
+	xml/XmlPullParser.cpp \
+	xml/XmlUtil.cpp
 
 testSources := \
 	compile/IdAssigner_test.cpp \
@@ -90,8 +91,9 @@
 	ResourceUtils_test.cpp \
 	StringPool_test.cpp \
 	ValueVisitor_test.cpp \
-	XmlDom_test.cpp \
-	XmlPullParser_test.cpp
+	xml/XmlDom_test.cpp \
+	xml/XmlPullParser_test.cpp \
+	xml/XmlUtil_test.cpp
 
 toolSources := \
 	compile/Compile.cpp \
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index d292f62..02fe59c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -19,9 +19,8 @@
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-#include "XmlPullParser.h"
-
 #include "util/Util.h"
+#include "xml/XmlPullParser.h"
 
 #include <sstream>
 
@@ -29,26 +28,6 @@
 
 constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
 
-static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
-    auto iter = parser->findAttribute(u"", name);
-    if (iter != parser->endAttributes()) {
-        return StringPiece16(util::trimWhitespace(iter->value));
-    }
-    return {};
-}
-
-static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
-                                                  const StringPiece16& name) {
-    auto iter = parser->findAttribute(u"", name);
-    if (iter != parser->endAttributes()) {
-        StringPiece16 trimmed = util::trimWhitespace(iter->value);
-        if (!trimmed.empty()) {
-            return trimmed;
-        }
-    }
-    return {};
-}
-
 /**
  * Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
  */
@@ -65,7 +44,7 @@
 /**
  * Build a string from XML that converts nested elements into Span objects.
  */
-bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
                                        StyleString* outStyleString) {
     std::vector<Span> spanStack;
 
@@ -74,9 +53,9 @@
     outStyleString->spans.clear();
     util::StringBuilder builder;
     size_t depth = 1;
-    while (XmlPullParser::isGoodEvent(parser->next())) {
-        const XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kEndElement) {
+    while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+        const xml::XmlPullParser::Event event = parser->getEvent();
+        if (event == xml::XmlPullParser::Event::kEndElement) {
             if (!parser->getElementNamespace().empty()) {
                 // We already warned and skipped the start element, so just skip here too
                 continue;
@@ -91,11 +70,11 @@
             outStyleString->spans.push_back(spanStack.back());
             spanStack.pop_back();
 
-        } else if (event == XmlPullParser::Event::kText) {
+        } else if (event == xml::XmlPullParser::Event::kText) {
             outRawString->append(parser->getText());
             builder.append(parser->getText());
 
-        } else if (event == XmlPullParser::Event::kStartElement) {
+        } else if (event == xml::XmlPullParser::Event::kStartElement) {
             if (!parser->getElementNamespace().empty()) {
                 if (parser->getElementNamespace() != sXliffNamespaceUri) {
                     // Only warn if this isn't an xliff namespace.
@@ -128,7 +107,7 @@
                 spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
             }
 
-        } else if (event == XmlPullParser::Event::kComment) {
+        } else if (event == xml::XmlPullParser::Event::kComment) {
             // Skip
         } else {
             assert(false);
@@ -140,11 +119,11 @@
     return !error;
 }
 
-bool ResourceParser::parse(XmlPullParser* parser) {
+bool ResourceParser::parse(xml::XmlPullParser* parser) {
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip comments and text.
             continue;
         }
@@ -159,7 +138,7 @@
         break;
     };
 
-    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+    if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
         mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                      << "xml parser error: " << parser->getLastError());
         return false;
@@ -167,10 +146,11 @@
     return !error;
 }
 
-static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
-    assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+static bool shouldStripResource(const xml::XmlPullParser* parser,
+                                const Maybe<std::u16string> productToMatch) {
+    assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement);
 
-    if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+    if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
         if (!productToMatch) {
             if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
                 // We didn't specify a product and this is not a default product, so skip.
@@ -229,20 +209,20 @@
     return !error;
 }
 
-bool ResourceParser::parseResources(XmlPullParser* parser) {
+bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
     std::set<ResourceName> strippedResources;
 
     bool error = false;
     std::u16string comment;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        const XmlPullParser::Event event = parser->getEvent();
-        if (event == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        const xml::XmlPullParser::Event event = parser->getEvent();
+        if (event == xml::XmlPullParser::Event::kComment) {
             comment = parser->getComment();
             continue;
         }
 
-        if (event == XmlPullParser::Event::kText) {
+        if (event == xml::XmlPullParser::Event::kText) {
             if (!util::trimWhitespace(parser->getText()).empty()) {
                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
                              << "plain text not allowed here");
@@ -251,7 +231,7 @@
             continue;
         }
 
-        assert(event == XmlPullParser::Event::kStartElement);
+        assert(event == xml::XmlPullParser::Event::kStartElement);
 
         if (!parser->getElementNamespace().empty()) {
             // Skip unknown namespace.
@@ -266,7 +246,7 @@
 
         if (elementName == u"item") {
             // Items simply have their type encoded in the type attribute.
-            if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+            if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
                 elementName = maybeType.value().toString();
             } else {
                 mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
@@ -280,7 +260,7 @@
         parsedResource.source = mSource.withLine(parser->getLineNumber());
         parsedResource.comment = std::move(comment);
 
-        if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) {
+        if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) {
             parsedResource.name.entry = maybeName.value().toString();
 
         } else if (elementName != u"public-group") {
@@ -403,7 +383,7 @@
  * an Item. If allowRawValue is false, nullptr is returned in this
  * case.
  */
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
                                                const bool allowRawValue) {
     const size_t beginXmlLine = parser->getLineNumber();
 
@@ -432,10 +412,7 @@
     if (processedItem) {
         // Fix up the reference.
         if (Reference* ref = valueCast<Reference>(processedItem.get())) {
-            if (Maybe<ResourceName> transformedName =
-                    parser->transformPackage(ref->name.value(), u"")) {
-                ref->name = std::move(transformedName);
-            }
+            transformReferenceFromNamespace(parser, u"", ref);
         }
         return processedItem;
     }
@@ -456,11 +433,11 @@
     return {};
 }
 
-bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     bool formatted = true;
-    if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+    if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
         if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
             mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
             return false;
@@ -468,7 +445,7 @@
     }
 
     bool translateable = mOptions.translatable;
-    if (Maybe<StringPiece16> translateableAttr = findAttribute(parser, u"translatable")) {
+    if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
         if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
             mDiag->error(DiagMessage(source)
                          << "invalid value for 'translatable'. Must be a boolean");
@@ -495,7 +472,7 @@
     return true;
 }
 
-bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
@@ -506,7 +483,7 @@
     return true;
 }
 
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
     uint32_t typeMask = 0;
@@ -540,10 +517,10 @@
     return true;
 }
 
-bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
         return false;
@@ -558,7 +535,7 @@
 
     outResource->name.type = *parsedType;
 
-    if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
+    if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
         android::Res_value val;
         bool result = android::ResTable::stringToInt(maybeId.value().data(),
                                                      maybeId.value().size(), &val);
@@ -580,10 +557,10 @@
     return true;
 }
 
-bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute");
         return false;
@@ -596,7 +573,7 @@
         return false;
     }
 
-    Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id");
+    Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
     if (!maybeId) {
         mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute");
         return false;
@@ -615,11 +592,11 @@
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text.
             continue;
         }
@@ -628,20 +605,20 @@
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"public") {
-            Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+            Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
             if (!maybeName) {
                 mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
                 error = true;
                 continue;
             }
 
-            if (findNonEmptyAttribute(parser, u"id")) {
+            if (xml::findNonEmptyAttribute(parser, u"id")) {
                 mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
                 error = true;
                 continue;
             }
 
-            if (findNonEmptyAttribute(parser, u"type")) {
+            if (xml::findNonEmptyAttribute(parser, u"type")) {
                 mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
                 error = true;
                 continue;
@@ -666,10 +643,10 @@
     return !error;
 }
 
-bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+    Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
     if (!maybeType) {
         mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
                      "'type' attribute");
@@ -715,17 +692,16 @@
     return mask;
 }
 
-
-
-bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
     outResource->source = mSource.withLine(parser->getLineNumber());
     return parseAttrImpl(parser, outResource, false);
 }
 
-bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+                                   bool weak) {
     uint32_t typeMask = 0;
 
-    Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+    Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
     if (maybeFormat) {
         typeMask = parseFormatAttribute(maybeFormat.value());
         if (typeMask == 0) {
@@ -735,18 +711,6 @@
         }
     }
 
-    // If this is a declaration, the package name may be in the name. Separate these out.
-    // Eg. <attr name="android:text" />
-    // No format attribute is allowed.
-    if (weak && !maybeFormat) {
-        StringPiece16 package, type, name;
-        ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
-        if (type.empty() && !package.empty()) {
-            outResource->name.package = package.toString();
-            outResource->name.entry = name.toString();
-        }
-    }
-
     struct SymbolComparator {
         bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
             return a.symbol.name.value() < b.symbol.name.value();
@@ -758,11 +722,11 @@
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text.
             continue;
         }
@@ -834,17 +798,17 @@
     return true;
 }
 
-Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
                                                              const StringPiece16& tag) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
     if (!maybeName) {
         mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
         return {};
     }
 
-    Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
+    Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
     if (!maybeValue) {
         mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
         return {};
@@ -863,12 +827,19 @@
             val.data };
 }
 
-static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
+static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
     str = util::trimWhitespace(str);
-    const char16_t* const start = str.data();
+    const char16_t* start = str.data();
     const char16_t* const end = start + str.size();
     const char16_t* p = start;
 
+    Reference ref;
+    if (p != end && *p == u'*') {
+        ref.privateReference = true;
+        start++;
+        p++;
+    }
+
     StringPiece16 package;
     StringPiece16 name;
     while (p != end) {
@@ -880,28 +851,27 @@
         p++;
     }
 
-    return ResourceName(package.toString(), ResourceType::kAttr,
+    ref.name = ResourceName(package.toString(), ResourceType::kAttr,
                         name.empty() ? str.toString() : name.toString());
+    return Maybe<Reference>(std::move(ref));
 }
 
-bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
     const Source source = mSource.withLine(parser->getLineNumber());
 
-    Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+    Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
     if (!maybeName) {
         mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
         return false;
     }
 
-    Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+    Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
     if (!maybeKey) {
         mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
         return false;
     }
 
-    if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
-        maybeKey = std::move(transformedName);
-    }
+    transformReferenceFromNamespace(parser, u"", &maybeKey.value());
 
     std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
     if (!value) {
@@ -909,15 +879,15 @@
         return false;
     }
 
-    style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
+    style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
     return true;
 }
 
-bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Style> style = util::make_unique<Style>();
 
-    Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+    Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
     if (maybeParent) {
         // If the parent is empty, we don't have a parent, but we also don't infer either.
         if (!maybeParent.value().empty()) {
@@ -928,10 +898,9 @@
                 return false;
             }
 
-            if (Maybe<ResourceName> transformedName =
-                    parser->transformPackage(style->parent.value().name.value(), u"")) {
-                style->parent.value().name = std::move(transformedName);
-            }
+            // Transform the namespace prefix to the actual package name, and mark the reference as
+            // private if appropriate.
+            transformReferenceFromNamespace(parser, u"", &style->parent.value());
         }
 
     } else {
@@ -940,15 +909,15 @@
         size_t pos = styleName.find_last_of(u'.');
         if (pos != std::string::npos) {
             style->parentInferred = true;
-            style->parent = Reference(
-                    ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
+            style->parent = Reference(ResourceName({}, ResourceType::kStyle,
+                                                   styleName.substr(0, pos)));
         }
     }
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -973,15 +942,15 @@
     return true;
 }
 
-bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource,
                                 uint32_t typeMask) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Array> array = util::make_unique<Array>();
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -1014,14 +983,14 @@
     return true;
 }
 
-bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
 
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Skip text and comments.
             continue;
         }
@@ -1030,16 +999,15 @@
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"item") {
-            const auto endAttrIter = parser->endAttributes();
-            auto attrIter = parser->findAttribute(u"", u"quantity");
-            if (attrIter == endAttrIter || attrIter->value.empty()) {
+            Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
+            if (!maybeQuantity) {
                 mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
                              << "'quantity'");
                 error = true;
                 continue;
             }
 
-            StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+            StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
             size_t index = 0;
             if (trimmedQuantity == u"zero") {
                 index = Plural::Zero;
@@ -1089,7 +1057,7 @@
     return true;
 }
 
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) {
     const Source source = mSource.withLine(parser->getLineNumber());
     std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
 
@@ -1099,11 +1067,11 @@
     std::u16string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
-    while (XmlPullParser::nextChildNode(parser, depth)) {
-        if (parser->getEvent() == XmlPullParser::Event::kComment) {
+    while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+        if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
             comment = util::trimWhitespace(parser->getComment()).toString();
             continue;
-        } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+        } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
             // Ignore text.
             continue;
         }
@@ -1112,17 +1080,29 @@
         const std::u16string& elementNamespace = parser->getElementNamespace();
         const std::u16string& elementName = parser->getElementName();
         if (elementNamespace.empty() && elementName == u"attr") {
-            const auto endAttrIter = parser->endAttributes();
-            auto attrIter = parser->findAttribute(u"", u"name");
-            if (attrIter == endAttrIter || attrIter->value.empty()) {
+            Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+            if (!maybeName) {
                 mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
                 error = true;
                 continue;
             }
 
+            // If this is a declaration, the package name may be in the name. Separate these out.
+            // Eg. <attr name="android:text" />
+            Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+            if (!maybeRef) {
+                mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+                             << maybeName.value() << "'");
+                error = true;
+                continue;
+            }
+
+            Reference& childRef = maybeRef.value();
+            xml::transformReferenceFromNamespace(parser, u"", &childRef);
+
             // Create the ParsedResource that will add the attribute to the table.
             ParsedResource childResource;
-            childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+            childResource.name = childRef.name.value();
             childResource.source = itemSource;
             childResource.comment = std::move(comment);
 
@@ -1132,7 +1112,6 @@
             }
 
             // Create the reference to this attribute.
-            Reference childRef(childResource.name);
             childRef.setComment(childResource.comment);
             childRef.setSource(itemSource);
             styleable->entries.push_back(std::move(childRef));
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 18101ee..1150758 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -22,10 +22,9 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "StringPool.h"
-#include "XmlPullParser.h"
-
 #include "util/Maybe.h"
 #include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
 
 #include <memory>
 
@@ -57,7 +56,7 @@
 
     ResourceParser(const ResourceParser&) = delete; // No copy.
 
-    bool parse(XmlPullParser* parser);
+    bool parse(xml::XmlPullParser* parser);
 
 private:
     /*
@@ -66,7 +65,7 @@
      * contains the escaped and whitespace trimmed text, while `outRawString`
      * contains the unescaped text. Returns true on success.
      */
-    bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+    bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
                            StyleString* outStyleString);
 
     /*
@@ -75,24 +74,25 @@
      * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
      * RawString is returned. Otherwise this returns false;
      */
-    std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+    std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
                                    const bool allowRawValue);
 
-    bool parseResources(XmlPullParser* parser);
-    bool parseString(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
-    bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
-    bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
-    bool parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
-    Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
-    bool parseStyle(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseStyleItem(XmlPullParser* parser, Style* style);
-    bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource);
-    bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
-    bool parsePlural(XmlPullParser* parser, ParsedResource* outResource);
+    bool parseResources(xml::XmlPullParser* parser);
+    bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
+    Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+                                                 const StringPiece16& tag);
+    bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+    bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
+    bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+    bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
 
     IDiagnostics* mDiag;
     ResourceTable* mTable;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59eb95..ab16424 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -18,9 +18,8 @@
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "XmlPullParser.h"
-
 #include "test/Context.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -36,7 +35,7 @@
     input << "<attr name=\"foo\"/>" << std::endl;
     ResourceTable table;
     ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
-    XmlPullParser xmlParser(input);
+    xml::XmlPullParser xmlParser(input);
     ASSERT_FALSE(parser.parse(&xmlParser));
 }
 
@@ -56,7 +55,7 @@
         parserOptions.product = product;
         ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
                               parserOptions);
-        XmlPullParser xmlParser(input);
+        xml::XmlPullParser xmlParser(input);
         if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
@@ -360,6 +359,25 @@
     EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
 }
 
+TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
+    std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+                        "  <attr name=\"*android:bar\" />\n"
+                        "  <attr name=\"privAndroid:bat\" />\n"
+                        "</declare-styleable>";
+    ASSERT_TRUE(testParse(input));
+    Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+    ASSERT_NE(nullptr, styleable);
+    ASSERT_EQ(2u, styleable->entries.size());
+
+    EXPECT_TRUE(styleable->entries[0].privateReference);
+    AAPT_ASSERT_TRUE(styleable->entries[0].name);
+    EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package);
+
+    EXPECT_TRUE(styleable->entries[1].privateReference);
+    AAPT_ASSERT_TRUE(styleable->entries[1].name);
+    EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package);
+}
+
 TEST_F(ResourceParserTest, ParseArray) {
     std::string input = "<array name=\"foo\">\n"
                         "  <item>@string/ref</item>\n"
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index b1a4c7d..ffe6595 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "ResourceUtils.h"
+#include "flatten/ResourceTypeExtensions.h"
 #include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
@@ -47,6 +48,42 @@
     return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
 }
 
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+    size_t offset = 0;
+    bool priv = false;
+    if (str.data()[0] == u'*') {
+        priv = true;
+        offset = 1;
+    }
+
+    StringPiece16 package;
+    StringPiece16 type;
+    StringPiece16 entry;
+    if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
+        return false;
+    }
+
+    const ResourceType* parsedType = parseResourceType(type);
+    if (!parsedType) {
+        return false;
+    }
+
+    if (entry.empty()) {
+        return false;
+    }
+
+    if (outRef) {
+        outRef->package = package;
+        outRef->type = *parsedType;
+        outRef->entry = entry;
+    }
+
+    if (outPrivate) {
+        *outPrivate = priv;
+    }
+    return true;
+}
+
 bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
                        bool* outPrivate) {
     StringPiece16 trimmedStr(util::trimWhitespace(str));
@@ -61,35 +98,24 @@
         if (trimmedStr.data()[1] == u'+') {
             create = true;
             offset += 1;
-        } else if (trimmedStr.data()[1] == u'*') {
-            priv = true;
-            offset += 1;
         }
-        StringPiece16 package;
-        StringPiece16 type;
-        StringPiece16 entry;
-        if (!extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
-                                 &package, &type, &entry)) {
+
+        ResourceNameRef name;
+        if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+                               &name, &priv)) {
             return false;
         }
 
-        const ResourceType* parsedType = parseResourceType(type);
-        if (!parsedType) {
+        if (create && priv) {
             return false;
         }
 
-        if (entry.empty()) {
-            return false;
-        }
-
-        if (create && *parsedType != ResourceType::kId) {
+        if (create && name.type != ResourceType::kId) {
             return false;
         }
 
         if (outRef) {
-            outRef->package = package;
-            outRef->type = *parsedType;
-            outRef->entry = entry;
+            *outRef = name;
         }
 
         if (outCreate) {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 34daa66..f93a4c7 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -39,6 +39,13 @@
 bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
                          StringPiece16* outType, StringPiece16* outEntry);
 
+/**
+ * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * was present.
+ */
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+
 /*
  * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
  * `outReference` set to the parsed reference.
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 3d2a6e1..4bbfc32 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -16,15 +16,30 @@
 
 #include "Resource.h"
 #include "ResourceUtils.h"
-
 #include "test/Common.h"
 
 #include <gtest/gtest.h>
 
 namespace aapt {
 
+TEST(ResourceUtilsTest, ParseResourceName) {
+    ResourceNameRef actual;
+    bool actualPriv = false;
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+    EXPECT_FALSE(actualPriv);
+
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual);
+    EXPECT_FALSE(actualPriv);
+
+    EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
+    EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+    EXPECT_TRUE(actualPriv);
+}
+
 TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
-    ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+    ResourceNameRef expected({}, ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -35,7 +50,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -47,7 +62,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
-    ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -59,7 +74,7 @@
 }
 
 TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -71,7 +86,7 @@
 }
 
 TEST(ResourceUtilsTest, ParsePrivateReference) {
-    ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+    ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
     ResourceNameRef actual;
     bool create = false;
     bool privateRef = false;
@@ -111,8 +126,8 @@
 }
 
 TEST(ResourceUtilsTest, ParseStyleParentReference) {
-    const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
-    const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+    const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo");
+    const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo");
 
     std::string errStr;
     Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 8acff0d..5550f19 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -71,27 +71,23 @@
 }
 
 bool Reference::flatten(android::Res_value* outValue) const {
-    outValue->dataType = (referenceType == Reference::Type::kResource)
-        ? android::Res_value::TYPE_REFERENCE
-        : android::Res_value::TYPE_ATTRIBUTE;
+    outValue->dataType = (referenceType == Reference::Type::kResource) ?
+            android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
     outValue->data = util::hostToDevice32(id ? id.value().id : 0);
     return true;
 }
 
 Reference* Reference::clone(StringPool* /*newPool*/) const {
-    Reference* ref = new Reference();
-    ref->mComment = mComment;
-    ref->mSource = mSource;
-    ref->referenceType = referenceType;
-    ref->name = name;
-    ref->id = id;
-    return ref;
+    return new Reference(*this);
 }
 
 void Reference::print(std::ostream* out) const {
     *out << "(reference) ";
     if (referenceType == Reference::Type::kResource) {
         *out << "@";
+        if (privateReference) {
+            *out << "*";
+        }
     } else {
         *out << "?";
     }
@@ -116,10 +112,7 @@
 }
 
 Id* Id::clone(StringPool* /*newPool*/) const {
-    Id* id = new Id();
-    id->mComment = mComment;
-    id->mSource = mSource;
-    return id;
+    return new Id(*this);
 }
 
 void Id::print(std::ostream* out) const {
@@ -214,10 +207,7 @@
 }
 
 BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
-    BinaryPrimitive* bp = new BinaryPrimitive(value);
-    bp->mComment = mComment;
-    bp->mSource = mSource;
-    return bp;
+    return new BinaryPrimitive(*this);
 }
 
 void BinaryPrimitive::print(std::ostream* out) const {
@@ -255,12 +245,7 @@
 }
 
 Attribute* Attribute::clone(StringPool* /*newPool*/) const {
-    Attribute* attr = new Attribute(weak);
-    attr->mComment = mComment;
-    attr->mSource = mSource;
-    attr->typeMask = typeMask;
-    std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
-    return attr;
+    return new Attribute(*this);
 }
 
 void Attribute::printMask(std::ostream* out) const {
@@ -450,11 +435,7 @@
 }
 
 Styleable* Styleable::clone(StringPool* /*newPool*/) const {
-    Styleable* styleable = new Styleable();
-    styleable->mComment = mComment;
-    styleable->mSource = mSource;
-    std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
-    return styleable;
+    return new Styleable(*this);
 }
 
 void Styleable::print(std::ostream* out) const {
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 39088bc..17a658e 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -19,9 +19,6 @@
 #include "Flags.h"
 #include "ResourceParser.h"
 #include "ResourceTable.h"
-#include "XmlDom.h"
-#include "XmlPullParser.h"
-
 #include "compile/IdAssigner.h"
 #include "compile/Png.h"
 #include "compile/XmlIdCollector.h"
@@ -31,6 +28,8 @@
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlPullParser.h"
 
 #include <fstream>
 #include <string>
@@ -131,7 +130,7 @@
 
 
         // Parse the values file from XML.
-        XmlPullParser xmlParser(fin);
+        xml::XmlPullParser xmlParser(fin);
 
         ResourceParserOptions parserOptions;
         parserOptions.product = options.product;
@@ -191,7 +190,7 @@
 static bool compileXml(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& pathData, const std::string& outputPath) {
 
-    std::unique_ptr<XmlResource> xmlRes;
+    std::unique_ptr<xml::XmlResource> xmlRes;
 
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index dfdf710..f40689e 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -16,9 +16,8 @@
 
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "XmlDom.h"
-
 #include "compile/XmlIdCollector.h"
+#include "xml/XmlDom.h"
 
 #include <algorithm>
 #include <vector>
@@ -61,7 +60,7 @@
 
 } // namespace
 
-bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
     xmlRes->file.exportedSymbols.clear();
     IdCollector collector(&xmlRes->file.exportedSymbols);
     xmlRes->root->accept(&collector);
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 96a58f2..1b14944 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -18,11 +18,12 @@
 #define AAPT_XMLIDCOLLECTOR_H
 
 #include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
 struct XmlIdCollector : public IXmlResourceConsumer {
-    bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+    bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index c703f451..45b7af2 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -26,7 +26,7 @@
 TEST(XmlIdCollectorTest, CollectsIds) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                   android:id="@+id/foo"
                   text="@+id/bar">
@@ -50,7 +50,7 @@
 TEST(XmlIdCollectorTest, DontCollectNonIds) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
 
     XmlIdCollector collector;
     ASSERT_TRUE(collector.consume(context.get(), doc.get()));
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index c1ff556..acf5bb5 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -62,7 +62,7 @@
          * A raw string value that hasn't had its escape sequences
          * processed nor whitespace removed.
          */
-        TYPE_RAW_STRING = 0xfe
+        TYPE_RAW_STRING = 0xfe,
     };
 };
 
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 6b90fb2..636e977 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -78,10 +78,18 @@
     explicit SymbolWriter(StringPool* pool) : mPool(pool) {
     }
 
-    void addSymbol(const ResourceNameRef& name, size_t offset) {
-        symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" +
-                                               toString(name.type).toString() + u"/" +
-                                               name.entry.toString()), offset });
+    void addSymbol(const Reference& ref, size_t offset) {
+        const ResourceName& name = ref.name.value();
+        std::u16string fullName;
+        if (ref.privateReference) {
+            fullName += u"*";
+        }
+
+        if (!name.package.empty()) {
+            fullName += name.package + u":";
+        }
+        fullName += toString(name.type).toString() + u"/" + name.entry;
+        symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
     }
 
     void shiftAllOffsets(size_t offset) {
@@ -100,20 +108,23 @@
     SymbolWriter* mSymbols;
     FlatEntry* mEntry;
     BigBuffer* mBuffer;
+    bool mUseExtendedChunks;
     size_t mEntryCount = 0;
     Maybe<uint32_t> mParentIdent;
     Maybe<ResourceNameRef> mParentName;
 
-    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
-            mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+                      bool useExtendedChunks) :
+            mSymbols(symbols), mEntry(entry), mBuffer(buffer),
+            mUseExtendedChunks(useExtendedChunks) {
     }
 
     void flattenKey(Reference* key, ResTable_map* outEntry) {
-        if (!key->id) {
+        if (!key->id || (key->privateReference && mUseExtendedChunks)) {
             assert(key->name && "reference must have a name");
 
             outEntry->name.ident = util::hostToDevice32(0);
-            mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+            mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
                                     offsetof(ResTable_map, name));
         } else {
             outEntry->name.ident = util::hostToDevice32(key->id.value().id);
@@ -121,16 +132,21 @@
     }
 
     void flattenValue(Item* value, ResTable_map* outEntry) {
+        bool privateRef = false;
         if (Reference* ref = valueCast<Reference>(value)) {
-            if (!ref->id) {
+            privateRef = ref->privateReference && mUseExtendedChunks;
+            if (!ref->id || privateRef) {
                 assert(ref->name && "reference must have a name");
 
-                mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+                mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
                                         offsetof(ResTable_map, value) + offsetof(Res_value, data));
             }
         }
 
         bool result = value->flatten(&outEntry->value);
+        if (privateRef) {
+            outEntry->value.data = 0;
+        }
         assert(result && "flatten failed");
     }
 
@@ -169,7 +185,8 @@
 
     void visit(Style* style) override {
         if (style->parent) {
-            if (!style->parent.value().id) {
+            bool privateRef = style->parent.value().privateReference && mUseExtendedChunks;
+            if (!style->parent.value().id || privateRef) {
                 assert(style->parent.value().name && "reference must have a name");
                 mParentName = style->parent.value().name;
             } else {
@@ -339,21 +356,28 @@
     bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
         if (Item* item = valueCast<Item>(entry->value)) {
             writeEntry<ResTable_entry, true>(entry, buffer);
+            bool privateRef = false;
             if (Reference* ref = valueCast<Reference>(entry->value)) {
-                if (!ref->id) {
+                // If there is no ID or the reference is private and we allow extended chunks,
+                // write out a 0 and mark the symbol table with the name of the reference.
+                privateRef = (ref->privateReference && mOptions.useExtendedChunks);
+                if (!ref->id || privateRef) {
                     assert(ref->name && "reference must have at least a name");
-                    mSymbols->addSymbol(ref->name.value(),
-                                        buffer->size() + offsetof(Res_value, data));
+                    mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
                 }
             }
             Res_value* outValue = buffer->nextBlock<Res_value>();
             bool result = item->flatten(outValue);
             assert(result && "flatten failed");
+            if (privateRef) {
+                // Force the value of 0 so we look up the symbol at unflatten time.
+                outValue->data = 0;
+            }
             outValue->size = util::hostToDevice16(sizeof(*outValue));
         } else {
             const size_t beforeEntry = buffer->size();
             ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
-            MapFlattenVisitor visitor(mSymbols, entry, buffer);
+            MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
             entry->value->accept(&visitor);
             outEntry->count = util::hostToDevice32(visitor.mEntryCount);
             if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 68a1f47..4ffb980 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -15,11 +15,11 @@
  */
 
 #include "flatten/TableFlattener.h"
+#include "test/Builders.h"
+#include "test/Context.h"
 #include "unflatten/BinaryResourceParser.h"
 #include "util/Util.h"
 
-#include "test/Builders.h"
-#include "test/Context.h"
 
 #include <gtest/gtest.h>
 
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 4efb08b..8219462 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -15,15 +15,14 @@
  */
 
 #include "SdkConstants.h"
-#include "XmlDom.h"
-
 #include "flatten/ChunkWriter.h"
 #include "flatten/ResourceTypeExtensions.h"
 #include "flatten/XmlFlattener.h"
+#include "xml/XmlDom.h"
 
 #include <androidfw/ResourceTypes.h>
-#include <vector>
 #include <utils/misc.h>
+#include <vector>
 
 using namespace android;
 
@@ -306,7 +305,7 @@
     return true;
 }
 
-bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
     if (!resource->root) {
         return false;
     }
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index b1fb3a7..a688ac9 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,16 +17,12 @@
 #ifndef AAPT_FLATTEN_XMLFLATTENER_H
 #define AAPT_FLATTEN_XMLFLATTENER_H
 
-#include "util/BigBuffer.h"
-
 #include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
-namespace xml {
-struct Node;
-}
-
 struct XmlFlattenerOptions {
     /**
      * Keep attribute raw string values along with typed values.
@@ -45,7 +41,7 @@
             mBuffer(buffer), mOptions(options) {
     }
 
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 
 private:
     BigBuffer* mBuffer;
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 318bcdd..8648879 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -16,11 +16,10 @@
 
 #include "flatten/XmlFlattener.h"
 #include "link/Linkers.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <gtest/gtest.h>
@@ -45,7 +44,7 @@
                 .build();
     }
 
-    ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+    ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
                                        XmlFlattenerOptions options = {}) {
         BigBuffer buffer(1024);
         XmlFlattener flattener(&buffer, options);
@@ -65,7 +64,7 @@
 };
 
 TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:test="http://com.test"
                   attr="hey">
               <Layout test:hello="hi" />
@@ -144,7 +143,7 @@
 }
 
 TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                 android:paddingStart="1dp"
                 android:colorAccent="#ffffff"/>)EOF");
@@ -169,7 +168,7 @@
 }
 
 TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/android"
                   android:id="@id/id"
                   class="str"
@@ -192,7 +191,7 @@
  * namespace.
  */
 TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
 
     android::ResXMLTree tree;
     ASSERT_TRUE(flatten(doc.get(), &tree));
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index d5a2b38..da96b84 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -17,12 +17,10 @@
 #include "ResourceParser.h"
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "XmlPullParser.h"
-
 #include "java/AnnotationProcessor.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 
@@ -42,7 +40,7 @@
                               options);
         std::stringstream in;
         in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
-        XmlPullParser xmlParser(in);
+        xml::XmlPullParser xmlParser(in);
         if (parser.parse(&xmlParser)) {
             return ::testing::AssertionSuccess();
         }
diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h
index b8886f9..04e1274 100644
--- a/tools/aapt2/java/ClassDefinitionWriter.h
+++ b/tools/aapt2/java/ClassDefinitionWriter.h
@@ -17,6 +17,7 @@
 #ifndef AAPT_JAVA_CLASSDEFINITION_H
 #define AAPT_JAVA_CLASSDEFINITION_H
 
+#include "Resource.h"
 #include "java/AnnotationProcessor.h"
 #include "util/StringPiece.h"
 #include "util/Util.h"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index d963d89..a9b4c14 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -15,12 +15,11 @@
  */
 
 #include "Source.h"
-#include "XmlDom.h"
-
 #include "java/AnnotationProcessor.h"
 #include "java/ClassDefinitionWriter.h"
 #include "java/ManifestClassGenerator.h"
 #include "util/Maybe.h"
+#include "xml/XmlDom.h"
 
 #include <algorithm>
 
@@ -80,7 +79,7 @@
 }
 
 bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
-                                      XmlResource* res, std::ostream* out) {
+                                      xml::XmlResource* res, std::ostream* out) {
     xml::Element* el = xml::findRootElement(res->root.get());
     if (!el) {
         return false;
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index 0f0998f..226ed23 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -18,15 +18,15 @@
 #define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
 
 #include "Diagnostics.h"
-#include "process/IResourceTableConsumer.h"
 #include "util/StringPiece.h"
+#include "xml/XmlDom.h"
 
 #include <iostream>
 
 namespace aapt {
 
 struct ManifestClassGenerator {
-    bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+    bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res,
                   std::ostream* out);
 };
 
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 4081287..fc57ae6f 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "java/ManifestClassGenerator.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -25,7 +24,7 @@
 
 TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
-    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android">
           <permission android:name="android.permission.ACCESS_INTERNET" />
           <permission android:name="android.DO_DANGEROUS_THINGS" />
@@ -75,7 +74,7 @@
 
 TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
-    std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
         <manifest xmlns:android="http://schemas.android.com/apk/res/android">
           <!-- Required to access the internet.
                Added in API 1. -->
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 4431477..c096854 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-#include "XmlDom.h"
-
 #include "java/ProguardRules.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 #include <memory>
 #include <string>
@@ -40,11 +39,11 @@
 
     virtual void visit(xml::Element* node) override {
         if (!node->namespaceUri.empty()) {
-            Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+            Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
                     node->namespaceUri);
             if (maybePackage) {
                 // This is a custom view, let's figure out the class name from this.
-                std::u16string package = maybePackage.value() + u"." + node->name;
+                std::u16string package = maybePackage.value().package + u"." + node->name;
                 if (util::isJavaClassName(package)) {
                     addClass(node->lineNumber, package);
                 }
@@ -185,7 +184,8 @@
     std::u16string mPackage;
 };
 
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
+                                     KeepSet* keepSet) {
     ManifestVisitor visitor(source, keepSet);
     if (res->root) {
         res->root->accept(&visitor);
@@ -194,7 +194,7 @@
     return false;
 }
 
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
     if (!res->root) {
         return false;
     }
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index be61eb9..aafffd3 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -19,8 +19,7 @@
 
 #include "Resource.h"
 #include "Source.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 #include <map>
 #include <ostream>
@@ -47,8 +46,8 @@
     std::map<std::u16string, std::set<Source>> mKeepMethodSet;
 };
 
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
 
 bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
 
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 97be774..9850ae5 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -18,8 +18,6 @@
 #include "Debug.h"
 #include "Flags.h"
 #include "NameMangler.h"
-#include "XmlDom.h"
-
 #include "compile/IdAssigner.h"
 #include "flatten/Archive.h"
 #include "flatten/TableFlattener.h"
@@ -28,6 +26,7 @@
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
 #include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
 #include "link/ManifestFixer.h"
 #include "link/TableMerger.h"
 #include "process/IResourceTableConsumer.h"
@@ -36,6 +35,7 @@
 #include "unflatten/FileExportHeaderReader.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
+#include "xml/XmlDom.h"
 
 #include <fstream>
 #include <sys/stat.h>
@@ -159,7 +159,7 @@
     /**
      * Inflates an XML file from the source path.
      */
-    std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+    std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
         std::ifstream fin(path, std::ifstream::binary);
         if (!fin) {
             mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
@@ -172,7 +172,7 @@
     /**
      * Inflates a binary XML file from the source path.
      */
-    std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
+    std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
         // Read header for symbol info and export info.
         std::string errorStr;
         Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
@@ -188,7 +188,7 @@
             return {};
         }
 
-        std::unique_ptr<XmlResource> xmlRes = xml::inflate(
+        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
                 (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
                 maybeF.value().getDataLength() - offset,
                 mContext.getDiagnostics(), Source(path));
@@ -245,7 +245,7 @@
         return true;
     }
 
-    Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+    Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
         // Make sure the first element is <manifest> with package attribute.
         if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
             if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
@@ -309,7 +309,7 @@
         return true;
     }
 
-    bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+    bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
                     IArchiveWriter* writer) {
         BigBuffer buffer(1024);
         XmlFlattenerOptions options = {};
@@ -354,7 +354,7 @@
         return true;
     }
 
-    bool writeManifestJavaFile(XmlResource* manifestXml) {
+    bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
         if (!mOptions.generateJavaClassPath) {
             return true;
         }
@@ -502,7 +502,7 @@
 
     int run(const std::vector<std::string>& inputFiles) {
         // Load the AndroidManifest.xml
-        std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
         if (!manifestXml) {
             return 1;
         }
@@ -545,25 +545,21 @@
                                   << "with package ID " << std::hex << (int) mContext.mPackageId);
         }
 
-        bool error = false;
 
         for (const std::string& input : inputFiles) {
             if (!processFile(input, false)) {
-                error = true;
+                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+                return 1;
             }
         }
 
         for (const std::string& input : mOptions.overlayFiles) {
             if (!processFile(input, true)) {
-                error = true;
+                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+                return 1;
             }
         }
 
-        if (error) {
-            mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
-            return 1;
-        }
-
         if (!verifyNoExternalPackages()) {
             return 1;
         }
@@ -608,6 +604,7 @@
             return 1;
         }
 
+        bool error = false;
         {
             ManifestFixerOptions manifestFixerOptions;
             manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
@@ -617,6 +614,11 @@
                 error = true;
             }
 
+            // AndroidManifest.xml has no resource name, but the CallSite is built from the name
+            // (aka, which package the AndroidManifest.xml is coming from).
+            // So we give it a package name so it can see local resources.
+            manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+
             XmlReferenceLinker manifestLinker;
             if (manifestLinker.consume(&mContext, manifestXml.get())) {
                 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
@@ -640,14 +642,21 @@
             }
         }
 
+        if (error) {
+            mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+            return 1;
+        }
+
         for (const FileToProcess& file : mFilesToProcess) {
             if (file.file.name.type != ResourceType::kRaw &&
                     util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
                 if (mOptions.verbose) {
-                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << file.source.path);
+                    mContext.getDiagnostics()->note(DiagMessage()
+                                                    << "linking " << file.source.path);
                 }
 
-                std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(file.source.path);
+                std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
+                        file.source.path);
                 if (!xmlRes) {
                     return 1;
                 }
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 7b3fc35..4d3a483 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,7 +17,9 @@
 #ifndef AAPT_LINKER_LINKERS_H
 #define AAPT_LINKER_LINKERS_H
 
+#include "Resource.h"
 #include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 #include <set>
 
@@ -28,6 +30,14 @@
 struct ConfigDescription;
 
 /**
+ * Defines the location in which a value exists. This determines visibility of other
+ * package's private symbols.
+ */
+struct CallSite {
+    ResourceNameRef resource;
+};
+
+/**
  * Determines whether a versioned resource should be created. If a versioned resource already
  * exists, it takes precedence.
  */
@@ -39,7 +49,7 @@
 };
 
 struct XmlAutoVersioner : public IXmlResourceConsumer {
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 };
 
 /**
@@ -69,15 +79,6 @@
 };
 
 /**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
- * The ResourceTable must already have IDs assigned to each resource.
- * Once the ResourceTable is processed by this linker, it is ready to be flattened.
- */
-struct ReferenceLinker : public IResourceTableConsumer {
-    bool consume(IAaptContext* context, ResourceTable* table) override;
-};
-
-/**
  * Resolves attributes in the XmlResource and compiles string values to resource values.
  * Once an XmlResource is processed by this linker, it is ready to be flattened.
  */
@@ -86,7 +87,7 @@
     std::set<int> mSdkLevelsFound;
 
 public:
-    bool consume(IAaptContext* context, XmlResource* resource) override;
+    bool consume(IAaptContext* context, xml::XmlResource* resource) override;
 
     /**
      * Once the XmlResource has been consumed, this returns the various SDK levels in which
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 52d9426..2034c57 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -15,10 +15,9 @@
  */
 
 #include "ResourceUtils.h"
-#include "XmlDom.h"
-
 #include "link/ManifestFixer.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
@@ -63,7 +62,7 @@
     return true;
 }
 
-bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
     xml::Element* root = xml::findRootElement(doc->root.get());
     if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
         context->getDiagnostics()->error(DiagMessage(doc->file.source)
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 16e161d..a77e6d5 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,6 +18,10 @@
 #define AAPT_LINK_MANIFESTFIXER_H
 
 #include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
+#include <string>
 
 namespace aapt {
 
@@ -36,7 +40,7 @@
     ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
     }
 
-    bool consume(IAaptContext* context, XmlResource* doc) override;
+    bool consume(IAaptContext* context, xml::XmlResource* doc) override;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 5c5d8af..f6bf895 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "link/ManifestFixer.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -51,13 +50,13 @@
                 .build();
     }
 
-    std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+    std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
         return verifyWithOptions(str, {});
     }
 
-    std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
-                                                   const ManifestFixerOptions& options) {
-        std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+    std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
+                                                        const ManifestFixerOptions& options) {
+        std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
         ManifestFixer fixer(options);
         if (fixer.consume(mContext.get(), doc.get())) {
             return doc;
@@ -88,7 +87,7 @@
 TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
     ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
 
-    std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                 package="android">
         <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 5a2f5f0..3c8af4f 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -15,7 +15,6 @@
  */
 
 #include "ResourceTable.h"
-
 #include "link/Linkers.h"
 
 #include <algorithm>
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 8c924b5..4b82537 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
+#include "ReferenceLinker.h"
+
 #include "Diagnostics.h"
 #include "ResourceTable.h"
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
-#include "util/Util.h"
 #include "ValueVisitor.h"
-
 #include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <cassert>
@@ -41,52 +42,15 @@
  *
  * NOTE: All of the entries in the ResourceTable must be assigned IDs.
  */
-class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+class ReferenceLinkerVisitor : public ValueVisitor {
 private:
-    ReferenceLinkerVisitor mReferenceVisitor;
     IAaptContext* mContext;
     ISymbolTable* mSymbols;
-    IPackageDeclStack* mPackageDecls;
+    xml::IPackageDeclStack* mPackageDecls;
     StringPool* mStringPool;
+    CallSite* mCallSite;
     bool mError = false;
 
-    const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
-        assert(reference);
-        assert(reference->name || reference->id);
-
-        if (reference->name) {
-            // Transform the package name if it is an alias.
-            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
-                    reference->name.value(), mContext->getCompilationPackage());
-
-            // Mangle the reference name if it should be mangled.
-            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
-                    realName ? realName.value() : reference->name.value());
-
-            const ISymbolTable::Symbol* s = nullptr;
-            if (mangledName) {
-                s = mSymbols->findByName(mangledName.value());
-            } else if (realName) {
-                s = mSymbols->findByName(realName.value());
-            } else {
-                s = mSymbols->findByName(reference->name.value());
-            }
-
-            if (s && s->attribute) {
-                return s;
-            }
-        }
-
-        if (reference->id) {
-            if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
-                if (s->attribute) {
-                    return s;
-                }
-            }
-        }
-        return nullptr;
-    }
-
     /**
      * Transform a RawString value into a more specific, appropriate value, based on the
      * Attribute. If a non RawString value is passed in, this is an identity transform.
@@ -94,8 +58,8 @@
     std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
                                                   const Attribute* attr) {
         if (RawString* rawString = valueCast<RawString>(value.get())) {
-            std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
-                    *rawString->value, attr);
+            std::unique_ptr<Item> transformed =
+                    ResourceUtils::parseItemForAttribute(*rawString->value, attr);
 
             // If we could not parse as any specific type, try a basic STRING.
             if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
@@ -163,14 +127,16 @@
 public:
     using ValueVisitor::visit;
 
-    StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
-                                   StringPool* stringPool, IPackageDeclStack* decl) :
-            mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
-            mPackageDecls(decl), mStringPool(stringPool) {
+    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, StringPool* stringPool,
+                           xml::IPackageDeclStack* decl,CallSite* callSite) :
+            mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
+            mCallSite(callSite) {
     }
 
-    void visit(Reference* reference) override {
-        mReferenceVisitor.visit(reference);
+    void visit(Reference* ref) override {
+        if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
+            mError = true;
+        }
     }
 
     /**
@@ -184,13 +150,26 @@
         }
 
         for (Style::Entry& entry : style->entries) {
-            if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+            std::string errStr;
+
+            // Transform the attribute reference so that it is using the fully qualified package
+            // name. This will also mark the reference as being able to see private resources if
+            // there was a '*' in the reference or if the package came from the private namespace.
+            Reference transformedReference = entry.key;
+            transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
+                                            &transformedReference);
+
+            // Find the attribute in the symbol table and check if it is visible from this callsite.
+            const ISymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
+                    transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+            if (symbol) {
                 // Assign our style key the correct ID.
-                entry.key.id = s->id;
+                entry.key.id = symbol->id;
 
                 // Try to convert the value to a more specific, typed value based on the
                 // attribute it is set to.
-                entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+                entry.value = parseValueWithAttribute(std::move(entry.value),
+                                                      symbol->attribute.get());
 
                 // Link/resolve the final value (mostly if it's a reference).
                 entry.value->accept(this);
@@ -201,13 +180,13 @@
                 entry.value->flatten(&val);
 
                 // Always allow references.
-                const uint32_t typeMask = s->attribute->typeMask |
+                const uint32_t typeMask = symbol->attribute->typeMask |
                         android::ResTable_map::TYPE_REFERENCE;
 
                 if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
                     // The actual type of this item is incompatible with the attribute.
                     DiagMessage msg(style->getSource());
-                    buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+                    buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
                     mContext->getDiagnostics()->error(msg);
                     mError = true;
                 }
@@ -219,23 +198,151 @@
                 } else {
                     msg << entry.key.id.value();
                 }
-                msg << "' not found";
+                msg << "' " << errStr;
                 mContext->getDiagnostics()->error(msg);
+                mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
                 mError = true;
             }
         }
     }
 
-    inline bool hasError() {
-        return mError || mReferenceVisitor.hasError();
+    bool hasError() {
+        return mError;
     }
 };
 
-struct EmptyDeclStack : public IPackageDeclStack {
-    Maybe<ResourceName> transformPackage(const ResourceName& name,
-                                         const StringPiece16& localPackage) const override {
-        if (name.package.empty()) {
-            return ResourceName{ localPackage.toString(), name.type, name.entry };
+} // namespace
+
+/**
+ * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * or if the callsite comes from the same package.
+ */
+bool ReferenceLinker::isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+                                      const CallSite& callSite) {
+    if (!symbol.isPublic && !ref.privateReference) {
+        if (ref.name) {
+            return callSite.resource.package == ref.name.value().package;
+        } else if (ref.id) {
+            return ref.id.value().packageId() == symbol.id.packageId();
+        } else {
+            return false;
+        }
+    }
+    return true;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
+                                                           NameMangler* mangler,
+                                                           ISymbolTable* symbols) {
+    if (reference.name) {
+        Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+        return symbols->findByName(mangled ? mangled.value() : reference.name.value());
+    } else if (reference.id) {
+        return symbols->findById(reference.id.value());
+    } else {
+        return nullptr;
+    }
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
+        const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+        CallSite* callSite, std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+    if (!symbol) {
+        std::stringstream errStr;
+        errStr << "not found";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+
+    if (!isSymbolVisible(*symbol, reference, *callSite)) {
+        std::stringstream errStr;
+        errStr << "is private";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+    return symbol;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
+        const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+        CallSite* callSite, std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
+                                                                      symbols, callSite,
+                                                                      outError);
+    if (!symbol) {
+        return nullptr;
+    }
+
+    if (!symbol->attribute) {
+        std::stringstream errStr;
+        errStr << "is not an attribute";
+        if (outError) *outError = errStr.str();
+        return nullptr;
+    }
+    return symbol;
+}
+
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
+                                                               NameMangler* nameMangler,
+                                                               ISymbolTable* symbols,
+                                                               CallSite* callSite,
+                                                               std::string* outError) {
+    const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+    if (!symbol) {
+        return {};
+    }
+
+    if (!symbol->attribute) {
+        std::stringstream errStr;
+        errStr << "is not an attribute";
+        if (outError) *outError = errStr.str();
+        return {};
+    }
+    return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+}
+
+bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
+                                    ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+                                    CallSite* callSite) {
+    assert(reference);
+    assert(reference->name || reference->id);
+
+    Reference transformedReference = *reference;
+    transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+                                    &transformedReference);
+
+    std::string errStr;
+    const ISymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+            transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
+    if (s) {
+        reference->id = s->id;
+        return true;
+    }
+
+    DiagMessage errorMsg(reference->getSource());
+    errorMsg << "resource ";
+    if (reference->name) {
+        errorMsg << reference->name.value();
+        if (transformedReference.name.value() != reference->name.value()) {
+            errorMsg << " (aka " << transformedReference.name.value() << ")";
+        }
+    } else {
+        errorMsg << reference->id.value();
+    }
+
+    errorMsg << " " << errStr;
+    context->getDiagnostics()->error(errorMsg);
+    return false;
+}
+
+namespace {
+
+struct EmptyDeclStack : public xml::IPackageDeclStack {
+    Maybe<xml::ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override {
+        if (alias.empty()) {
+            return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
         }
         return {};
     }
@@ -259,14 +366,16 @@
                     error = true;
                 }
 
+                CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
+                ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+                                               &table->stringPool, &declStack, &callSite);
+
                 for (auto& configValue : entry->values) {
-                    StyleAndReferenceLinkerVisitor visitor(context,
-                                                           context->getExternalSymbols(),
-                                                           &table->stringPool, &declStack);
                     configValue.value->accept(&visitor);
-                    if (visitor.hasError()) {
-                        error = true;
-                    }
+                }
+
+                if (visitor.hasError()) {
+                    error = true;
                 }
             }
         }
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
new file mode 100644
index 0000000..6f11d58
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_LINKER_REFERENCELINKER_H
+#define AAPT_LINKER_REFERENCELINKER_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "xml/XmlDom.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * The ResourceTable must already have IDs assigned to each resource.
+ * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ */
+struct ReferenceLinker : public IResourceTableConsumer {
+    /**
+     * Returns true if the symbol is visible by the reference and from the callsite.
+     */
+    static bool isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+                                const CallSite& callSite);
+
+    /**
+     * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
+     * if the symbol was not found.
+     */
+    static const ISymbolTable::Symbol* resolveSymbol(const Reference& reference,
+                                                     NameMangler* mangler, ISymbolTable* symbols);
+
+    /**
+     * Performs name mangling and looks up the resource in the symbol table. If the symbol is
+     * not visible by the reference at the callsite, nullptr is returned. outError holds
+     * the error message.
+     */
+    static const ISymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
+                                                                    NameMangler* nameMangler,
+                                                                    ISymbolTable* symbols,
+                                                                    CallSite* callSite,
+                                                                    std::string* outError);
+
+    /**
+     * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
+     * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
+     */
+    static const ISymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
+                                                                       NameMangler* nameMangler,
+                                                                       ISymbolTable* symbols,
+                                                                       CallSite* callSite,
+                                                                       std::string* outError);
+
+    /**
+     * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
+     * If resolution fails, outError holds the error message.
+     */
+    static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
+                                                         NameMangler* nameMangler,
+                                                         ISymbolTable* symbols,
+                                                         CallSite* callSite,
+                                                         std::string* outError);
+
+    /**
+     * Transforms the package name of the reference to the fully qualified package name using
+     * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
+     * to the reference at the callsite, the reference is updated with an ID.
+     * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
+     */
+    static bool linkReference(Reference* reference, IAaptContext* context, ISymbolTable* symbols,
+                              xml::IPackageDeclStack* decls, CallSite* callSite);
+
+    /**
+     * Links all references in the ResourceTable.
+     */
+    bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
deleted file mode 100644
index a4cb596..0000000
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ /dev/null
@@ -1,109 +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.
- */
-
-#ifndef AAPT_LINKER_REFERENCELINKERVISITOR_H
-#define AAPT_LINKER_REFERENCELINKERVISITOR_H
-
-#include "Resource.h"
-#include "ResourceValues.h"
-#include "ValueVisitor.h"
-
-#include "process/IResourceTableConsumer.h"
-#include "process/SymbolTable.h"
-
-#include <cassert>
-
-namespace aapt {
-
-/**
- * The ReferenceLinkerVisitor will follow all references and make sure they point
- * to resources that actually exist in the given ISymbolTable.
- * Once the target resource has been found, the ID of the resource will be assigned
- * to the reference object.
- */
-class ReferenceLinkerVisitor : public ValueVisitor {
-    using ValueVisitor::visit;
-private:
-    IAaptContext* mContext;
-    ISymbolTable* mSymbols;
-    IPackageDeclStack* mPackageDecls;
-    bool mError = false;
-
-public:
-    ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
-            mContext(context), mSymbols(symbols), mPackageDecls(decls) {
-    }
-
-    /**
-     * Lookup a reference and ensure it exists, either in our local table, or as an external
-     * symbol. Once found, assign the ID of the target resource to this reference object.
-     */
-    void visit(Reference* reference) override {
-        assert(reference);
-        assert(reference->name || reference->id);
-
-        // We prefer to lookup by name if the name is set. Otherwise it could be
-        // an out-of-date ID.
-        if (reference->name) {
-            // Transform the package name if it is an alias.
-            Maybe<ResourceName> realName = mPackageDecls->transformPackage(
-                    reference->name.value(), mContext->getCompilationPackage());
-
-            // Mangle the reference name if it should be mangled.
-            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
-                    realName ? realName.value() : reference->name.value());
-
-            const ISymbolTable::Symbol* s = nullptr;
-            if (mangledName) {
-                s = mSymbols->findByName(mangledName.value());
-            } else if (realName) {
-                s = mSymbols->findByName(realName.value());
-            } else {
-                s = mSymbols->findByName(reference->name.value());
-            }
-
-            if (s) {
-                reference->id = s->id;
-                return;
-            }
-
-            DiagMessage errorMsg(reference->getSource());
-            errorMsg << "reference to " << reference->name.value();
-            if (realName) {
-                errorMsg << " (aka " << realName.value() << ")";
-            }
-            errorMsg << " was not found";
-            mContext->getDiagnostics()->error(errorMsg);
-            mError = true;
-            return;
-        }
-
-        if (!mSymbols->findById(reference->id.value())) {
-            mContext->getDiagnostics()->error(DiagMessage(reference->getSource())
-                                              << "reference to " << reference->id.value()
-                                              << " was not found");
-            mError = true;
-        }
-    }
-
-    inline bool hasError() {
-        return mError;
-    }
-};
-
-} // namespace aapt
-
-#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 5e7641a..8d324fe 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
 #include "process/SymbolTable.h"
 
 #include "test/Builders.h"
@@ -44,7 +44,7 @@
             .setSymbolTable(JoinedSymbolTableBuilder()
                             .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
                             .addSymbolTable(test::StaticSymbolTableBuilder()
-                                    .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+                                    .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034))
                                     .build())
                             .build())
             .build();
@@ -92,12 +92,12 @@
             .setPackageId(0x7f)
             .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
             .setSymbolTable(test::StaticSymbolTableBuilder()
-                    .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
-                    .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+                    .addPublicSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+                    .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001),
                                test::AttributeBuilder()
                                     .setTypeMask(android::ResTable_map::TYPE_COLOR)
                                     .build())
-                    .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+                    .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002),
                                test::AttributeBuilder()
                                     .setTypeMask(android::ResTable_map::TYPE_FLAGS)
                                     .addItem(u"one", 0x01)
@@ -132,7 +132,7 @@
             .setPackageId(0x7f)
             .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
             .setSymbolTable(test::StaticSymbolTableBuilder()
-                    .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+                    .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo",
                                ResourceId(0x7f010000), test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
                     .build())
@@ -156,4 +156,78 @@
     EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
 }
 
+TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@android:string/hidden")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@android:string/hidden", ResourceId(0x01040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+                          u"@com.app.lib:string/hidden")
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@com.app.test:string/com.app.lib$hidden",
+                                               ResourceId(0x7f040034))
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.test", 0x7f)
+            .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+                    .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff"))
+                    .build())
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+            .setCompilationPackage(u"com.app.test")
+            .setPackageId(0x7f)
+            .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+            .setSymbolTable(JoinedSymbolTableBuilder()
+                            .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+                            .addSymbolTable(test::StaticSymbolTableBuilder()
+                                    .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001),
+                                               test::AttributeBuilder()
+                                                    .setTypeMask(android::ResTable_map::TYPE_COLOR)
+                                                    .build())
+                                    .build())
+                            .build())
+            .build();
+
+    ReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index caab9b8..a26d763 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -17,51 +17,91 @@
 #include "Diagnostics.h"
 #include "ResourceUtils.h"
 #include "SdkConstants.h"
-#include "XmlDom.h"
-
 #include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
+#include "link/ReferenceLinker.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
 #include "util/Util.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {
 
 namespace {
 
-class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+/**
+ * Visits all references (including parents of styles, references in styles, arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * as needed.
+ */
+class ReferenceVisitor : public ValueVisitor {
+private:
+    IAaptContext* mContext;
+    ISymbolTable* mSymbols;
+    xml::IPackageDeclStack* mDecls;
+    CallSite* mCallSite;
+    bool mError;
+
+public:
+    using ValueVisitor::visit;
+
+    ReferenceVisitor(IAaptContext* context, ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+                     CallSite* callSite) :
+             mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
+             mError(false) {
+    }
+
+    void visit(Reference* ref) override {
+        if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
+            mError = true;
+        }
+    }
+
+    bool hasError() const {
+        return mError;
+    }
+};
+
+/**
+ * Visits each xml Element and compiles the attributes within.
+ */
+class XmlVisitor : public xml::PackageAwareVisitor {
 private:
     IAaptContext* mContext;
     ISymbolTable* mSymbols;
     Source mSource;
     std::set<int>* mSdkLevelsFound;
-    ReferenceLinkerVisitor mReferenceLinkerVisitor;
+    CallSite* mCallSite;
+    ReferenceVisitor mReferenceVisitor;
     bool mError = false;
 
 public:
     using xml::PackageAwareVisitor::visit;
 
-    XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
-                              std::set<int>* sdkLevelsFound) :
+    XmlVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
+               std::set<int>* sdkLevelsFound, CallSite* callSite) :
             mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
-            mReferenceLinkerVisitor(context, symbols, this) {
+            mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
     }
 
     void visit(xml::Element* el) override {
         const Source source = mSource.withLine(el->lineNumber);
         for (xml::Attribute& attr : el->attributes) {
-            Maybe<std::u16string> maybePackage =
-                    util::extractPackageFromNamespace(attr.namespaceUri);
+            Maybe<xml::ExtractedPackage> maybePackage =
+                    xml::extractPackageFromNamespace(attr.namespaceUri);
             if (maybePackage) {
                 // There is a valid package name for this attribute. We will look this up.
-                StringPiece16 package = maybePackage.value();
+                StringPiece16 package = maybePackage.value().package;
                 if (package.empty()) {
                     // Empty package means the 'current' or 'local' package.
                     package = mContext->getCompilationPackage();
                 }
 
-                attr.compiledAttribute = compileAttribute(
-                        ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+                Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
+                attrRef.privateReference = maybePackage.value().privateNamespace;
+
+                std::string errStr;
+                attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+                        attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
 
                 // Convert the string value into a compiled Value if this is a valid attribute.
                 if (attr.compiledAttribute) {
@@ -87,7 +127,7 @@
                 } else {
                     mContext->getDiagnostics()->error(DiagMessage(source)
                                                       << "attribute '" << package << ":"
-                                                      << attr.name << "' was not found");
+                                                      << attr.name << "' " << errStr);
                     mError = true;
 
                 }
@@ -99,7 +139,7 @@
             if (attr.compiledValue) {
                 // With a compiledValue, we must resolve the reference and assign it an ID.
                 attr.compiledValue->setSource(source);
-                attr.compiledValue->accept(&mReferenceLinkerVisitor);
+                attr.compiledValue->accept(&mReferenceVisitor);
             }
         }
 
@@ -107,28 +147,18 @@
         xml::PackageAwareVisitor::visit(el);
     }
 
-    Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
-        Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
-        if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
-                mangledName ? mangledName.value() : name)) {
-            if (symbol->attribute) {
-                return xml::AaptAttribute{ symbol->id, *symbol->attribute };
-            }
-        }
-        return {};
-    }
-
-    inline bool hasError() {
-        return mError || mReferenceLinkerVisitor.hasError();
+    bool hasError() {
+        return mError || mReferenceVisitor.hasError();
     }
 };
 
 } // namespace
 
-bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
     mSdkLevelsFound.clear();
-    XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
-                                      &mSdkLevelsFound);
+    CallSite callSite = { resource->file.name };
+    XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+                       &mSdkLevelsFound, &callSite);
     if (resource->root) {
         resource->root->accept(&visitor);
         return !visitor.hasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 7f91ec3..3bfaf91 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -31,37 +31,40 @@
                 .setNameManglerPolicy(
                         NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
                 .setSymbolTable(test::StaticSymbolTableBuilder()
-                        .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+                        .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_ENUM |
                                                      android::ResTable_map::TYPE_DIMENSION)
                                         .addItem(u"match_parent", 0xffffffff)
                                         .build())
-                        .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+                        .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+                        .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002),
                                    test::AttributeBuilder().build())
-                        .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+                        .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_STRING)
                                         .build())
 
                          // Add one real symbol that was introduces in v21
-                        .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+                        .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
                                    test::AttributeBuilder().build())
 
-                        .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+                        // Private symbol.
+                        .addSymbol(u"@android:color/hidden", ResourceId(0x01020001))
+
+                        .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000))
                         .addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
                         .addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
                         .addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
                         .addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
                                    test::AttributeBuilder()
                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+                        .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
                                    ResourceId(0x7f010001), test::AttributeBuilder()
                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+                        .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
                                    test::AttributeBuilder().build())
                         .build())
                 .build();
@@ -71,23 +74,8 @@
     std::unique_ptr<IAaptContext> mContext;
 };
 
-static xml::Element* getRootElement(XmlResource* doc) {
-    xml::Node* node = doc->root.get();
-    while (xml::nodeCast<xml::Namespace>(node)) {
-        if (node->children.empty()) {
-            return nullptr;
-        }
-        node = node->children.front().get();
-    }
-
-    if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
-        return el;
-    }
-    return nullptr;
-}
-
 TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
         <View xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:background="@color/green"
@@ -97,7 +85,7 @@
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
@@ -132,8 +120,26 @@
     ASSERT_EQ(xmlAttr->compiledValue, nullptr);
 }
 
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+        <View xmlns:android="http://schemas.android.com/apk/res/android"
+              android:colorAccent="@android:color/hidden" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+    <View xmlns:android="http://schemas.android.com/apk/res/android"
+          android:colorAccent="@*android:color/hidden" />)EOF");
+
+    XmlReferenceLinker linker;
+    ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+}
+
 TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
         <View xmlns:android="http://schemas.android.com/apk/res/android"
               android:colorAccent="#ffffff" />)EOF");
 
@@ -143,14 +149,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
             <View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
                   support:colorAccent="#ff0000" />)EOF");
 
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(
@@ -162,14 +168,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
             <View xmlns:app="http://schemas.android.com/apk/res-auto"
                   app:colorAccent="@app:color/red" />)EOF");
 
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
@@ -185,7 +191,7 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
             <View xmlns:app="http://schemas.android.com/apk/res/android"
                   app:attr="@app:id/id">
               <View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
@@ -195,7 +201,7 @@
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     // All attributes and references in this element should be referring to "android" (0x01).
@@ -225,14 +231,14 @@
 }
 
 TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
-    std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+    std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
             <View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
                   android:attr="@id/id"/>)EOF");
 
     XmlReferenceLinker linker;
     ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
 
-    xml::Element* viewEl = getRootElement(doc.get());
+    xml::Element* viewEl = xml::findRootElement(doc.get());
     ASSERT_NE(viewEl, nullptr);
 
     // All attributes and references in this element should be referring to "com.app.test" (0x7f).
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 24ad05d..a2528d2 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -49,25 +49,13 @@
 };
 
 namespace xml {
-struct Node;
+struct XmlResource;
 }
 
-struct XmlResource {
-    ResourceFile file;
-    std::unique_ptr<xml::Node> root;
-};
-
 struct IXmlResourceConsumer {
     virtual ~IXmlResourceConsumer() = default;
 
-    virtual bool consume(IAaptContext* context, XmlResource* resource) = 0;
-};
-
-struct IPackageDeclStack {
-    virtual ~IPackageDeclStack() = default;
-
-    virtual Maybe<ResourceName> transformPackage(const ResourceName& name,
-                                                 const StringPiece16& localPackage) const = 0;
+    virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index bb33ea7..d04181d 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -51,6 +51,7 @@
 
     std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
     symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
+    symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
 
     if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
         const ConfigDescription kDefaultConfig;
@@ -158,6 +159,7 @@
         }
 
         if (s) {
+            s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
             mCache.put(name, s);
             return s.get();
         }
@@ -165,6 +167,44 @@
     return nullptr;
 }
 
+static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
+    android::ResTable::resource_name resName;
+    if (!table.getResourceName(id.id, true, &resName)) {
+        return {};
+    }
+
+    ResourceName name;
+    if (resName.package) {
+        name.package = StringPiece16(resName.package, resName.packageLen).toString();
+    }
+
+    const ResourceType* type;
+    if (resName.type) {
+        type = parseResourceType(StringPiece16(resName.type, resName.typeLen));
+
+    } else if (resName.type8) {
+        type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen)));
+    } else {
+        return {};
+    }
+
+    if (!type) {
+        return {};
+    }
+
+    name.type = *type;
+
+    if (resName.name) {
+        name.entry = StringPiece16(resName.name, resName.nameLen).toString();
+    } else if (resName.name8) {
+        name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen));
+    } else {
+        return {};
+    }
+
+    return name;
+}
+
 const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
         ResourceId id) {
     if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
@@ -174,22 +214,16 @@
     for (const auto& asset : mAssets) {
         const android::ResTable& table = asset->getResources(false);
 
-        android::ResTable::resource_name name;
-        if (!table.getResourceName(id.id, true, &name)) {
+        Maybe<ResourceName> maybeName = getResourceName(table, id);
+        if (!maybeName) {
             continue;
         }
 
-        bool isAttr = false;
-        if (name.type) {
-            if (const ResourceType* t = parseResourceType(StringPiece16(name.type, name.typeLen))) {
-                isAttr = (*t == ResourceType::kAttr);
-            }
-        } else if (name.type8) {
-            isAttr = (StringPiece(name.type8, name.typeLen) == "attr");
-        }
+        uint32_t typeSpecFlags = 0;
+        table.getResourceFlags(id.id, &typeSpecFlags);
 
         std::shared_ptr<Symbol> s;
-        if (isAttr) {
+        if (maybeName.value().type == ResourceType::kAttr) {
             s = lookupAttributeInTable(table, id);
         } else {
             s = std::make_shared<Symbol>();
@@ -197,6 +231,7 @@
         }
 
         if (s) {
+            s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
             mIdCache.put(id, s);
             return s.get();
         }
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 89cd972..9ca694a 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,10 +19,9 @@
 
 #include "ResourceTable.h"
 #include "ResourceValues.h"
-#include "XmlDom.h"
-#include "util/Util.h"
-
 #include "test/Common.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
 
 #include <memory>
 
@@ -212,15 +211,22 @@
     }
 };
 
-inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) {
     std::stringstream in;
     in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
     StdErrDiagnostics diag;
-    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {});
+    std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {});
     assert(doc);
     return doc;
 }
 
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
+                                                                   const StringPiece& str) {
+    std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+    doc->file.name.package = context->getCompilationPackage().toString();
+    return doc;
+}
+
 } // namespace test
 } // namespace aapt
 
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4fa4918..555a539 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -135,8 +135,19 @@
     std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
 
 public:
+    StaticSymbolTableBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id,
+                                              std::unique_ptr<Attribute> attr = {}) {
+        std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
+                id, std::move(attr));
+        symbol->isPublic = true;
+        mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+        mSymbolTable->mIdMap[id] = symbol.get();
+        mSymbolTable->mSymbols.push_back(std::move(symbol));
+        return *this;
+    }
+
     StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
-                                  std::unique_ptr<Attribute> attr = {}) {
+                                        std::unique_ptr<Attribute> attr = {}) {
         std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
                 id, std::move(attr));
         mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 3048334..49625b5 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -97,19 +97,19 @@
     return !error;
 }
 
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
     if (!mSymbolEntries || mSymbolEntryCount == 0) {
-        return false;
+        return {};
     }
 
     if ((uintptr_t) data < (uintptr_t) mData) {
-        return false;
+        return {};
     }
 
     // We only support 32 bit offsets right now.
     const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
     if (offset > std::numeric_limits<uint32_t>::max()) {
-        return false;
+        return {};
     }
 
     for (size_t i = 0; i < mSymbolEntryCount; i++) {
@@ -118,24 +118,23 @@
             const StringPiece16 str = util::getString(
                     mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
 
-            StringPiece16 typeStr;
-            ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
-                                               &outSymbol->entry);
-            const ResourceType* type = parseResourceType(typeStr);
-            if (!type) {
-                return false;
+            ResourceNameRef nameRef;
+            bool privateRef = false;
+            if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
+                return {};
             }
 
-            outSymbol->type = *type;
-
             // Since we scan the symbol table in order, we can start looking for the
             // next symbol from this point.
             mSymbolEntryCount -= i + 1;
             mSymbolEntries += i + 1;
-            return true;
+
+            Reference ref(nameRef);
+            ref.privateReference = privateRef;
+            return Maybe<Reference>(std::move(ref));
         }
     }
-    return false;
+    return {};
 }
 
 /**
@@ -566,7 +565,13 @@
             resourceValue = parseValue(name, config, value, entry->flags);
         }
 
-        assert(resourceValue && "failed to interpret valid resource");
+        if (!resourceValue) {
+            mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                              << "failed to parse value for resource " << name
+                                              << " (" << resId << ") with configuration '"
+                                              << config << "'");
+            return false;
+        }
 
         Source source = mSource;
         if (sourceBlock) {
@@ -657,7 +662,7 @@
     if (value->dataType == Res_value::TYPE_REFERENCE ||
             value->dataType == Res_value::TYPE_ATTRIBUTE) {
         const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
-                    Reference::Type::kResource : Reference::Type::kAttribute;
+                Reference::Type::kResource : Reference::Type::kAttribute;
 
         if (data != 0) {
             // This is a normal reference.
@@ -665,9 +670,9 @@
         }
 
         // This reference has an invalid ID. Check if it is an unresolved symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&value->data, &symbol)) {
-            return util::make_unique<Reference>(symbol, type);
+        if (Maybe<Reference> ref = getSymbol(&value->data)) {
+            ref.value().referenceType = type;
+            return util::make_unique<Reference>(std::move(ref.value()));
         }
 
         // This is not an unresolved symbol, so it must be the magic @null reference.
@@ -715,10 +720,8 @@
     if (util::deviceToHost32(map->parent.ident) == 0) {
         // The parent is either not set or it is an unresolved symbol.
         // Check to see if it is a symbol.
-        ResourceNameRef symbol;
-        if (getSymbol(&map->parent.ident, &symbol)) {
-            style->parent = Reference(symbol.toResourceName());
-        }
+        style->parent = getSymbol(&map->parent.ident);
+
     } else {
          // The parent is a regular reference to a resource.
         style->parent = Reference(util::deviceToHost32(map->parent.ident));
@@ -731,10 +734,14 @@
         if (util::deviceToHost32(mapEntry.name.ident) == 0) {
             // The map entry's key (attribute) is not set. This must be
             // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleEntry.key.name = symbol.toResourceName();
+            Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
+            if (!symbol) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "unresolved style attribute");
+                return {};
+            }
+            styleEntry.key = std::move(symbol.value());
+
         } else {
             // The map entry's key (attribute) is a regular reference.
             styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -742,7 +749,9 @@
 
         // Parse the attribute's value.
         styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
-        assert(styleEntry.value);
+        if (!styleEntry.value) {
+            return {};
+        }
     }
     return style;
 }
@@ -773,10 +782,14 @@
             if (util::deviceToHost32(mapEntry.name.ident) == 0) {
                 // The map entry's key (id) is not set. This must be
                 // a symbol reference, so resolve it.
-                ResourceNameRef symbolName;
-                bool result = getSymbol(&mapEntry.name.ident, &symbolName);
-                assert(result);
-                symbol.symbol.name = symbolName.toResourceName();
+                Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+                if (!ref) {
+                    mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                      << "unresolved attribute symbol");
+                    return {};
+                }
+                symbol.symbol = std::move(ref.value());
+
             } else {
                 // The map entry's key (id) is a regular reference.
                 symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -808,10 +821,14 @@
         if (util::deviceToHost32(mapEntry.name.ident) == 0) {
             // The map entry's key (attribute) is not set. This must be
             // a symbol reference, so resolve it.
-            ResourceNameRef symbol;
-            bool result = getSymbol(&mapEntry.name.ident, &symbol);
-            assert(result);
-            styleable->entries.emplace_back(symbol);
+            Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+            if (!ref) {
+                mContext->getDiagnostics()->error(DiagMessage(mSource)
+                                                  << "unresolved styleable symbol");
+                return {};
+            }
+            styleable->entries.emplace_back(std::move(ref.value()));
+
         } else {
             // The map entry's key (attribute) is a regular reference.
             styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
@@ -826,6 +843,9 @@
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
     for (const ResTable_map& mapEntry : map) {
         std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+        if (!item) {
+            return {};
+        }
 
         switch (util::deviceToHost32(mapEntry.name.ident)) {
             case android::ResTable_map::ATTR_ZERO:
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 02c4081..73fcf52 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -57,7 +57,7 @@
 private:
     // Helper method to retrieve the symbol name for a given table offset specified
     // as a pointer.
-    bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+    Maybe<Reference> getSymbol(const void* data);
 
     bool parseTable(const android::ResChunk_header* chunk);
     bool parseSymbolTable(const android::ResChunk_header* chunk);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 59b8385..9ecc974 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -28,9 +28,6 @@
 namespace aapt {
 namespace util {
 
-constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
-constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
-
 static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
         const std::function<char(char)>& f) {
     std::vector<std::string> parts;
@@ -467,18 +464,6 @@
     return data;
 }
 
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
-    if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
-        StringPiece16 schemaPrefix = kSchemaPrefix;
-        StringPiece16 package = namespaceUri;
-        return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
-                .toString();
-    } else if (namespaceUri == kSchemaAuto) {
-        return std::u16string();
-    }
-    return {};
-}
-
 bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
                              StringPiece16* outEntry, StringPiece16* outSuffix) {
     if (!stringStartsWith<char16_t>(path, u"res/")) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 324afb3..a898619 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -331,15 +331,6 @@
 }
 
 /**
- * Returns a package name if the namespace URI is of the form:
- * http://schemas.android.com/apk/res/<package>
- *
- * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
- * returns an empty package name.
- */
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
-
-/**
  * Given a path like: res/xml-sw600dp/foo.xml
  *
  * Extracts "res/xml-sw600dp/" into outPrefix.
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
similarity index 91%
rename from tools/aapt2/XmlDom.cpp
rename to tools/aapt2/xml/XmlDom.cpp
index b769c76..d27b62fd 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include "util/Util.h"
 #include "XmlDom.h"
 #include "XmlPullParser.h"
+#include "util/Util.h"
 
 #include <cassert>
+#include <expat.h>
 #include <memory>
 #include <stack>
 #include <string>
@@ -317,6 +318,10 @@
     return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
 }
 
+Element* findRootElement(XmlResource* doc) {
+    return findRootElement(doc->root.get());
+}
+
 Element* findRootElement(Node* node) {
     if (!node) {
         return nullptr;
@@ -397,5 +402,39 @@
     return elements;
 }
 
+void PackageAwareVisitor::visit(Namespace* ns) {
+   bool added = false;
+   if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
+       ExtractedPackage& package = maybePackage.value();
+       mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
+       added = true;
+   }
+
+   Visitor::visit(ns);
+
+   if (added) {
+       mPackageDecls.pop_back();
+   }
+}
+
+Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
+       const StringPiece16& alias, const StringPiece16& localPackage) const {
+   if (alias.empty()) {
+       return ExtractedPackage{ localPackage.toString(), false /* private */ };
+   }
+
+   const auto rend = mPackageDecls.rend();
+   for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+       if (alias == iter->prefix) {
+           if (iter->package.package.empty()) {
+               return ExtractedPackage{ localPackage.toString(),
+                                              iter->package.privateNamespace };
+           }
+           return iter->package;
+       }
+   }
+   return {};
+}
+
 } // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/xml/XmlDom.h
similarity index 74%
rename from tools/aapt2/XmlDom.h
rename to tools/aapt2/xml/XmlDom.h
index 721bf5b..033b0a4 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -22,11 +22,9 @@
 #include "ResourceValues.h"
 #include "util/StringPiece.h"
 #include "util/Util.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlUtil.h"
 
 #include <istream>
-#include <expat.h>
 #include <memory>
 #include <string>
 #include <vector>
@@ -34,21 +32,9 @@
 namespace aapt {
 namespace xml {
 
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
 struct RawVisitor;
 
 /**
- * The type of node. Can be used to downcast to the concrete XML node
- * class.
- */
-enum class NodeType {
-    kNamespace,
-    kElement,
-    kText,
-};
-
-/**
  * Base class for all XML nodes.
  */
 struct Node {
@@ -58,9 +44,10 @@
     std::u16string comment;
     std::vector<std::unique_ptr<Node>> children;
 
+    virtual ~Node() = default;
+
     void addChild(std::unique_ptr<Node> child);
     virtual void accept(RawVisitor* visitor) = 0;
-    virtual ~Node() {}
 };
 
 /**
@@ -122,6 +109,14 @@
 };
 
 /**
+ * An XML resource with a source, name, and XML tree.
+ */
+struct XmlResource {
+    ResourceFile file;
+    std::unique_ptr<xml::Node> root;
+};
+
+/**
  * Inflates an XML DOM from a text stream, logging errors to the logger.
  * Returns the root node on success, or nullptr on failure.
  */
@@ -134,6 +129,7 @@
 std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
                                      const Source& source);
 
+Element* findRootElement(XmlResource* doc);
 Element* findRootElement(Node* node);
 
 /**
@@ -180,7 +176,7 @@
 private:
     struct PackageDecl {
         std::u16string prefix;
-        std::u16string package;
+        ExtractedPackage package;
     };
 
     std::vector<PackageDecl> mPackageDecls;
@@ -188,44 +184,9 @@
 public:
     using Visitor::visit;
 
-    void visit(Namespace* ns) override {
-        bool added = false;
-        {
-            Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
-            if (package) {
-                mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
-                added = true;
-            }
-        }
-
-        Visitor::visit(ns);
-
-        if (added) {
-            mPackageDecls.pop_back();
-        }
-    }
-
-    Maybe<ResourceName> transformPackage(const ResourceName& name,
-                                         const StringPiece16& localPackage) const override {
-        if (name.package.empty()) {
-            return ResourceName{ localPackage.toString(), name.type, name.entry };
-        }
-
-        const auto rend = mPackageDecls.rend();
-        for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
-            if (name.package == iter->prefix) {
-                if (iter->package.empty()) {
-                    if (localPackage != name.package) {
-                        return ResourceName{ localPackage.toString(), name.type, name.entry };
-                    }
-                } else if (iter->package != name.package) {
-                    return ResourceName{ iter->package, name.type, name.entry };
-                }
-                break;
-            }
-        }
-        return {};
-    }
+    void visit(Namespace* ns) override;
+    Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override;
 };
 
 // Implementations
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
similarity index 93%
rename from tools/aapt2/XmlDom_test.cpp
rename to tools/aapt2/xml/XmlDom_test.cpp
index a1b9ed0..431ee2c 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -38,7 +38,7 @@
 
     const Source source = { "test.xml" };
     StdErrDiagnostics diag;
-    std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+    std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
     ASSERT_NE(doc, nullptr);
 
     xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
similarity index 85%
rename from tools/aapt2/XmlPullParser.cpp
rename to tools/aapt2/xml/XmlPullParser.cpp
index cff935c..323ec05 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -16,12 +16,14 @@
 
 #include "util/Maybe.h"
 #include "util/Util.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
+#include "xml/XmlUtil.h"
 
 #include <iostream>
 #include <string>
 
 namespace aapt {
+namespace xml {
 
 constexpr char kXmlNamespaceSep = 1;
 
@@ -72,14 +74,14 @@
     // Record namespace prefixes and package names so that we can do our own
     // handling of references that use namespace aliases.
     if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
-        Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+        Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
         if (event == Event::kStartNamespace) {
             if (result) {
-                mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+                mPackageAliases.emplace_back(
+                        PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
             }
         } else {
             if (result) {
-                assert(mPackageAliases.back().second == result.value());
                 mPackageAliases.pop_back();
             }
         }
@@ -131,20 +133,20 @@
     return mEventQueue.front().data2;
 }
 
-Maybe<ResourceName> XmlPullParser::transformPackage(
-        const ResourceName& name, const StringPiece16& localPackage) const {
-    if (name.package.empty()) {
-        return ResourceName{ localPackage.toString(), name.type, name.entry };
+Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
+        const StringPiece16& alias, const StringPiece16& localPackage) const {
+    if (alias.empty()) {
+        return ExtractedPackage{ localPackage.toString(), false /* private */ };
     }
 
     const auto endIter = mPackageAliases.rend();
     for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
-        if (name.package == iter->first) {
-            if (iter->second.empty()) {
-                return ResourceName{ localPackage.toString(), name.type, name.entry };
-            } else {
-                return ResourceName{ iter->second, name.type, name.entry };
+        if (alias == iter->prefix) {
+            if (iter->package.package.empty()) {
+                return ExtractedPackage{ localPackage.toString(),
+                                         iter->package.privateNamespace };
             }
+            return iter->package;
         }
     }
     return {};
@@ -283,4 +285,24 @@
     });
 }
 
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        return StringPiece16(util::trimWhitespace(iter->value));
+    }
+    return {};
+}
+
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+    auto iter = parser->findAttribute(u"", name);
+    if (iter != parser->endAttributes()) {
+        StringPiece16 trimmed = util::trimWhitespace(iter->value);
+        if (!trimmed.empty()) {
+            return trimmed;
+        }
+    }
+    return {};
+}
+
+} // namespace xml
 } // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
similarity index 91%
rename from tools/aapt2/XmlPullParser.h
rename to tools/aapt2/xml/XmlPullParser.h
index a0ce21d..7e7070e 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,11 +17,11 @@
 #ifndef AAPT_XML_PULL_PARSER_H
 #define AAPT_XML_PULL_PARSER_H
 
-#include "util/Maybe.h"
 #include "Resource.h"
-#include "util/StringPiece.h"
-
 #include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
 
 #include <algorithm>
 #include <expat.h>
@@ -33,6 +33,7 @@
 #include <vector>
 
 namespace aapt {
+namespace xml {
 
 class XmlPullParser : public IPackageDeclStack {
 public:
@@ -60,7 +61,7 @@
     static bool isGoodEvent(Event event);
 
     XmlPullParser(std::istream& in);
-    virtual ~XmlPullParser();
+    ~XmlPullParser();
 
     /**
      * Returns the current event that is being processed.
@@ -95,6 +96,13 @@
     const std::u16string& getNamespacePrefix() const;
     const std::u16string& getNamespaceUri() const;
 
+    //
+    // These are available for StartElement and EndElement.
+    //
+
+    const std::u16string& getElementNamespace() const;
+    const std::u16string& getElementName() const;
+
     /*
      * Uses the current stack of namespaces to resolve the package. Eg:
      * xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
@@ -106,17 +114,8 @@
      * If xmlns:app="http://schemas.android.com/apk/res-auto", then
      * 'package' will be set to 'defaultPackage'.
      */
-    //
-
-    //
-    // These are available for StartElement and EndElement.
-    //
-
-    const std::u16string& getElementNamespace() const;
-    const std::u16string& getElementName() const;
-
-    Maybe<ResourceName> transformPackage(const ResourceName& name,
-                                         const StringPiece16& localPackage) const override;
+    Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const override;
 
     //
     // Remaining methods are for retrieving information about attributes
@@ -169,9 +168,25 @@
     const std::u16string mEmpty;
     size_t mDepth;
     std::stack<std::u16string> mNamespaceUris;
-    std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+
+    struct PackageDecl {
+        std::u16string prefix;
+        ExtractedPackage package;
+    };
+    std::vector<PackageDecl> mPackageAliases;
 };
 
+/**
+ * Finds the attribute in the current element within the global namespace.
+ */
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
+/**
+ * Finds the attribute in the current element within the global namespace. The attribute's value
+ * must not be the empty string.
+ */
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
 //
 // Implementation
 //
@@ -277,6 +292,7 @@
     return endIter;
 }
 
+} // namespace xml
 } // namespace aapt
 
 #endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
similarity index 63%
rename from tools/aapt2/XmlPullParser_test.cpp
rename to tools/aapt2/xml/XmlPullParser_test.cpp
index 1c99a43..8fa2c6d 100644
--- a/tools/aapt2/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "util/StringPiece.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
 
 #include <gtest/gtest.h>
 #include <sstream>
@@ -26,30 +26,30 @@
     std::stringstream str;
     str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
             "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
-    XmlPullParser parser(str);
+    xml::XmlPullParser parser(str);
 
     const size_t depthOuter = parser.getDepth();
-    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
 
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
     EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
 
     const size_t depthA = parser.getDepth();
-    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
+    EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
     EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
 
     const size_t depthB = parser.getDepth();
-    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
     EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
 
-    ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
-    EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+    ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+    EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
     EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
 
-    ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
-    EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
+    ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+    EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
new file mode 100644
index 0000000..ab9f544
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.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 "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+    if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) {
+        StringPiece16 schemaPrefix = kSchemaPublicPrefix;
+        StringPiece16 package = namespaceUri;
+        package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+        if (package.empty()) {
+            return {};
+        }
+        return ExtractedPackage{ package.toString(), false /* isPrivate */ };
+
+    } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) {
+        StringPiece16 schemaPrefix = kSchemaPrivatePrefix;
+        StringPiece16 package = namespaceUri;
+        package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+        if (package.empty()) {
+            return {};
+        }
+        return ExtractedPackage{ package.toString(), true /* isPrivate */ };
+
+    } else if (namespaceUri == kSchemaAuto) {
+        return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+    }
+    return {};
+}
+
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+                                     const StringPiece16& localPackage, Reference* inRef) {
+    if (inRef->name) {
+        if (Maybe<ExtractedPackage> transformedPackage =
+                   declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
+            ExtractedPackage& extractedPackage = transformedPackage.value();
+            inRef->name.value().package = std::move(extractedPackage.package);
+
+            // If the reference was already private (with a * prefix) and the namespace is public,
+            // we keep the reference private.
+            inRef->privateReference |= extractedPackage.privateNamespace;
+        }
+    }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
new file mode 100644
index 0000000..98e5520
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_XML_XMLUTIL_H
+#define AAPT_XML_XMLUTIL_H
+
+#include "ResourceValues.h"
+#include "util/Maybe.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/";
+constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/";
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+/**
+ * Result of extracting a package name from a namespace URI declaration.
+ */
+struct ExtractedPackage {
+    /**
+     * The name of the package. This can be the empty string, which means that the package
+     * should be assumed to be the package being compiled.
+     */
+    std::u16string package;
+
+    /**
+     * True if the package's private namespace was declared. This means that private resources
+     * are made visible.
+     */
+    bool privateNamespace;
+};
+
+/**
+ * Returns an ExtractedPackage struct if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package> or
+ * http://schemas.android.com/apk/prv/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+/**
+ * Interface representing a stack of XML namespace declarations. When looking up the package
+ * for a namespace prefix, the stack is checked from top to bottom.
+ */
+struct IPackageDeclStack {
+    virtual ~IPackageDeclStack() = default;
+
+    /**
+     * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
+     */
+    virtual Maybe<ExtractedPackage> transformPackageAlias(
+            const StringPiece16& alias, const StringPiece16& localPackage) const = 0;
+};
+
+/**
+ * Helper function for transforming the original Reference inRef to a fully qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * the package declaration was private.
+ */
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+                                     const StringPiece16& localPackage, Reference* inRef);
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
new file mode 100644
index 0000000..7796b3e
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "test/Common.h"
+#include "xml/XmlUtil.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlUtilTest, ExtractPackageFromNamespace) {
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/"));
+    AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(
+            u"http://schemas.android.com/apk/prv/res/"));
+
+    Maybe<xml::ExtractedPackage> p =
+            xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"a"), p.value().package);
+    EXPECT_EQ(false, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"android"), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+
+    p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
+    AAPT_ASSERT_TRUE(p);
+    EXPECT_EQ(std::u16string(), p.value().package);
+    EXPECT_EQ(true, p.value().privateNamespace);
+}
+
+} // namespace aapt
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 61ddb04..53bfc15 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -30,6 +30,9 @@
 built_framework_dep := $(call java-lib-deps,framework)
 built_framework_classes := $(call java-lib-files,framework)
 
+built_oj_dep := $(call java-lib-deps,core-oj)
+built_oj_classes := $(call java-lib-files,core-oj)
+
 built_core_dep := $(call java-lib-deps,core-libart)
 built_core_classes := $(call java-lib-files,core-libart)
 
@@ -56,7 +59,8 @@
 include $(BUILD_SYSTEM)/base_rules.mk
 #######################################
 
-$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
+                       $(built_core_dep) \
                        $(built_framework_dep) \
                        $(built_ext_dep) \
                        $(built_ext_data) \
@@ -69,6 +73,7 @@
 	$(hide) ls -l $(built_framework_classes)
 	$(hide) java -ea -jar $(built_layoutlib_create_jar) \
 	             $@ \
+	             $(built_oj_classes) \
 	             $(built_core_classes) \
 	             $(built_framework_classes) \
 	             $(built_ext_classes) \
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 4436a40..5c73fb6a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -167,7 +167,7 @@
     }
 
     @Override
-    public void cancelDrag(IBinder dragToken) {
+    public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
         // pass for now
     }