Merge "Add device document to MtpDatabase."
diff --git a/api/current.txt b/api/current.txt
index f5ccf0e..df6c473a3 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40337,6 +40337,38 @@
     field public static final int ORIENTATION_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class PointerIcon implements android.os.Parcelable {
+    method public static android.view.PointerIcon createCustomIcon(android.graphics.Bitmap, float, float);
+    method public int describeContents();
+    method public static android.view.PointerIcon getSystemIcon(android.content.Context, int);
+    method public static android.view.PointerIcon loadCustomIcon(android.content.res.Resources, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.PointerIcon> CREATOR;
+    field public static final int STYLE_ALIAS = 1010; // 0x3f2
+    field public static final int STYLE_ALL_SCROLL = 1013; // 0x3f5
+    field public static final int STYLE_ARROW = 1000; // 0x3e8
+    field public static final int STYLE_CELL = 1006; // 0x3ee
+    field public static final int STYLE_CONTEXT_MENU = 1001; // 0x3e9
+    field public static final int STYLE_COPY = 1011; // 0x3f3
+    field public static final int STYLE_CROSSHAIR = 1007; // 0x3ef
+    field public static final int STYLE_DEFAULT = 1000; // 0x3e8
+    field public static final int STYLE_GRAB = 1020; // 0x3fc
+    field public static final int STYLE_GRABBING = 1021; // 0x3fd
+    field public static final int STYLE_HAND = 1002; // 0x3ea
+    field public static final int STYLE_HELP = 1003; // 0x3eb
+    field public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+    field public static final int STYLE_NO_DROP = 1012; // 0x3f4
+    field public static final int STYLE_NULL = 0; // 0x0
+    field public static final int STYLE_TEXT = 1008; // 0x3f0
+    field public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+    field public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+    field public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+    field public static final int STYLE_VERTICAL_TEXT = 1009; // 0x3f1
+    field public static final int STYLE_WAIT = 1004; // 0x3ec
+    field public static final int STYLE_ZOOM_IN = 1018; // 0x3fa
+    field public static final int STYLE_ZOOM_OUT = 1019; // 0x3fb
+  }
+
   public class ScaleGestureDetector {
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler);
@@ -40696,6 +40728,7 @@
     method public android.view.ViewParent getParentForAccessibility();
     method public float getPivotX();
     method public float getPivotY();
+    method public android.view.PointerIcon getPointerIcon(android.view.MotionEvent, float, float);
     method public android.content.res.Resources getResources();
     method public final int getRight();
     method protected float getRightFadingEdgeStrength();
@@ -40978,6 +41011,7 @@
     method public void setPaddingRelative(int, int, int, int);
     method public void setPivotX(float);
     method public void setPivotY(float);
+    method public void setPointerIcon(android.view.PointerIcon);
     method public void setPressed(boolean);
     method public final void setRight(int);
     method public void setRotation(float);
diff --git a/api/system-current.txt b/api/system-current.txt
index bc63b48..ee975d1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -42675,6 +42675,38 @@
     field public static final int ORIENTATION_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class PointerIcon implements android.os.Parcelable {
+    method public static android.view.PointerIcon createCustomIcon(android.graphics.Bitmap, float, float);
+    method public int describeContents();
+    method public static android.view.PointerIcon getSystemIcon(android.content.Context, int);
+    method public static android.view.PointerIcon loadCustomIcon(android.content.res.Resources, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.PointerIcon> CREATOR;
+    field public static final int STYLE_ALIAS = 1010; // 0x3f2
+    field public static final int STYLE_ALL_SCROLL = 1013; // 0x3f5
+    field public static final int STYLE_ARROW = 1000; // 0x3e8
+    field public static final int STYLE_CELL = 1006; // 0x3ee
+    field public static final int STYLE_CONTEXT_MENU = 1001; // 0x3e9
+    field public static final int STYLE_COPY = 1011; // 0x3f3
+    field public static final int STYLE_CROSSHAIR = 1007; // 0x3ef
+    field public static final int STYLE_DEFAULT = 1000; // 0x3e8
+    field public static final int STYLE_GRAB = 1020; // 0x3fc
+    field public static final int STYLE_GRABBING = 1021; // 0x3fd
+    field public static final int STYLE_HAND = 1002; // 0x3ea
+    field public static final int STYLE_HELP = 1003; // 0x3eb
+    field public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+    field public static final int STYLE_NO_DROP = 1012; // 0x3f4
+    field public static final int STYLE_NULL = 0; // 0x0
+    field public static final int STYLE_TEXT = 1008; // 0x3f0
+    field public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+    field public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+    field public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+    field public static final int STYLE_VERTICAL_TEXT = 1009; // 0x3f1
+    field public static final int STYLE_WAIT = 1004; // 0x3ec
+    field public static final int STYLE_ZOOM_IN = 1018; // 0x3fa
+    field public static final int STYLE_ZOOM_OUT = 1019; // 0x3fb
+  }
+
   public class ScaleGestureDetector {
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler);
@@ -43034,6 +43066,7 @@
     method public android.view.ViewParent getParentForAccessibility();
     method public float getPivotX();
     method public float getPivotY();
+    method public android.view.PointerIcon getPointerIcon(android.view.MotionEvent, float, float);
     method public android.content.res.Resources getResources();
     method public final int getRight();
     method protected float getRightFadingEdgeStrength();
@@ -43316,6 +43349,7 @@
     method public void setPaddingRelative(int, int, int, int);
     method public void setPivotX(float);
     method public void setPivotY(float);
+    method public void setPointerIcon(android.view.PointerIcon);
     method public void setPressed(boolean);
     method public final void setRight(int);
     method public void setRotation(float);
diff --git a/api/test-current.txt b/api/test-current.txt
index a222037..c494895 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -40339,6 +40339,38 @@
     field public static final int ORIENTATION_UNKNOWN = -1; // 0xffffffff
   }
 
+  public final class PointerIcon implements android.os.Parcelable {
+    method public static android.view.PointerIcon createCustomIcon(android.graphics.Bitmap, float, float);
+    method public int describeContents();
+    method public static android.view.PointerIcon getSystemIcon(android.content.Context, int);
+    method public static android.view.PointerIcon loadCustomIcon(android.content.res.Resources, int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.view.PointerIcon> CREATOR;
+    field public static final int STYLE_ALIAS = 1010; // 0x3f2
+    field public static final int STYLE_ALL_SCROLL = 1013; // 0x3f5
+    field public static final int STYLE_ARROW = 1000; // 0x3e8
+    field public static final int STYLE_CELL = 1006; // 0x3ee
+    field public static final int STYLE_CONTEXT_MENU = 1001; // 0x3e9
+    field public static final int STYLE_COPY = 1011; // 0x3f3
+    field public static final int STYLE_CROSSHAIR = 1007; // 0x3ef
+    field public static final int STYLE_DEFAULT = 1000; // 0x3e8
+    field public static final int STYLE_GRAB = 1020; // 0x3fc
+    field public static final int STYLE_GRABBING = 1021; // 0x3fd
+    field public static final int STYLE_HAND = 1002; // 0x3ea
+    field public static final int STYLE_HELP = 1003; // 0x3eb
+    field public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+    field public static final int STYLE_NO_DROP = 1012; // 0x3f4
+    field public static final int STYLE_NULL = 0; // 0x0
+    field public static final int STYLE_TEXT = 1008; // 0x3f0
+    field public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+    field public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+    field public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+    field public static final int STYLE_VERTICAL_TEXT = 1009; // 0x3f1
+    field public static final int STYLE_WAIT = 1004; // 0x3ec
+    field public static final int STYLE_ZOOM_IN = 1018; // 0x3fa
+    field public static final int STYLE_ZOOM_OUT = 1019; // 0x3fb
+  }
+
   public class ScaleGestureDetector {
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener);
     ctor public ScaleGestureDetector(android.content.Context, android.view.ScaleGestureDetector.OnScaleGestureListener, android.os.Handler);
@@ -40698,6 +40730,7 @@
     method public android.view.ViewParent getParentForAccessibility();
     method public float getPivotX();
     method public float getPivotY();
+    method public android.view.PointerIcon getPointerIcon(android.view.MotionEvent, float, float);
     method public android.content.res.Resources getResources();
     method public final int getRight();
     method protected float getRightFadingEdgeStrength();
@@ -40980,6 +41013,7 @@
     method public void setPaddingRelative(int, int, int, int);
     method public void setPivotX(float);
     method public void setPivotY(float);
+    method public void setPointerIcon(android.view.PointerIcon);
     method public void setPressed(boolean);
     method public final void setRight(int);
     method public void setRotation(float);
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 174291e..ff33bd9 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.PointerIcon;
 
 /** @hide */
 interface IInputManager {
@@ -71,4 +72,5 @@
     void cancelVibrate(int deviceId, IBinder token);
 
     void setPointerIconShape(int shapeId);
+    void setCustomPointerIcon(in PointerIcon icon);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 201afee..fab4718 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware.input;
 
+import android.view.PointerIcon;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 
@@ -819,6 +820,15 @@
         }
     }
 
+    /** @hide */
+    public void setCustomPointerIcon(PointerIcon icon) {
+        try {
+            mIm.setCustomPointerIcon(icon);
+        } catch (RemoteException ex) {
+            // Do nothing.
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 46b9a46..88cd7ca 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -784,6 +784,15 @@
     }
 
     /**
+     * Specifies the current custom pointer.
+     * @param icon the icon data.
+     * @hide
+     */
+    public void setCustomPointerIcon(PointerIcon icon) {
+        InputManager.getInstance().setCustomPointerIcon(icon);
+    }
+
+    /**
      * Provides information about the range of values for a particular {@link MotionEvent} axis.
      *
      * @see InputDevice#getMotionRange(int)
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index d2a7d4a..aa879cd 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -16,8 +16,10 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.SparseArray;
 import com.android.internal.util.XmlUtils;
 
 import android.annotation.XmlRes;
@@ -39,13 +41,11 @@
  * Pointer icons can be provided either by the system using system styles,
  * or by applications using bitmaps or application resources.
  * </p>
- *
- * @hide
  */
 public final class PointerIcon implements Parcelable {
     private static final String TAG = "PointerIcon";
 
-    /** Style constant: Custom icon with a user-supplied bitmap. */
+    /** {@hide} Style constant: Custom icon with a user-supplied bitmap. */
     public static final int STYLE_CUSTOM = -1;
 
     /** Style constant: Null icon.  It has no bitmap. */
@@ -54,6 +54,7 @@
     /** Style constant: no icons are specified. If all views uses this, then falls back
      * to the default style, but this is helpful to distinguish a view explicitly want
      * to have the default icon.
+     * @hide
      */
     public static final int STYLE_NOT_SPECIFIED = 1;
 
@@ -135,10 +136,11 @@
     // conflicts with any system styles that may be defined in the future.
     private static final int STYLE_OEM_FIRST = 10000;
 
-    /** {@hide} The default pointer icon. */
+    /** The default pointer icon. */
     public static final int STYLE_DEFAULT = STYLE_ARROW;
 
     private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+    private static final SparseArray<PointerIcon> gSystemIcons = new SparseArray<PointerIcon>();
 
     private final int mStyle;
     private int mSystemIconResourceId;
@@ -160,6 +162,7 @@
      * @return The null pointer icon.
      *
      * @see #STYLE_NULL
+     * @hide
      */
     public static PointerIcon getNullIcon() {
         return gNullIcon;
@@ -172,8 +175,9 @@
      * @return The default pointer icon.
      *
      * @throws IllegalArgumentException if context is null.
+     * @hide
      */
-    public static PointerIcon getDefaultIcon(Context context) {
+    public static PointerIcon getDefaultIcon(@NonNull Context context) {
         return getSystemIcon(context, STYLE_DEFAULT);
     }
 
@@ -187,7 +191,7 @@
      *
      * @throws IllegalArgumentException if context is null.
      */
-    public static PointerIcon getSystemIcon(Context context, int style) {
+    public static PointerIcon getSystemIcon(@NonNull Context context, int style) {
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
@@ -196,6 +200,11 @@
             return gNullIcon;
         }
 
+        PointerIcon icon = gSystemIcons.get(style);
+        if (icon != null) {
+            return icon;
+        }
+
         int styleIndex = getSystemIconStyleIndex(style);
         if (styleIndex == 0) {
             styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
@@ -217,12 +226,13 @@
             return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
         }
 
-        PointerIcon icon = new PointerIcon(style);
+        icon = new PointerIcon(style);
         if ((resourceId & 0xff000000) == 0x01000000) {
             icon.mSystemIconResourceId = resourceId;
         } else {
             icon.loadResource(context, context.getResources(), resourceId);
         }
+        gSystemIcons.append(style, icon);
         return icon;
     }
 
@@ -239,7 +249,8 @@
      * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
      *         parameters are invalid.
      */
-    public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+    public static PointerIcon createCustomIcon(
+            @NonNull Bitmap bitmap, float hotSpotX, float hotSpotY) {
         if (bitmap == null) {
             throw new IllegalArgumentException("bitmap must not be null");
         }
@@ -273,7 +284,7 @@
      * @throws Resources.NotFoundException if the resource was not found or the drawable
      * linked in the resource was not found.
      */
-    public static PointerIcon loadCustomIcon(Resources resources, @XmlRes int resourceId) {
+    public static PointerIcon loadCustomIcon(@NonNull Resources resources, @XmlRes int resourceId) {
         if (resources == null) {
             throw new IllegalArgumentException("resources must not be null");
         }
@@ -291,10 +302,9 @@
      * @return The loaded pointer icon.
      *
      * @throws IllegalArgumentException if context is null.
-     * @see #isLoaded()
      * @hide
      */
-    public PointerIcon load(Context context) {
+    public PointerIcon load(@NonNull Context context) {
         if (context == null) {
             throw new IllegalArgumentException("context must not be null");
         }
@@ -309,83 +319,11 @@
         return result;
     }
 
-    /**
-     * Returns true if the pointer icon style is {@link #STYLE_NULL}.
-     *
-     * @return True if the pointer icon style is {@link #STYLE_NULL}.
-     */
-    public boolean isNullIcon() {
-        return mStyle == STYLE_NULL;
-    }
-
-    /**
-     * Returns true if the pointer icon has been loaded and its bitmap and hotspot
-     * information are available.
-     *
-     * @return True if the pointer icon is loaded.
-     * @see #load(Context)
-     */
-    public boolean isLoaded() {
-        return mBitmap != null || mStyle == STYLE_NULL;
-    }
-
-    /**
-     * Gets the style of the pointer icon.
-     *
-     * @return The pointer icon style.
-     */
+    /** @hide */
     public int getStyle() {
         return mStyle;
     }
 
-    /**
-     * Gets the bitmap of the pointer icon.
-     *
-     * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
-     *
-     * @throws IllegalStateException if the bitmap is not loaded.
-     * @see #isLoaded()
-     * @see #load(Context)
-     */
-    public Bitmap getBitmap() {
-        throwIfIconIsNotLoaded();
-        return mBitmap;
-    }
-
-    /**
-     * Gets the X offset of the pointer icon hotspot.
-     *
-     * @return The hotspot X offset.
-     *
-     * @throws IllegalStateException if the bitmap is not loaded.
-     * @see #isLoaded()
-     * @see #load(Context)
-     */
-    public float getHotSpotX() {
-        throwIfIconIsNotLoaded();
-        return mHotSpotX;
-    }
-
-    /**
-     * Gets the Y offset of the pointer icon hotspot.
-     *
-     * @return The hotspot Y offset.
-     *
-     * @throws IllegalStateException if the bitmap is not loaded.
-     * @see #isLoaded()
-     * @see #load(Context)
-     */
-    public float getHotSpotY() {
-        throwIfIconIsNotLoaded();
-        return mHotSpotY;
-    }
-
-    private void throwIfIconIsNotLoaded() {
-        if (!isLoaded()) {
-            throw new IllegalStateException("The icon is not loaded.");
-        }
-    }
-
     public static final Parcelable.Creator<PointerIcon> CREATOR
             = new Parcelable.Creator<PointerIcon>() {
         public PointerIcon createFromParcel(Parcel in) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3db18066..2368642 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2628,7 +2628,7 @@
     static final int PFLAG3_POINTER_ICON_LSHIFT = 15;
 
     /**
-     * Value indicating {@link PointerIcon.STYLE_NOT_SPECIFIED}.
+     * Value indicating no specific pointer icons.
      */
     private static final int PFLAG3_POINTER_ICON_NOT_SPECIFIED = 0 << PFLAG3_POINTER_ICON_LSHIFT;
 
@@ -2638,14 +2638,9 @@
     private static final int PFLAG3_POINTER_ICON_NULL = 1 << PFLAG3_POINTER_ICON_LSHIFT;
 
     /**
-     * Value incicating {@link PointerIcon.STYLE_CUSTOM}.
-     */
-    private static final int PFLAG3_POINTER_ICON_CUSTOM = 2 << PFLAG3_POINTER_ICON_LSHIFT;
-
-    /**
      * The base value for other pointer icon shapes.
      */
-    private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
+    private static final int PFLAG3_POINTER_ICON_VALUE_START = 2 << PFLAG3_POINTER_ICON_LSHIFT;
 
     /**
      * Always allow a user to over-scroll this view, provided it is a
@@ -3927,6 +3922,11 @@
     private HandlerActionQueue mRunQueue;
 
     /**
+     * The pointer icon when the mouse hovers on this view. The default is null.
+     */
+    private PointerIcon mPointerIcon;
+
+    /**
      * @hide
      */
     String mStartActivityRequestWho;
@@ -4490,7 +4490,7 @@
                 case R.styleable.View_pointerShape:
                     final int pointerShape = a.getInt(attr, PointerIcon.STYLE_NOT_SPECIFIED);
                     if (pointerShape != PointerIcon.STYLE_NOT_SPECIFIED) {
-                        setPointerShape(pointerShape);
+                        setPointerIcon(PointerIcon.getSystemIcon(context, pointerShape));
                     }
                     break;
             }
@@ -21191,42 +21191,25 @@
         }
     }
 
-    /** @hide */
-    public int getPointerShape(MotionEvent event, float x, float y) {
-        final int value = (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK);
-        switch (value) {
-            case PFLAG3_POINTER_ICON_NOT_SPECIFIED:
-                return PointerIcon.STYLE_NOT_SPECIFIED;
-            case PFLAG3_POINTER_ICON_NULL:
-                return PointerIcon.STYLE_NULL;
-            case PFLAG3_POINTER_ICON_CUSTOM:
-                return PointerIcon.STYLE_CUSTOM;
-            default:
-                return ((value - PFLAG3_POINTER_ICON_VALUE_START) >> PFLAG3_POINTER_ICON_LSHIFT)
-                        + PointerIcon.STYLE_ARROW;
-        }
+    /**
+     * Returns the pointer icon for the motion event, or null if it doesn't specify the icon.
+     * The default implementation does not care the location or event types, but some subclasses
+     * may use it (such as WebViews).
+     * @param event The MotionEvent from a mouse
+     * @param x The x position of the event, local to the view
+     * @param y The y position of the event, local to the view
+     * @see PointerIcon
+     */
+    public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
+        return mPointerIcon;
     }
 
-    /** @hide */
-    public void setPointerShape(int pointerShape) {
-        int newValue;
-        if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) {
-            newValue = PFLAG3_POINTER_ICON_NOT_SPECIFIED;
-        } else if (pointerShape == PointerIcon.STYLE_NULL) {
-            newValue = PFLAG3_POINTER_ICON_NULL;
-        } else if (pointerShape == PointerIcon.STYLE_CUSTOM) {
-            newValue = PFLAG3_POINTER_ICON_CUSTOM;
-        } else if (pointerShape >= PointerIcon.STYLE_ARROW
-                && pointerShape <= PointerIcon.STYLE_GRABBING) {
-            newValue = ((pointerShape - PointerIcon.STYLE_ARROW) << PFLAG3_POINTER_ICON_LSHIFT)
-                    + PFLAG3_POINTER_ICON_VALUE_START;
-        } else {
-            Log.w(VIEW_LOG_TAG, "Invalid pointer shape " + pointerShape + " is specified.");
-            return;
-        }
-        if (newValue != (mPrivateFlags3 & PFLAG3_POINTER_ICON_MASK)) {
-            mPrivateFlags3 = (mPrivateFlags3 & ~PFLAG3_POINTER_ICON_MASK) | newValue;
-        }
+    /**
+     * Set the pointer icon for the current view.
+     * @param pointerIcon A PointerIcon instance which will be shown when the mouse hovers.
+     */
+    public void setPointerIcon(PointerIcon pointerIcon) {
+        mPointerIcon = pointerIcon;
     }
 
     //
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0f7d296..cd93dab 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1715,9 +1715,8 @@
         return false;
     }
 
-    /** @hide */
     @Override
-    public int getPointerShape(MotionEvent event, float x, float y) {
+    public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
         // Check what the child under the pointer says about the pointer.
         final int childrenCount = mChildrenCount;
         if (childrenCount != 0) {
@@ -1731,9 +1730,9 @@
                         ? children[childIndex] : preorderedList.get(childIndex);
                 PointF point = getLocalPoint();
                 if (isTransformedTouchPointInView(x, y, child, point)) {
-                    final int pointerShape = child.getPointerShape(event, point.x, point.y);
-                    if (pointerShape != PointerIcon.STYLE_NOT_SPECIFIED) {
-                        return pointerShape;
+                    final PointerIcon pointerIcon = child.getPointerIcon(event, point.x, point.y);
+                    if (pointerIcon != null) {
+                        return pointerIcon;
                     }
                     break;
                 }
@@ -1742,7 +1741,7 @@
 
         // The pointer is not a child or the child has no preferences, returning the default
         // implementation.
-        return super.getPointerShape(event, x, y);
+        return super.getPointerIcon(event, x, y);
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 12cf66e..faf2640 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -332,6 +332,7 @@
     private int mFpsNumFrames;
 
     private int mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED;
+    private PointerIcon mCustomPointerIcon = null;
 
     /**
      * see {@link #playSoundEffect(int)}
@@ -4210,16 +4211,23 @@
                 final float y = event.getY();
                 if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT
                         && x >= 0 && x < mView.getWidth() && y >= 0 && y < mView.getHeight()) {
-                    int pointerShape = mView.getPointerShape(event, x, y);
-                    if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) {
-                        pointerShape = PointerIcon.STYLE_DEFAULT;
-                    }
+                    PointerIcon pointerIcon = mView.getPointerIcon(event, x, y);
+                    int pointerShape = (pointerIcon != null) ?
+                            pointerIcon.getStyle() : PointerIcon.STYLE_DEFAULT;
 
-                    if (mPointerIconShape != pointerShape) {
-                        mPointerIconShape = pointerShape;
-                        final InputDevice inputDevice = event.getDevice();
-                        if (inputDevice != null) {
-                            inputDevice.setPointerShape(pointerShape);
+                    final InputDevice inputDevice = event.getDevice();
+                    if (inputDevice != null) {
+                        if (mPointerIconShape != pointerShape) {
+                            mPointerIconShape = pointerShape;
+                            if (mPointerIconShape != PointerIcon.STYLE_CUSTOM) {
+                                mCustomPointerIcon = null;
+                                inputDevice.setPointerShape(pointerShape);
+                            }
+                        }
+                        if (mPointerIconShape == PointerIcon.STYLE_CUSTOM &&
+                                !pointerIcon.equals(mCustomPointerIcon)) {
+                            mCustomPointerIcon = pointerIcon;
+                            inputDevice.setCustomPointerIcon(mCustomPointerIcon);
                         }
                     }
                 } else if (event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d6bc27c..2e884cc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1171,6 +1171,16 @@
          */
         public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
 
+        /**
+         * Flag to indicate that this window is not expected to be replaced across
+         * configuration change triggered activity relaunches. In general the WindowManager
+         * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
+         * until the replacement is ready to show in order to prevent visual glitch. However
+         * some windows, such as PopupWindows expect to be cleared across configuration change,
+         * and thus should hint to the WindowManager that it should not wait for a replacement.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
 
         /**
          * Control flags that are private to the platform.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7b9de79..f4c343a 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,6 +16,8 @@
 
 package android.widget;
 
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
 import com.android.internal.R;
 
 import android.annotation.NonNull;
@@ -1311,6 +1313,8 @@
             p.width = mLastWidth = mWidth;
         }
 
+        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
         // Used for debugging.
         p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5574f86..17c803f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5955,15 +5955,12 @@
         return mLayout != null ? mLayout.getHeight() : 0;
     }
 
-    /**
-     * @hide
-     */
     @Override
-    public int getPointerShape(MotionEvent event, float x, float y) {
+    public PointerIcon getPointerIcon(MotionEvent event, float x, float y) {
         if (isTextSelectable() || isTextEditable()) {
-            return PointerIcon.STYLE_TEXT;
+            return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_TEXT);
         }
-        return super.getPointerShape(event, x, y);
+        return super.getPointerIcon(event, x, y);
     }
 
     @Override
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
index d7e2c02..71be52e 100644
--- a/core/jni/android_view_PointerIcon.cpp
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -65,33 +65,34 @@
         return OK;
     }
 
-    jobject loadedPointerIconObj = env->CallObjectMethod(pointerIconObj,
-            gPointerIconClassInfo.load, contextObj);
-    if (env->ExceptionCheck() || !loadedPointerIconObj) {
+    ScopedLocalRef<jobject> loadedPointerIconObj(env, env->CallObjectMethod(pointerIconObj,
+            gPointerIconClassInfo.load, contextObj));
+    if (env->ExceptionCheck() || !loadedPointerIconObj.get()) {
         ALOGW("An exception occurred while loading a pointer icon.");
         LOGW_EX(env);
         env->ExceptionClear();
         return UNKNOWN_ERROR;
     }
+    return android_view_PointerIcon_getLoadedIcon(env, loadedPointerIconObj.get(), outPointerIcon);
+}
 
-    outPointerIcon->style = env->GetIntField(loadedPointerIconObj,
-            gPointerIconClassInfo.mStyle);
-    outPointerIcon->hotSpotX = env->GetFloatField(loadedPointerIconObj,
-            gPointerIconClassInfo.mHotSpotX);
-    outPointerIcon->hotSpotY = env->GetFloatField(loadedPointerIconObj,
-            gPointerIconClassInfo.mHotSpotY);
+status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIconObj,
+        PointerIcon* outPointerIcon) {
+    outPointerIcon->style = env->GetIntField(pointerIconObj, gPointerIconClassInfo.mStyle);
+    outPointerIcon->hotSpotX = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotX);
+    outPointerIcon->hotSpotY = env->GetFloatField(pointerIconObj, gPointerIconClassInfo.mHotSpotY);
 
-    jobject bitmapObj = env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmap);
-    if (bitmapObj) {
-        GraphicsJNI::getSkBitmap(env, bitmapObj, &(outPointerIcon->bitmap));
-        env->DeleteLocalRef(bitmapObj);
+    ScopedLocalRef<jobject> bitmapObj(
+            env, env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmap));
+    if (bitmapObj.get()) {
+        GraphicsJNI::getSkBitmap(env, bitmapObj.get(), &(outPointerIcon->bitmap));
     }
 
     ScopedLocalRef<jobjectArray> bitmapFramesObj(env, reinterpret_cast<jobjectArray>(
-            env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmapFrames)));
+            env->GetObjectField(pointerIconObj, gPointerIconClassInfo.mBitmapFrames)));
     if (bitmapFramesObj.get()) {
         outPointerIcon->durationPerFrame = env->GetIntField(
-                loadedPointerIconObj, gPointerIconClassInfo.mDurationPerFrame);
+                pointerIconObj, gPointerIconClassInfo.mDurationPerFrame);
         jsize size = env->GetArrayLength(bitmapFramesObj.get());
         outPointerIcon->bitmapFrames.resize(size);
         for (jsize i = 0; i < size; ++i) {
@@ -100,7 +101,6 @@
         }
     }
 
-    env->DeleteLocalRef(loadedPointerIconObj);
     return OK;
 }
 
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
index ca08085..00bdfb4 100644
--- a/core/jni/android_view_PointerIcon.h
+++ b/core/jni/android_view_PointerIcon.h
@@ -97,6 +97,11 @@
 extern status_t android_view_PointerIcon_load(JNIEnv* env,
         jobject pointerIconObj, jobject contextObj, PointerIcon* outPointerIcon);
 
+/* Obtain the data of pointerIconObj and put to outPointerIcon. */
+extern status_t android_view_PointerIcon_getLoadedIcon(JNIEnv* env, jobject pointerIconObj,
+        PointerIcon* outPointerIcon);
+
+
 /* Loads the bitmap associated with a pointer icon by style.
  * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
 extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env,
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 6a1167a..529849e 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -439,6 +439,17 @@
     }
 }
 
+void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+    AutoMutex _l(mLock);
+
+    const int32_t iconId = mPolicy->getCustomPointerIconId();
+    mLocked.additionalMouseResources[iconId] = icon;
+    mLocked.requestedPointerShape = iconId;
+    mLocked.presentationChanged = true;
+
+    updatePointerLocked();
+}
+
 void PointerController::handleMessage(const Message& message) {
     switch (message.what) {
     case MSG_INACTIVITY_TIMEOUT:
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 4fd2d85..9ba37b3 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -67,6 +67,7 @@
     virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
             std::map<int32_t, PointerAnimation>* outAnimationResources) = 0;
     virtual int32_t getDefaultPointerIconId() = 0;
+    virtual int32_t getCustomPointerIconId() = 0;
 };
 
 
@@ -105,6 +106,7 @@
     virtual void clearSpots();
 
     void updatePointerShape(int32_t iconId);
+    void setCustomPointerIcon(const SpriteIcon& icon);
     void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
     void setInactivityTimeout(InactivityTimeout inactivityTimeout);
     void reloadPointerResources();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 44ff079..6d947d1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -28,6 +28,7 @@
 import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
@@ -592,9 +593,26 @@
 
         IOException copyError = null;
         try {
-            srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller);
+            // If the file is virtual, but can be converted to another format, then try to copy it
+            // as such format.
+            if (srcInfo.isVirtualDocument() && srcInfo.isTypedDocument()) {
+                final String[] streamTypes = mSrcClient.getStreamTypes(srcInfo.derivedUri, "*/*");
+                if (streamTypes.length > 0) {
+                    // Pick the first streamable format.
+                    final AssetFileDescriptor srcFileAsAsset =
+                            mSrcClient.openTypedAssetFileDescriptor(
+                                    srcInfo.derivedUri, streamTypes[0], null, canceller);
+                    srcFile = srcFileAsAsset.getParcelFileDescriptor();
+                    src = new AssetFileDescriptor.AutoCloseInputStream(srcFileAsAsset);
+                } else {
+                    // TODO: Log failures. b/26192412
+                    mFailedFiles.add(srcInfo);
+                }
+            } else {
+                srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller);
+                src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
+            }
             dstFile = mDstClient.openFile(dstInfo.derivedUri, "w", canceller);
