Merge "Fix issue #25779155: Random crash in the system server"
diff --git a/api/current.txt b/api/current.txt
index 24cddd6..5048788 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4867,6 +4867,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
@@ -36283,6 +36284,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -36763,7 +36765,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);
diff --git a/api/system-current.txt b/api/system-current.txt
index 00d5745..ae0ac0c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4985,6 +4985,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
@@ -38606,6 +38607,7 @@
     method public boolean canResolveTextDirection();
     method public boolean canScrollHorizontally(int);
     method public boolean canScrollVertically(int);
+    method public final void cancelDragAndDrop();
     method public void cancelLongPress();
     method public final void cancelPendingInputEvents();
     method public boolean checkInputConnectionProxy(android.view.View);
@@ -39086,7 +39088,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);
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 94b434c..89e974e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3616,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
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 93a5503..c43fa9a 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -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/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/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 3fc70cc..b3cd8c11 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,6 +186,11 @@
 	void reportDropResult(IWindow window, boolean consumed);
 
     /**
+     * Cancel the current drag operation.
+     */
+    void cancelDragAndDrop(IBinder dragToken);
+
+    /**
      * Tell the OS that we've just dragged into a View that is willing to accept the drop
      */
     void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 227d8f2..66381f9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3696,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.
      *
@@ -3741,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;
@@ -19910,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)}
@@ -19926,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.
@@ -19948,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;
 
@@ -19970,11 +19980,11 @@
         }
         Surface surface = new Surface();
         try {
-            IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+            mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
                     flags, shadowSize.x, shadowSize.y, surface);
-            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
-                    + " surface=" + surface);
-            if (token != null) {
+            if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
+                    + mAttachInfo.mDragToken + " surface=" + surface);
+            if (mAttachInfo.mDragToken != null) {
                 Canvas canvas = surface.lockCanvas(null);
                 try {
                     canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19991,7 +20001,7 @@
                 // repurpose 'shadowSize' for the last touch point
                 root.getLastTouchPoint(shadowSize);
 
-                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
+                okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
                         shadowSize.x, shadowSize.y,
                         shadowTouchPoint.x, shadowTouchPoint.y, data);
                 if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -20009,6 +20019,39 @@
     }
 
     /**
+     * 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, "cancelDragAndDrop");
+        }
+        if (mAttachInfo.mDragToken != null) {
+            try {
+                mAttachInfo.mSession.cancelDragAndDrop(mAttachInfo.mDragToken);
+            } catch (Exception e) {
+                Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
+            }
+            mAttachInfo.mDragToken = null;
+        } else {
+            Log.e(VIEW_LOG_TAG, "No active drag to cancel");
+        }
+    }
+
+    /**
      * Starts a move from {startX, startY}, the amount of the movement will be the offset
      * between {startX, startY} and the new cursor positon.
      * @param startX horizontal coordinate where the move started.
@@ -20030,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
@@ -22293,6 +22337,11 @@
         final List<View> mPartialLayoutViews = new ArrayList<View>();
 
         /**
+         * Used to track the identity of the current drag operation.
+         */
+        IBinder mDragToken;
+
+        /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
          *
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5bbfc3f..2c0cd7a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5372,10 +5372,10 @@
                     }
                 }
 
-                // When the drag operation ends, release any local state object
-                // that may have been in use
+                // When the drag operation ends, reset drag-related state
                 if (what == DragEvent.ACTION_DRAG_ENDED) {
                     setLocalDragState(null);
+                    mAttachInfo.mDragToken = null;
                 }
             }
         }
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/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/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/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index b505ea0..59d5f94 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -18,86 +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.START;
     private boolean mForceShowIcon;
-    private boolean mShowTitle;
     private Callback mPresenterCallback;
 
-    private int mInitXOffset;
-    private int mInitYOffset;
+    private MenuPopup mPopup;
+    private OnDismissListener mOnDismissListener;
 
-    /** Whether the popup has anchor-relative offsets. */
-    private boolean mHasOffsets;
-
-    public MenuPopupHelper(Context context, MenuBuilder menu) {
+    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;
     }
