Fix bug #5243493 TextView selection is not working correctly when there is some RTL run into it

Part 2

- make selection handles aware of the run direction

Change-Id: Idf41036de53d8968e7ae27eb87aea09e86bcd652
diff --git a/api/current.txt b/api/current.txt
index d4acca9..235b814 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19932,6 +19932,7 @@
     method public abstract int getTopPadding();
     method public final int getWidth();
     method public final void increaseWidthTo(int);
+    method public boolean isRtlCharAt(int);
     method protected final boolean isSpanned();
     field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
     field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 768071f..bdfe940 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -673,6 +673,35 @@
         return false;
     }
 
+    /**
+     * Returns true if the character at offset is right to left (RTL).
+     * @param offset the offset
+     * @return true if the character is RTL, false if it is LTR
+     */
+    public boolean isRtlCharAt(int offset) {
+        int line = getLineForOffset(offset);
+        Directions dirs = getLineDirections(line);
+        if (dirs == DIRS_ALL_LEFT_TO_RIGHT) {
+            return false;
+        }
+        if (dirs == DIRS_ALL_RIGHT_TO_LEFT) {
+            return  true;
+        }
+        int[] runs = dirs.mDirections;
+        int lineStart = getLineStart(line);
+        for (int i = 0; i < runs.length; i += 2) {
+            int start = lineStart + (runs[i] & RUN_LENGTH_MASK);
+            // No need to test the end as an offset after the last run should return the value
+            // corresponding of the last run
+            if (offset >= start) {
+                int level = (runs[i+1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+                return ((level & 1) != 0);
+            }
+        }
+        // Should happen only if the offset is "out of bounds"
+        return false;
+    }
+
     private boolean primaryIsTrailingPrevious(int offset) {
         int line = getLineForOffset(offset);
         int lineStart = getLineStart(line);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index abf1281..0f30734 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10333,6 +10333,8 @@
 
     private abstract class HandleView extends View implements TextViewPositionListener {
         protected Drawable mDrawable;
+        protected Drawable mDrawableLtr;
+        protected Drawable mDrawableRtl;
         private final PopupWindow mContainer;
         // Position with respect to the parent TextView
         private int mPositionX, mPositionY;
@@ -10355,7 +10357,7 @@
         // Used to delay the appearance of the action popup window
         private Runnable mActionPopupShower;
 
-        public HandleView() {
+        public HandleView(Drawable drawableLtr, Drawable drawableRtl) {
             super(TextView.this.mContext);
             mContainer = new PopupWindow(TextView.this.mContext, null,
                     com.android.internal.R.attr.textSelectHandleWindowStyle);
@@ -10364,14 +10366,24 @@
             mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
             mContainer.setContentView(this);
 
-            initDrawable();
+            mDrawableLtr = drawableLtr;
+            mDrawableRtl = drawableRtl;
+
+            updateDrawable();
 
             final int handleHeight = mDrawable.getIntrinsicHeight();
             mTouchOffsetY = -0.3f * handleHeight;
             mIdealVerticalOffset = 0.7f * handleHeight;
         }
 
-        protected abstract void initDrawable();
+        protected void updateDrawable() {
+            final int offset = getCurrentCursorOffset();
+            final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset);
+            mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr;
+            mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset);
+        }
+
+        protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun);
 
         // Touch-up filter: number of previous positions remembered
         private static final int HISTORY_SIZE = 5;
@@ -10487,7 +10499,9 @@
 
         public abstract int getCurrentCursorOffset();
 
-        public abstract void updateSelection(int offset);
+        protected void updateSelection(int offset) {
+            updateDrawable();
+        }
 
         public abstract void updatePosition(float x, float y);
 
@@ -10629,6 +10643,10 @@
         private float mDownPositionX, mDownPositionY;
         private Runnable mHider;
 
+        public InsertionHandleView(Drawable drawable) {
+            super(drawable, drawable);
+        }
+
         @Override
         public void show() {
             super.show();
@@ -10665,13 +10683,8 @@
         }
 
         @Override
-        protected void initDrawable() {
-            if (mSelectHandleCenter == null) {
-                mSelectHandleCenter = mContext.getResources().getDrawable(
-                        mTextSelectHandleRes);
-            }
-            mDrawable = mSelectHandleCenter;
-            mHotspotX = mDrawable.getIntrinsicWidth() / 2;
+        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
+            return drawable.getIntrinsicWidth() / 2;
         }
 
         @Override
@@ -10741,14 +10754,18 @@
     }
 
     private class SelectionStartHandleView extends HandleView {
+
+        public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) {
+            super(drawableLtr, drawableRtl);
+        }
+
         @Override
-        protected void initDrawable() {
-            if (mSelectHandleLeft == null) {
-                mSelectHandleLeft = mContext.getResources().getDrawable(
-                        mTextSelectHandleLeftRes);
+        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
+            if (isRtlRun) {
+                return drawable.getIntrinsicWidth() / 4;
+            } else {
+                return (drawable.getIntrinsicWidth() * 3) / 4;
             }
-            mDrawable = mSelectHandleLeft;
-            mHotspotX = (mDrawable.getIntrinsicWidth() * 3) / 4;
         }
 
         @Override
@@ -10758,6 +10775,7 @@
 
         @Override
         public void updateSelection(int offset) {
+            super.updateSelection(offset);
             Selection.setSelection((Spannable) mText, offset, getSelectionEnd());
         }
 
@@ -10778,14 +10796,18 @@
     }
 
     private class SelectionEndHandleView extends HandleView {
+
+        public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) {
+            super(drawableLtr, drawableRtl);
+        }
+
         @Override
-        protected void initDrawable() {
-            if (mSelectHandleRight == null) {
-                mSelectHandleRight = mContext.getResources().getDrawable(
-                        mTextSelectHandleRightRes);
+        protected int getHotspotX(Drawable drawable, boolean isRtlRun) {
+            if (isRtlRun) {
+                return (drawable.getIntrinsicWidth() * 3) / 4;
+            } else {
+                return drawable.getIntrinsicWidth() / 4;
             }
-            mDrawable = mSelectHandleRight;
-            mHotspotX = mDrawable.getIntrinsicWidth() / 4;
         }
 
         @Override
@@ -10795,6 +10817,7 @@
 
         @Override
         public void updateSelection(int offset) {
+            super.updateSelection(offset);
             Selection.setSelection((Spannable) mText, getSelectionStart(), offset);
         }
 
@@ -10864,8 +10887,12 @@
         }
 
         private InsertionHandleView getHandle() {
+            if (mSelectHandleCenter == null) {
+                mSelectHandleCenter = mContext.getResources().getDrawable(
+                        mTextSelectHandleRes);
+            }
             if (mHandle == null) {
-                mHandle = new InsertionHandleView();
+                mHandle = new InsertionHandleView(mSelectHandleCenter);
             }
             return mHandle;
         }
@@ -10899,10 +10926,30 @@
             if (isInBatchEditMode()) {
                 return;
             }
+            initDrawables();
+            initHandles();
+            hideInsertionPointCursorController();
+        }
 
+        private void initDrawables() {
+            if (mSelectHandleLeft == null) {
+                mSelectHandleLeft = mContext.getResources().getDrawable(
+                        mTextSelectHandleLeftRes);
+            }
+            if (mSelectHandleRight == null) {
+                mSelectHandleRight = mContext.getResources().getDrawable(
+                        mTextSelectHandleRightRes);
+            }
+        }
+
+        private void initHandles() {
             // Lazy object creation has to be done before updatePosition() is called.
-            if (mStartHandle == null) mStartHandle = new SelectionStartHandleView();
-            if (mEndHandle == null) mEndHandle = new SelectionEndHandleView();
+            if (mStartHandle == null) {
+                mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
+            }
+            if (mEndHandle == null) {
+                mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
+            }
 
             mStartHandle.show();
             mEndHandle.show();