-            src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
             dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
 
             byte[] buffer = new byte[8192];
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 69b574d..215c6e6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -251,6 +251,14 @@
         return isDirectory() || isArchive();
     }
 
+    public boolean isVirtualDocument() {
+        return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
+    }
+
+    public boolean isTypedDocument() {
+        return (flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) != 0;
+    }
+
     public int hashCode() {
         return derivedUri.hashCode() + mimeType.hashCode();
     }
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index ebbc5e1..6d57c5b 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -27,7 +27,9 @@
 
 import java.io.FileNotFoundException;
 
+import static android.provider.DocumentsContract.Document.*;
 import static com.android.mtp.MtpDatabase.strings;
+import static com.android.mtp.MtpDatabaseConstants.*;
 
 @SmallTest
 public class MtpDatabaseTest extends AndroidTestCase {
@@ -60,6 +62,18 @@
         mDatabase = null;
     }
 
+    private static int getInt(Cursor cursor, String columnName) {
+        return cursor.getInt(cursor.getColumnIndex(columnName));
+    }
+
+    private static boolean isNull(Cursor cursor, String columnName) {
+        return cursor.isNull(cursor.getColumnIndex(columnName));
+    }
+
+    private static String getString(Cursor cursor, String columnName) {
+        return cursor.getString(cursor.getColumnIndex(columnName));
+    }
+
     public void testPutRootDocuments() throws Exception {
         mDatabase.getMapper().startAddingDocuments("deviceDocId");
         mDatabase.getMapper().putRootDocuments("deviceDocId", resources, new MtpRoot[] {
@@ -73,27 +87,27 @@
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("deviceId", 0, cursor.getInt(1));
-            assertEquals("storageId", 1, cursor.getInt(2));
-            assertTrue("objectHandle", cursor.isNull(3));
-            assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(4));
-            assertEquals("displayName", "Device Storage", cursor.getString(5));
-            assertTrue("summary", cursor.isNull(6));
-            assertTrue("lastModified", cursor.isNull(7));
-            assertEquals("icon", R.drawable.ic_root_mtp, cursor.getInt(8));
-            assertEquals("flag", 0, cursor.getInt(9));
-            assertEquals("size", 1000, cursor.getInt(10));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
+            assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+            assertTrue(isNull(cursor, COLUMN_SUMMARY));
+            assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
+            assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
+            assertEquals(0, getInt(cursor, COLUMN_FLAGS));
+            assertEquals(1000, getInt(cursor, COLUMN_SIZE));
             assertEquals(
-                    "documentType", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, cursor.getInt(11));
+                    MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertEquals("displayName", "Device Storage", cursor.getString(5));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals("documentId", 3, cursor.getInt(0));
-            assertEquals("displayName", "Device /@#%&<>Storage", cursor.getString(5));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals("Device /@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
         }
@@ -166,61 +180,61 @@
         assertEquals(3, cursor.getCount());
 
         cursor.moveToNext();
-        assertEquals("documentId", 1, cursor.getInt(0));
-        assertEquals("deviceId", 0, cursor.getInt(1));
-        assertEquals("storageId", 0, cursor.getInt(2));
-        assertEquals("objectHandle", 100, cursor.getInt(3));
-        assertEquals("mimeType", "text/plain", cursor.getString(4));
-        assertEquals("displayName", "note.txt", cursor.getString(5));
-        assertTrue("summary", cursor.isNull(6));
-        assertTrue("lastModified", cursor.isNull(7));
-        assertTrue("icon", cursor.isNull(8));
+        assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
+        assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
+        assertEquals(100, getInt(cursor, COLUMN_OBJECT_HANDLE));
+        assertEquals("text/plain", getString(cursor, COLUMN_MIME_TYPE));
+        assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
+        assertTrue(isNull(cursor, COLUMN_SUMMARY));
+        assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
+        assertTrue(isNull(cursor, COLUMN_ICON));
         assertEquals(
-                "flag",
+                COLUMN_FLAGS,
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                 cursor.getInt(9));
-        assertEquals("size", 1024, cursor.getInt(10));
+        assertEquals(1024, getInt(cursor, COLUMN_SIZE));
         assertEquals(
-                "documentType", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, cursor.getInt(11));
+                MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
         cursor.moveToNext();
-        assertEquals("documentId", 2, cursor.getInt(0));
-        assertEquals("deviceId", 0, cursor.getInt(1));
-        assertEquals("storageId", 0, cursor.getInt(2));
-        assertEquals("objectHandle", 101, cursor.getInt(3));
-        assertEquals("mimeType", "image/jpeg", cursor.getString(4));
-        assertEquals("displayName", "image.jpg", cursor.getString(5));
-        assertTrue("summary", cursor.isNull(6));
-        assertTrue("lastModified", cursor.isNull(7));
-        assertTrue("icon", cursor.isNull(8));
+        assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
+        assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
+        assertEquals(101, getInt(cursor, COLUMN_OBJECT_HANDLE));
+        assertEquals("image/jpeg", getString(cursor, COLUMN_MIME_TYPE));
+        assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
+        assertTrue(isNull(cursor, COLUMN_SUMMARY));
+        assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
+        assertTrue(isNull(cursor, COLUMN_ICON));
         assertEquals(
-                "flag",
+                COLUMN_FLAGS,
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                 cursor.getInt(9));
-        assertEquals("size", 2 * 1024 * 1024, cursor.getInt(10));
+        assertEquals(2 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
         assertEquals(
-                "documentType", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, cursor.getInt(11));
+                MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
         cursor.moveToNext();
-        assertEquals("documentId", 3, cursor.getInt(0));
-        assertEquals("deviceId", 0, cursor.getInt(1));
-        assertEquals("storageId", 0, cursor.getInt(2));
-        assertEquals("objectHandle", 102, cursor.getInt(3));
-        assertEquals("mimeType", "audio/mpeg", cursor.getString(4));
-        assertEquals("displayName", "music.mp3", cursor.getString(5));
-        assertTrue("summary", cursor.isNull(6));
-        assertTrue("lastModified", cursor.isNull(7));
-        assertTrue("icon", cursor.isNull(8));
+        assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
+        assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
+        assertEquals(102, getInt(cursor, COLUMN_OBJECT_HANDLE));
+        assertEquals("audio/mpeg", getString(cursor, COLUMN_MIME_TYPE));
+        assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
+        assertTrue(isNull(cursor, COLUMN_SUMMARY));
+        assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
+        assertTrue(isNull(cursor, COLUMN_ICON));
         assertEquals(
-                "flag",
+                COLUMN_FLAGS,
                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                 cursor.getInt(9));
-        assertEquals("size", 3 * 1024 * 1024, cursor.getInt(10));
+        assertEquals(3 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
         assertEquals(
-                "documentType", MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, cursor.getInt(11));
+                MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
         cursor.close();
     }
@@ -246,13 +260,13 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 100, cursor.getInt(1));
-            assertEquals("name", "Device Storage A", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertEquals("storageId", 101, cursor.getInt(1));
-            assertEquals("name", "Device Storage B", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -260,11 +274,11 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
 
@@ -274,13 +288,13 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertTrue("storageId", cursor.isNull(1));
-            assertEquals("name", "Device Storage A", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertTrue("storageId", cursor.isNull(1));
-            assertEquals("name", "Device Storage B", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -288,11 +302,11 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
 
@@ -306,17 +320,17 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertTrue("storageId", cursor.isNull(1));
-            assertEquals("name", "Device Storage A", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertTrue("storageId", cursor.isNull(1));
-            assertEquals("name", "Device Storage B", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 4, cursor.getInt(0));
-            assertEquals("storageId", 202, cursor.getInt(1));
-            assertEquals("name", "Device Storage C", cursor.getString(2));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -324,14 +338,14 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 1001, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 4, cursor.getInt(0));
-            assertEquals("availableBytes", 2002, cursor.getInt(1));
+            assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
 
@@ -341,13 +355,13 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 200, cursor.getInt(1));
-            assertEquals("name", "Device Storage A", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 4, cursor.getInt(0));
-            assertEquals("storageId", 202, cursor.getInt(1));
-            assertEquals("name", "Device Storage C", cursor.getString(2));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -355,11 +369,11 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 4, cursor.getInt(0));
-            assertEquals("availableBytes", 2002, cursor.getInt(1));
+            assertEquals(4, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2002, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
     }
@@ -383,19 +397,19 @@
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertTrue("objectHandle", cursor.isNull(1));
-            assertEquals("name", "note.txt", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertTrue("objectHandle", cursor.isNull(1));
-            assertEquals("name", "image.jpg", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals("documentId", 3, cursor.getInt(0));
-            assertTrue("objectHandle", cursor.isNull(1));
-            assertEquals("name", "music.mp3", cursor.getString(2));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
         }
@@ -411,9 +425,9 @@
             assertEquals(4, cursor.getCount());
 
             cursor.moveToPosition(3);
-            assertEquals("documentId", 5, cursor.getInt(0));
-            assertEquals("objectHandle", 203, cursor.getInt(1));
-            assertEquals("name", "video.mp4", cursor.getString(2));
+            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
         }
@@ -425,14 +439,14 @@
             assertEquals(2, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("objectHandle", 200, cursor.getInt(1));
-            assertEquals("name", "note.txt", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals("documentId", 5, cursor.getInt(0));
-            assertEquals("objectHandle", 203, cursor.getInt(1));
-            assertEquals("name", "video.mp4", cursor.getString(2));
+            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
     }
@@ -460,13 +474,13 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 100, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertEquals("storageId", 100, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -474,11 +488,11 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 0, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 0, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
 
@@ -499,13 +513,13 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 200, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertEquals("storageId", 300, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
@@ -513,11 +527,11 @@
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 3000, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
     }
@@ -552,16 +566,16 @@
             final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("objectHandle", 200, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
             cursor.close();
         }
         {
             final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertTrue("objectHandle", cursor.isNull(1));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             cursor.close();
         }
     }
@@ -599,17 +613,17 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 300, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
         {
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 3000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
     }
@@ -642,24 +656,24 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 2, cursor.getInt(0));
-            assertEquals("storageId", 200, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals("documentId", 3, cursor.getInt(0));
-            assertEquals("storageId", 201, cursor.getInt(1));
-            assertEquals("name", "Device Storage", cursor.getString(2));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
         {
             final Cursor cursor = mDatabase.queryRoots(rootColumns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 2, cursor.getInt(0));
-            assertEquals("availableBytes", 2000, cursor.getInt(1));
+            assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.moveToNext();
-            assertEquals("rootId", 3, cursor.getInt(0));
-            assertEquals("availableBytes", 2001, cursor.getInt(1));
+            assertEquals(3, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(2001, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
     }
@@ -687,9 +701,9 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("documentId", 1, cursor.getInt(0));
-            assertEquals("storageId", 100, cursor.getInt(1));
-            assertEquals("name", "Device Storage B", cursor.getString(2));
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
+            assertEquals("Device Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
         {
@@ -700,8 +714,8 @@
             final Cursor cursor = mDatabase.queryRoots(columns);
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals("rootId", 1, cursor.getInt(0));
-            assertEquals("availableBytes", 1000, cursor.getInt(1));
+            assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
+            assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
             cursor.close();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 93264ff..c01f170 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -30,6 +30,7 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.View.OnTouchListener;
@@ -118,8 +119,8 @@
         updateDisplayInfo();
         boolean landscape = getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE;
-        mHandle.setPointerShape(
-                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW);
+        mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
+                landscape ? STYLE_HORIZONTAL_DOUBLE_ARROW : STYLE_VERTICAL_DOUBLE_ARROW));
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f2d0031..8b37383 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -213,6 +213,7 @@
     private static native void nativeMonitor(long ptr);
     private static native void nativeSetPointerIconShape(long ptr, int iconId);
     private static native void nativeReloadPointerIcons(long ptr);
+    private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
 
     // Input event injection constants defined in InputDispatcher.h.
     private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1451,6 +1452,12 @@
         nativeSetPointerIconShape(mPtr, iconId);
     }
 
+    // Binder call
+    @Override
+    public void setCustomPointerIcon(PointerIcon icon) {
+        nativeSetCustomPointerIcon(mPtr, icon);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index fd5c704..b49641f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -122,21 +123,6 @@
     // True if the windows associated with this token should be cropped to their stack bounds.
     boolean mCropWindowsToStack;
 
-    // This application will have its window replaced due to relaunch. This allows window manager
-    // to differentiate between simple removal of a window and replacement. In the latter case it
-    // will preserve the old window until the new one is drawn.
-    boolean mWillReplaceWindow;
-    // If true, the replaced window was already requested to be removed.
-    boolean mReplacingRemoveRequested;
-    // Whether the replacement of the window should trigger app transition animation.
-    boolean mAnimateReplacingWindow;
-    // If not null, the window that will be used to replace the old one. This is being set when
-    // the window is added and unset when this window reports its first draw.
-    WindowState mReplacingWindow;
-    // Whether the new window has replaced the old one, so the old one can be removed without
-    // blinking.
-    boolean mHasReplacedWindow;
-
     AppWindowToken(WindowManagerService _service, IApplicationToken _token,
             boolean _voiceInteraction) {
         super(_service, _token.asBinder(),
@@ -392,6 +378,62 @@
         }
     }
 
+    void setReplacingWindows(boolean animate) {
+        if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
+                + " with replacing windows.");
+
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState w = allAppWindows.get(i);
+            w.setReplacing(animate);
+        }
+        if (animate) {
+            // Set-up dummy animation so we can start treating windows associated with this
+            // token like they are in transition before the new app window is ready for us to
+            // run the real transition animation.
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+                    "setReplacingWindow() Setting dummy animation on: " + this);
+            mAppAnimator.setDummyAnimation();
+        }
+    }
+
+    void addWindow(WindowState w) {
+        for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow && candidate.mReplacingWindow == null &&
+                    candidate.getWindowTag().equals(w.getWindowTag().toString())) {
+                candidate.mReplacingWindow = w;
+            }
+        }
+        allAppWindows.add(w);
+    }
+
+    boolean waitingForReplacement() {
+        for (int i = allAppWindows.size() -1; i >= 0; i--) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void clearTimedoutReplaceesLocked() {
+        for (int i = allAppWindows.size() - 1; i >= 0;
+             // removeWindowLocked at bottom of loop may remove multiple entries from
+             // allAppWindows if the window to be removed has child windows. It also may
+             // not remove any windows from allAppWindows at all if win is exiting and
+             // currently animating away. This ensures that winNdx is monotonically decreasing
+             // and never beyond allAppWindows bounds.
+             i = Math.min(i - 1, allAppWindows.size() - 1)) {
+            WindowState candidate = allAppWindows.get(i);
+            if (candidate.mWillReplaceWindow == false) {
+                continue;
+            }
+            candidate.mWillReplaceWindow = false;
+            service.removeWindowLocked(candidate);
+        }
+    }
+
     @Override
     void dump(PrintWriter pw, String prefix) {
         super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3db9ae0..2e424d0 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -506,7 +506,7 @@
                 inFreeformSpace = stack != null && stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
             }
 
-            replacing = replacing || (w.mAppToken != null && w.mAppToken.mWillReplaceWindow);
+            replacing = replacing || w.mWillReplaceWindow;
 
             // If the app is executing an animation because the keyguard is going away,
             // keep the wallpaper during the animation so it doesn't flicker out.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a6523a4..6a5183f 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -918,10 +918,6 @@
     }
 
     void requestRemovalOfReplacedWindows(WindowState win) {
-        final AppWindowToken token = win.mAppToken;
-        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == win) {
-            token.mHasReplacedWindow = true;
-        }
         mRemoveReplacedWindows = true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4190bd77..456c416 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -269,6 +269,9 @@
     /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
     static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
 
+    /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
+    static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
+
     /** Amount of time to allow a last ANR message to exist before freeing the memory. */
     static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
     /**
@@ -1350,10 +1353,7 @@
         final AppWindowToken appToken = win.mAppToken;
         if (appToken != null) {
             if (addToToken) {
-                appToken.allAppWindows.add(win);
-            }
-            if (appToken.mWillReplaceWindow) {
-                appToken.mReplacingWindow = win;
+                appToken.addWindow(win);
             }
         }
     }
@@ -2088,14 +2088,15 @@
     }
 
     private void prepareWindowReplacementTransition(AppWindowToken atoken) {
-        if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+        if (atoken == null) {
             return;
         }
         atoken.allDrawn = false;
         WindowState replacedWindow = null;
         for (int i = atoken.windows.size() - 1; i >= 0 && replacedWindow == null; i--) {
             WindowState candidate = atoken.windows.get(i);
-            if (candidate.mExiting) {
+            if (candidate.mExiting && candidate.mWillReplaceWindow
+                    && candidate.mAnimateReplacingWindow) {
                 replacedWindow = candidate;
             }
         }
@@ -2195,7 +2196,7 @@
                 + " app-animation="
                 + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
                 + " mWillReplaceWindow="
-                + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
+                + win.mWillReplaceWindow
                 + " inPendingTransaction="
                 + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
                 + " mDisplayFrozen=" + mDisplayFrozen);
@@ -2207,13 +2208,13 @@
         // animation wouldn't be seen.
         if (win.mHasSurface && okToDisplay()) {
             final AppWindowToken appToken = win.mAppToken;
-            if (appToken != null && appToken.mWillReplaceWindow) {
+            if (win.mWillReplaceWindow) {
                 // This window is going to be replaced. We need to keep it around until the new one
                 // gets added, then we will get rid of this one.
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Preserving " + win + " until the new one is "
                         + "added");
                 win.mExiting = true;
-                appToken.mReplacingRemoveRequested = true;
+                win.mReplacingRemoveRequested = true;
                 Binder.restoreCallingIdentity(origId);
                 return;
             }
@@ -4052,7 +4053,7 @@
         // transition animation
         // * or this is an opening app and windows are being replaced.
         if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
-                (visible && wtoken.mWillReplaceWindow)) {
+                (visible && wtoken.waitingForReplacement())) {
             boolean changed = false;
             if (DEBUG_APP_TRANSITIONS) Slog.v(
                 TAG_WM, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -7514,6 +7515,8 @@
         public static final int TWO_FINGER_SCROLL_START = 45;
         public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
 
+        public static final int WINDOW_REPLACEMENT_TIMEOUT = 47;
+
         /**
          * Used to denote that an integer field in a message will not be used.
          */
@@ -8096,6 +8099,13 @@
                     toast.show();
                 }
                 break;
+                case WINDOW_REPLACEMENT_TIMEOUT: {
+                    final AppWindowToken token = (AppWindowToken) msg.obj;
+                    synchronized (mWindowMap) {
+                        token.clearTimedoutReplaceesLocked();
+                    }
+                }
+                break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
@@ -8618,7 +8628,7 @@
                 final int numTokens = tokens.size();
                 for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
                     final AppWindowToken wtoken = tokens.get(tokenNdx);
-                    if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
+                    if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) {
                         continue;
                     }
                     i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8727,8 +8737,8 @@
     private void forceHigherLayerIfNeeded(WindowState w, WindowStateAnimator winAnimator,
             AppWindowToken wtoken) {
         boolean force = false;
-        if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w
-                && wtoken.mAnimateReplacingWindow) {
+
+        if (w.mWillReplaceWindow) {
             // We know that we will be animating a relaunching window in the near future,
             // which will receive a z-order increase. We want the replaced window to
             // immediately receive the same treatment, e.g. to be above the dock divider.
@@ -10244,26 +10254,20 @@
      * @param token Application token for which the activity will be relaunched.
      */
     public void setReplacingWindow(IBinder token, boolean animate) {
+        AppWindowToken appWindowToken = null;
         synchronized (mWindowMap) {
-            AppWindowToken appWindowToken = findAppWindowToken(token);
+            appWindowToken = findAppWindowToken(token);
             if (appWindowToken == null || !appWindowToken.isVisible()) {
                 Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
                 return;
             }
-            if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
-                    + " as replacing window.");
-            appWindowToken.mWillReplaceWindow = true;
-            appWindowToken.mHasReplacedWindow = false;
-            appWindowToken.mAnimateReplacingWindow = animate;
+            appWindowToken.setReplacingWindows(animate);
+        }
 
-            if (animate) {
-                // Set-up dummy animation so we can start treating windows associated with this
-                // token like they are in transition before the new app window is ready for us to
-                // run the real transition animation.
-                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
-                        "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
-                appWindowToken.mAppAnimator.setDummyAnimation();
-            }
+        if (appWindowToken != null) {
+            mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+            mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
+                    WINDOW_REPLACEMENT_TIMEOUT_DURATION);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5a589e3..e4a6806 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -75,6 +75,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -403,6 +404,18 @@
     // used to start an entering animation earlier.
     public boolean mSurfaceSaved = false;
 
+    // This window will be replaced due to relaunch. This allows window manager
+    // to differentiate between simple removal of a window and replacement. In the latter case it
+    // will preserve the old window until the new one is drawn.
+    boolean mWillReplaceWindow = false;
+    // If true, the replaced window was already requested to be removed.
+    boolean mReplacingRemoveRequested = false;
+    // Whether the replacement of the window should trigger app transition animation.
+    boolean mAnimateReplacingWindow = false;
+    // If not null, the window that will be used to replace the old one. This is being set when
+    // the window is added and unset when this window reports its first draw.
+    WindowState mReplacingWindow = null;
+
     /**
      * Wake lock for drawing.
      * Even though it's slightly more expensive to do so, we will use a separate wake lock
@@ -580,8 +593,7 @@
     @Override
     public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
             Rect osf) {
-        if (mAppToken != null && mAppToken.mWillReplaceWindow
-                && (mExiting || !mAppToken.mReplacingRemoveRequested)) {
+        if (mWillReplaceWindow && (mExiting || !mReplacingRemoveRequested)) {
             // This window is being replaced and either already got information that it's being
             // removed or we are still waiting for some information. Because of this we don't
             // want to apply any more changes to it, so it remains in this state until new window
@@ -1343,17 +1355,17 @@
     }
 
     void maybeRemoveReplacedWindow() {
-        AppWindowToken token = mAppToken;
-        if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this
-                && token.mHasReplacedWindow) {
-            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
-            token.mWillReplaceWindow = false;
-            token.mAnimateReplacingWindow = false;
-            token.mReplacingRemoveRequested = false;
-            token.mReplacingWindow = null;
-            token.mHasReplacedWindow = false;
-            for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
-                final WindowState win = token.allAppWindows.get(i);
+        if (mAppToken == null) {
+            return;
+        }
+        for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) {
+            final WindowState win = mAppToken.allAppWindows.get(i);
+            if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win);
+            if (win.mWillReplaceWindow && win.mReplacingWindow == this) {
+                win.mWillReplaceWindow = false;
+                win.mAnimateReplacingWindow = false;
+                win.mReplacingRemoveRequested = false;
+                win.mReplacingWindow = null;
                 if (win.mExiting) {
                     mService.removeWindowInnerLocked(win);
                 }
@@ -2161,7 +2173,7 @@
             + " " + getWindowTag();
     }
 
-    private CharSequence getWindowTag() {
+    CharSequence getWindowTag() {
         CharSequence tag = mAttrs.getTitle();
         if (tag == null || tag.length() <= 0) {
             tag = mAttrs.packageName;
@@ -2259,4 +2271,12 @@
     boolean isChildWindow() {
         return mAttachedWindow != null;
     }
+
+    void setReplacing(boolean animate) {
+        if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) == 0) {
+            mWillReplaceWindow = true;
+            mReplacingWindow = null;
+            mAnimateReplacingWindow = animate;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index cd82a5f..d4001cd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1192,7 +1192,7 @@
         // different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
         // We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
         // The window that will replace it will abide them.
-        if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace()
+        if (isAnimating() && (w.mWillReplaceWindow || w.inDockedWorkspace()
                 || w.inFreeformWorkspace())) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 6001321..160c97f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1193,7 +1193,7 @@
                     // if app window is removed, or window relayout to invisible. We don't want to
                     // clear it out for windows that get replaced, because the animation depends on
                     // the flag to remove the replaced window.
-                    if (win.mAppToken == null || !win.mAppToken.mWillReplaceWindow) {
+                    if (!win.mWillReplaceWindow) {
                         win.mExiting = false;
                     }
                     if (win.mWinAnimator.mAnimLayer > layer) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9c9a5da..4c8474a 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -206,6 +206,7 @@
     void reloadCalibration();
     void setPointerIconShape(int32_t iconId);
     void reloadPointerIcons();
+    void setCustomPointerIcon(const SpriteIcon& icon);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -248,6 +249,7 @@
     virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources,
             std::map<int32_t, PointerAnimation>* outAnimationResources);
     virtual int32_t getDefaultPointerIconId();
+    virtual int32_t getCustomPointerIconId();
 
 private:
     sp<InputManager> mInputManager;
@@ -790,6 +792,14 @@
     }
 }
 
+void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) {
+    AutoMutex _l(mLock);
+    sp<PointerController> controller = mLocked.pointerController.promote();
+    if (controller != NULL) {
+        controller->setCustomPointerIcon(icon);
+    }
+}
+
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
         JNIEnv *env, jfloatArray matrixArr) {
     ScopedFloatArrayRO matrix(env, matrixArr);
@@ -1090,6 +1100,10 @@
     return POINTER_ICON_STYLE_ARROW;
 }
 
+int32_t NativeInputManager::getCustomPointerIconId() {
+    return POINTER_ICON_STYLE_CUSTOM;
+}
+
 // ----------------------------------------------------------------------------
 
 static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
@@ -1437,6 +1451,20 @@
     im->reloadPointerIcons();
 }
 
+static void nativeSetCustomPointerIcon(JNIEnv* env, jclass /* clazz */,
+                                       jlong ptr, jobject iconObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    PointerIcon pointerIcon;
+    android_view_PointerIcon_getLoadedIcon(env, iconObj, &pointerIcon);
+
+    SpriteIcon spriteIcon;
+    pointerIcon.bitmap.copyTo(&spriteIcon.bitmap, kN32_SkColorType);
+    spriteIcon.hotSpotX = pointerIcon.hotSpotX;
+    spriteIcon.hotSpotY = pointerIcon.hotSpotY;
+    im->setCustomPointerIcon(spriteIcon);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -1499,6 +1527,8 @@
             (void*) nativeSetPointerIconShape },
     { "nativeReloadPointerIcons", "(J)V",
             (void*) nativeReloadPointerIcons },
+    { "nativeSetCustomPointerIcon", "(JLandroid/view/PointerIcon;)V",
+            (void*) nativeSetCustomPointerIcon },
 };
 
 #define FIND_CLASS(var, className) \