@@ -114,7 +132,11 @@
         }
     }
 
-    public ShowableListMenu getPopup() {
+    @NonNull
+    public MenuPopup getPopup() {
+        if (mPopup == null) {
+            mPopup = createPopup();
+        }
         return mPopup;
     }
 
@@ -133,12 +155,7 @@
             return false;
         }
 
-        mInitXOffset = 0;
-        mInitYOffset = 0;
-        mHasOffsets = false;
-        mShowTitle = false;
-
-        showPopup();
+        showPopup(0, 0, false, false);
         return true;
     }
 
@@ -169,67 +186,104 @@
             return false;
         }
 
-        mInitXOffset = x;
-        mInitYOffset = y;
-        mHasOffsets = true;
-        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.setShowTitle(mShowTitle);
+    /**
+     * 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);
 
-        if (mHasOffsets) {
+        final MenuPopup popup;
+        if (enableCascadingSubmenus) {
+            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        } else {
+            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+                    mPopupStyleRes, mOverflowOnly);
+        }
+
+        // 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;
-            final int resolvedXOffset;
             if (hgrav == Gravity.RIGHT) {
-                resolvedXOffset = mInitXOffset - mAnchorView.getWidth();
-            } else {
-                resolvedXOffset = mInitXOffset;
+                xOffset -= mAnchorView.getWidth();
             }
-
-            mPopup.setHorizontalOffset(resolvedXOffset);
-            mPopup.setVerticalOffset(mInitYOffset);
         }
 
-        // 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);
-
-        mPopup.addMenu(mMenu);
-        mPopup.show();
+        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/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/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/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/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/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 057790a..539baa5 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1946,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 1e325b1..edd3555 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2321,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" />
@@ -2337,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/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/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 0a57d50..cc68fb2 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -37,6 +37,7 @@
     AnimatorManager.cpp \
     AssetAtlas.cpp \
     Caches.cpp \
+    Canvas.cpp \
     CanvasState.cpp \
     ClipArea.cpp \
     DamageAccumulator.cpp \
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/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index ec8048d..d76086c 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -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) {
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 22190f5..c23d47e 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -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
index 84230a7..dd6fc36 100644
--- a/libs/hwui/utils/TestUtils.cpp
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -36,5 +36,46 @@
             | (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/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h
index f7f4f2d..f9fa242 100644
--- a/libs/hwui/utils/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -196,6 +196,9 @@
 
     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/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/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/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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6d8bd1c..4b2a55f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -674,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 4f3544b..21a4206 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -617,6 +617,9 @@
     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;
@@ -13015,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);
         }
@@ -13090,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.
@@ -13512,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.
@@ -16686,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) {
@@ -16896,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.
@@ -17133,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/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1caeca0..01bdb7c 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -401,6 +401,34 @@
         }
     }
 
+    public void cancelDragAndDrop(IBinder dragToken) {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "cancelDragAndDrop");
+        }
+
+        synchronized (mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (mService.mDragState == null) {
+                    Slog.w(WindowManagerService.TAG, "cancelDragAndDrop() without prepareDrag()");
+                    throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()");
+                }
+
+                if (mService.mDragState.mToken != dragToken) {
+                    Slog.w(WindowManagerService.TAG,
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                    throw new IllegalStateException(
+                            "cancelDragAndDrop() does not match prepareDrag()");
+                }
+
+                mService.mDragState.mDragResult = false;
+                mService.mDragState.endDragLw();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     public void dragRecipientEntered(IWindow window) {
         if (WindowManagerService.DEBUG_DRAG) {
             Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 642def3..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;
@@ -416,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;
@@ -489,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;
@@ -674,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);
@@ -787,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 {
@@ -981,13 +988,17 @@
                     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);
@@ -1068,6 +1079,10 @@
             return LocalServices.getService(UserManagerInternal.class);
         }
 
+        PackageManagerInternal getPackageManagerInternal() {
+            return LocalServices.getService(PackageManagerInternal.class);
+        }
+
         NotificationManager getNotificationManager() {
             return mContext.getSystemService(NotificationManager.class);
         }
@@ -2101,6 +2116,14 @@
         new SetupContentObserver(mHandler).register(mContext.getContentResolver());
         // Initialize the user setup state, to handle the upgrade case.
         updateUserSetupComplete();
+
+        List<String> packageList;
+        synchronized (this) {
+            packageList = getKeepUninstalledPackagesLocked();
+        }
+        if (packageList != null) {
+            mInjector.getPackageManagerInternal().setKeepUninstalledPackages(packageList);
+        }
     }
 
     private void ensureDeviceOwnerUserStarted() {
@@ -4490,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;
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/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index f4fdc95..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);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
deleted file mode 100644
index 56fd351..0000000
--- a/services/tests/servicestests/src/com/android/server/pm/UserRestrictionsUtilsTest.java
+++ /dev/null
@@ -1,195 +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.server.pm;
-
-import com.android.server.devicepolicy.DpmTestUtils;
-
-import android.os.Bundle;
-import android.os.UserManager;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-/**
- * Tests for {@link com.android.server.pm.UserRestrictionsUtils}.
- *
- * <p>Run with:<pre>
-   m FrameworksServicesTests &&
-   adb install \
-     -r out/target/product/hammerhead/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
-   adb shell am instrument -e class com.android.server.pm.UserRestrictionsUtilsTest \
-     -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
- * </pre>
- */
-public class UserRestrictionsUtilsTest extends AndroidTestCase {
-    public void testNonNull() {
-        Bundle out = UserRestrictionsUtils.nonNull(null);
-        assertNotNull(out);
-        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
-
-        Bundle in = new Bundle();
-        assertSame(in, UserRestrictionsUtils.nonNull(in));
-    }
-
-    public void testIsEmpty() {
-        assertTrue(UserRestrictionsUtils.isEmpty(null));
-        assertTrue(UserRestrictionsUtils.isEmpty(new Bundle()));
-        assertFalse(UserRestrictionsUtils.isEmpty(DpmTestUtils.newRestrictions("a")));
-    }
-
-    public void testClone() {
-        Bundle in = new Bundle();
-        Bundle out = UserRestrictionsUtils.clone(in);
-        assertNotSame(in, out);
-        DpmTestUtils.assertRestrictions(out, new Bundle());
-
-        out = UserRestrictionsUtils.clone(null);
-        assertNotNull(out);
-        out.putBoolean("a", true); // Should not be Bundle.EMPTY.
-    }
-
-    public void testMerge() {
-        Bundle a = DpmTestUtils.newRestrictions("a", "d");
-        Bundle b = DpmTestUtils.newRestrictions("b", "d", "e");
-
-        UserRestrictionsUtils.merge(a, b);
-
-        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
-
-        UserRestrictionsUtils.merge(a, null);
-
-        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions("a", "b", "d", "e"), a);
-
-        try {
-            UserRestrictionsUtils.merge(a, a);
-            fail();
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    public void testIsSystemControlled() {
-        assertTrue(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_RECORD_AUDIO));
-        assertFalse(UserRestrictionsUtils.isSystemControlled(UserManager.DISALLOW_FUN));
-    }
-
-    public void testCanDeviceOwnerChange() {
-        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
-        assertFalse(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_WALLPAPER));
-        assertTrue(UserRestrictionsUtils.canDeviceOwnerChange(UserManager.DISALLOW_ADD_USER));
-    }
-
-    public void testCanProfileOwnerChange() {
-        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_RECORD_AUDIO));
-        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_WALLPAPER));
-        assertFalse(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADD_USER));
-        assertTrue(UserRestrictionsUtils.canProfileOwnerChange(UserManager.DISALLOW_ADJUST_VOLUME));
-    }
-
-    public void testSortToGlobalAndLocal() {
-        final Bundle local = new Bundle();
-        final Bundle global = new Bundle();
-
-        UserRestrictionsUtils.sortToGlobalAndLocal(null, global, local);
-        assertEquals(0, global.size());
-        assertEquals(0, local.size());
-
-        UserRestrictionsUtils.sortToGlobalAndLocal(Bundle.EMPTY, global, local);
-        assertEquals(0, global.size());
-        assertEquals(0, local.size());
-
-        UserRestrictionsUtils.sortToGlobalAndLocal(DpmTestUtils.newRestrictions(
-                UserManager.DISALLOW_ADJUST_VOLUME,
-                UserManager.DISALLOW_UNMUTE_MICROPHONE,
-                UserManager.DISALLOW_USB_FILE_TRANSFER,
-                UserManager.DISALLOW_CONFIG_TETHERING,
-                UserManager.DISALLOW_OUTGOING_BEAM,
-                UserManager.DISALLOW_APPS_CONTROL
-        ), global, local);
-
-
-        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
-                // These can be set by PO too, but when DO sets them, they're global.
-                UserManager.DISALLOW_ADJUST_VOLUME,
-                UserManager.DISALLOW_UNMUTE_MICROPHONE,
-
-                // These can only be set by DO.
-                UserManager.DISALLOW_USB_FILE_TRANSFER,
-                UserManager.DISALLOW_CONFIG_TETHERING
-        ), global);
-
-        DpmTestUtils.assertRestrictions(DpmTestUtils.newRestrictions(
-                // They can be set by both DO/PO.
-                UserManager.DISALLOW_OUTGOING_BEAM,
-                UserManager.DISALLOW_APPS_CONTROL
-        ), local);
-    }
-
-    public void testAreEqual() {
-        assertTrue(UserRestrictionsUtils.areEqual(
-                null,
-                null));
-
-        assertTrue(UserRestrictionsUtils.areEqual(
-                null,
-                Bundle.EMPTY));
-
-        assertTrue(UserRestrictionsUtils.areEqual(
-                Bundle.EMPTY,
-                null));
-
-        assertTrue(UserRestrictionsUtils.areEqual(
-                Bundle.EMPTY,
-                Bundle.EMPTY));
-
-        assertTrue(UserRestrictionsUtils.areEqual(
-                new Bundle(),
-                Bundle.EMPTY));
-
-        assertFalse(UserRestrictionsUtils.areEqual(
-                null,
-                DpmTestUtils.newRestrictions("a")));
-
-        assertFalse(UserRestrictionsUtils.areEqual(
-                DpmTestUtils.newRestrictions("a"),
-                null));
-
-        assertTrue(UserRestrictionsUtils.areEqual(
-                DpmTestUtils.newRestrictions("a"),
-                DpmTestUtils.newRestrictions("a")));
-
-        assertFalse(UserRestrictionsUtils.areEqual(
-                DpmTestUtils.newRestrictions("a"),
-                DpmTestUtils.newRestrictions("a", "b")));
-
-        assertFalse(UserRestrictionsUtils.areEqual(
-                DpmTestUtils.newRestrictions("a", "b"),
-                DpmTestUtils.newRestrictions("a")));
-
-        assertFalse(UserRestrictionsUtils.areEqual(
-                DpmTestUtils.newRestrictions("b", "a"),
-                DpmTestUtils.newRestrictions("a", "a")));
-
-        // Make sure false restrictions are handled correctly.
-        final Bundle a = DpmTestUtils.newRestrictions("a");
-        a.putBoolean("b", true);
-
-        final Bundle b = DpmTestUtils.newRestrictions("a");
-        b.putBoolean("b", false);
-
-        assertFalse(UserRestrictionsUtils.areEqual(a, b));
-        assertFalse(UserRestrictionsUtils.areEqual(b, a));
-    }
-}
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/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 1ec0547..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,6 +167,11 @@
     }
 
     @Override
+    public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
+        // pass for now
+    }
+
+    @Override
     public void dragRecipientEntered(IWindow window) throws RemoteException {
         // pass for now
     }