TextView cursor and selection improvements.

Insertion cursor handle no longer appears on empty text views (Bug 3075988).

Tapping on an unfocused TextView moves the insertion point at tapped position.

Bug fixes for trackball initiated text selection.

Change-Id: Ief246fd9a9f1eb745dcf9f0605e2ce53b5563f01
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d1974dc..44b14e7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6549,12 +6549,9 @@
             int selEnd = getSelectionEnd();
 
             if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+                // If a tap was used to give focus to that view, move cursor at tap position.
                 // Has to be done before onTakeFocus, which can be overloaded.
-                if (mLastTouchOffset >= 0) {
-                    // Can happen when a TextView is displayed after its content has been deleted.
-                    mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
-                    Selection.setSelection((Spannable) mText, mLastTouchOffset);
-                }
+                moveCursorToLastTapPosition();
 
                 if (mMovement != null) {
                     mMovement.onTakeFocus(this, (Spannable) mText, direction);
@@ -6613,8 +6610,6 @@
             } else {
                 terminateTextSelectionMode();
             }
-
-            mLastTouchOffset = -1;
         }
 
         startStopMarquee(focused);
@@ -6626,6 +6621,22 @@
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
     }
 
+    private void moveCursorToLastTapPosition() {
+        if (mSelectionModifierCursorController != null) {
+            int mTapToFocusPosition = ((SelectionModifierCursorController)
+                    mSelectionModifierCursorController).getMinTouchOffset();
+            if (mTapToFocusPosition >= 0) {
+                // Safety check, should not be possible.
+                if (mTapToFocusPosition > mText.length()) {
+                    Log.e(LOG_TAG, "Invalid tap focus position (" + mTapToFocusPosition + " vs "
+                            + mText.length() + ")");
+                    mTapToFocusPosition = mText.length();
+                }
+                Selection.setSelection((Spannable) mText, mTapToFocusPosition);
+            }
+        }
+    }
+
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
@@ -6717,7 +6728,7 @@
                 // Tapping outside stops selection mode, if any
                 stopTextSelectionMode();
 
-                if (mInsertionPointCursorController != null) {
+                if (mInsertionPointCursorController != null && mText.length() > 0) {
                     mInsertionPointCursorController.show();
                 }
             }
@@ -7255,7 +7266,7 @@
 
         int minOffset, maxOffset;
 
-        if (mDPadCenterIsDown || mEnterKeyIsDown) {
+        if (mContextMenuTriggeredByKey) {
             minOffset = getSelectionStart();
             maxOffset = getSelectionEnd();
         } else {
@@ -7286,6 +7297,9 @@
     }
     
     private String getWordForDictionary() {
+        if (!mContextMenuTriggeredByKey) {
+            moveCursorToLastTapPosition();
+        }
         long wordLimits = getWordLimitsAt(getSelectionStart());
         if (wordLimits >= 0) {
             int start = extractRangeStartFromLong(wordLimits);
@@ -7336,6 +7350,14 @@
     protected void onCreateContextMenu(ContextMenu menu) {
         super.onCreateContextMenu(menu);
         boolean added = false;
+        mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
+        // Problem with context menu on long press: the menu appears while the key in down and when
+        // the key is released, the view does not receive the key_up event. This ensures that the
+        // state is reset whenever the context menu action is displayed.
+        // mContextMenuTriggeredByKey saved that state so that it is available in
+        // onTextContextMenuItem. We cannot simply clear these flags in onTextContextMenuItem since
+        // it may not be called (if the user/ discards the context menu with the back key).
+        mDPadCenterIsDown = mEnterKeyIsDown = false;
 
         if (mIsInTextSelectionMode) {
             MenuHandler handler = new MenuHandler();
@@ -7361,21 +7383,6 @@
                 added = true;
             }
         } else {
-            /*
-            if (!isFocused()) {
-                if (isFocusable() && mInput != null) {
-                    if (canCopy()) {
-                        MenuHandler handler = new MenuHandler();
-                        menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
-                             setOnMenuItemClickListener(handler).
-                             setAlphabeticShortcut('c');
-                        menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
-                    }
-                }
-
-                //return;
-            }
-             */
             MenuHandler handler = new MenuHandler();
 
             if (canSelectText()) {
@@ -7529,7 +7536,6 @@
 
             case ID_ADD_TO_DICTIONARY:
                 String word = getWordForDictionary();
-
                 if (word != null) {
                     Intent i = new Intent("com.android.settings.USER_DICTIONARY_INSERT");
                     i.putExtra("word", word);
@@ -8013,6 +8019,7 @@
         SelectionModifierCursorController() {
             mStartHandle = new HandleView(this, HandleView.LEFT);
             mEndHandle = new HandleView(this, HandleView.RIGHT);
+            mMinTouchOffset = mMaxTouchOffset = -1;
         }
 
         public void show() {
@@ -8095,14 +8102,16 @@
         }
 
         public boolean onTouchEvent(MotionEvent event) {
-            if (isFocused() && isTextEditable()) {
+            // This is done even when the View does not have focus, so that long presses can start
+            // selection and tap can move cursor from this tap position.
+            if (isTextEditable()) {
                 switch (event.getActionMasked()) {
                     case MotionEvent.ACTION_DOWN:
                         final int x = (int) event.getX();
                         final int y = (int) event.getY();
 
                         // Remember finger down position, to be able to start selection from there
-                        mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
+                        mMinTouchOffset = mMaxTouchOffset = getOffset(x, y);
 
                         break;
 
@@ -8259,11 +8268,11 @@
     private CursorController        mInsertionPointCursorController;
     private CursorController        mSelectionModifierCursorController;
     private boolean                 mIsInTextSelectionMode = false;
-    private int                     mLastTouchOffset = -1;
     // These are needed to desambiguate a long click. If the long click comes from ones of these, we
     // select from the current cursor position. Otherwise, select from long pressed position.
     private boolean                 mDPadCenterIsDown = false;
     private boolean                 mEnterKeyIsDown = false;
+    private boolean                 mContextMenuTriggeredByKey = false;
     // Created once and shared by different CursorController helper methods.
     // Only one cursor controller is active at any time which prevent race conditions.
     private static Rect             sCursorControllerTempRect = new Rect();