summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Andrei Stingaceanu <stg@google.com> 2016-08-15 15:16:24 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2016-08-15 15:16:26 +0000
commitd055939c7cd1ef78af2c432816a788624e1a2b7b (patch)
treeb26562963d1b316af1a3d49bb7c1926aa380cc44
parentc20cfa69aecc59a990ea481ae7b4808021bd47ee (diff)
parent3df24c31bbc41eabb661d642f1859b6fd5eb9660 (diff)
Merge "TextView - fix ClickableSpans always triggering on touch up"
-rw-r--r--core/java/android/text/method/LinkMovementMethod.java63
-rw-r--r--core/java/android/widget/TextView.java35
-rw-r--r--core/tests/coretests/src/android/widget/TextViewTest.java98
3 files changed, 117 insertions, 79 deletions
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index 3855ff3ae44b..24c119f29d71 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -29,6 +29,9 @@ import android.widget.TextView;
/**
* A movement method that traverses links in the text buffer and scrolls if necessary.
* Supports clicking on links with DPad Center or Enter.
+ *
+ * <p>Note: Starting from Android 8.0 (API level 25) this class no longer handles the touch
+ * clicks.
*/
public class LinkMovementMethod extends ScrollingMovementMethod {
private static final int CLICK = 1;
@@ -98,14 +101,14 @@ public class LinkMovementMethod extends ScrollingMovementMethod {
int padding = widget.getTotalPaddingTop() +
widget.getTotalPaddingBottom();
- int areatop = widget.getScrollY();
- int areabot = areatop + widget.getHeight() - padding;
+ int areaTop = widget.getScrollY();
+ int areaBot = areaTop + widget.getHeight() - padding;
- int linetop = layout.getLineForVertical(areatop);
- int linebot = layout.getLineForVertical(areabot);
+ int lineTop = layout.getLineForVertical(areaTop);
+ int lineBot = layout.getLineForVertical(areaBot);
- int first = layout.getLineStart(linetop);
- int last = layout.getLineEnd(linebot);
+ int first = layout.getLineStart(lineTop);
+ int last = layout.getLineEnd(lineBot);
ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);
@@ -141,46 +144,46 @@ public class LinkMovementMethod extends ScrollingMovementMethod {
break;
case UP:
- int beststart, bestend;
+ int bestStart, bestEnd;
- beststart = -1;
- bestend = -1;
+ bestStart = -1;
+ bestEnd = -1;
for (int i = 0; i < candidates.length; i++) {
int end = buffer.getSpanEnd(candidates[i]);
if (end < selEnd || selStart == selEnd) {
- if (end > bestend) {
- beststart = buffer.getSpanStart(candidates[i]);
- bestend = end;
+ if (end > bestEnd) {
+ bestStart = buffer.getSpanStart(candidates[i]);
+ bestEnd = end;
}
}
}
- if (beststart >= 0) {
- Selection.setSelection(buffer, bestend, beststart);
+ if (bestStart >= 0) {
+ Selection.setSelection(buffer, bestEnd, bestStart);
return true;
}
break;
case DOWN:
- beststart = Integer.MAX_VALUE;
- bestend = Integer.MAX_VALUE;
+ bestStart = Integer.MAX_VALUE;
+ bestEnd = Integer.MAX_VALUE;
for (int i = 0; i < candidates.length; i++) {
int start = buffer.getSpanStart(candidates[i]);
if (start > selStart || selStart == selEnd) {
- if (start < beststart) {
- beststart = start;
- bestend = buffer.getSpanEnd(candidates[i]);
+ if (start < bestStart) {
+ bestStart = start;
+ bestEnd = buffer.getSpanEnd(candidates[i]);
}
}
}
- if (bestend < Integer.MAX_VALUE) {
- Selection.setSelection(buffer, beststart, bestend);
+ if (bestEnd < Integer.MAX_VALUE) {
+ Selection.setSelection(buffer, bestStart, bestEnd);
return true;
}
@@ -195,8 +198,7 @@ public class LinkMovementMethod extends ScrollingMovementMethod {
MotionEvent event) {
int action = event.getAction();
- if (action == MotionEvent.ACTION_UP ||
- action == MotionEvent.ACTION_DOWN) {
+ if (action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
@@ -210,17 +212,12 @@ public class LinkMovementMethod extends ScrollingMovementMethod {
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
- ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- link[0].onClick(widget);
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer,
- buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
+ ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
+ if (links.length != 0) {
+ Selection.setSelection(buffer,
+ buffer.getSpanStart(links[0]),
+ buffer.getSpanEnd(links[0]));
return true;
} else {
Selection.removeSelection(buffer);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index a6d004fd0ec9..6dafe34fdc13 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -114,6 +114,7 @@ import android.view.ActionMode;
import android.view.Choreographer;
import android.view.ContextMenu;
import android.view.DragEvent;
+import android.view.GestureDetector;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyCharacterMap;
@@ -650,6 +651,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private Editor mEditor;
+ private final GestureDetector mClickableSpanOnClickGestureDetector;
+
private static final int DEVICE_PROVISIONED_UNKNOWN = 0;
private static final int DEVICE_PROVISIONED_NO = 1;
private static final int DEVICE_PROVISIONED_YES = 2;
@@ -1488,6 +1491,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ mClickableSpanOnClickGestureDetector = new GestureDetector(context,
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ if (mLinksClickable && (mMovement != null) &&
+ (mMovement instanceof LinkMovementMethod
+ || (mAutoLinkMask != 0 && isTextSelectable()))) {
+ ClickableSpan[] links = ((Spannable) mText).getSpans(
+ getSelectionStart(), getSelectionEnd(), ClickableSpan.class);
+ if (links.length > 0) {
+ links[0].onClick(TextView.this);
+ return true;
+ }
+ }
+ return false;
+ }
+ });
}
private int[] parseDimensionArray(TypedArray dimens) {
@@ -8515,21 +8536,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
+ handled |= mClickableSpanOnClickGestureDetector.onTouchEvent(event);
final boolean textIsSelectable = isTextSelectable();
- if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
- // The LinkMovementMethod which should handle taps on links has not been installed
- // on non editable text that support text selection.
- // We reproduce its behavior here to open links for these.
- ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
- getSelectionEnd(), ClickableSpan.class);
-
- if (links.length > 0) {
- links[0].onClick(this);
- handled = true;
- }
- }
-
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 522149941416..3ccbf17c815e 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -29,6 +29,7 @@ import android.text.Spannable;
* TextViewTest tests {@link TextView}.
*/
public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewActivity> {
+ private TextView mTextView;
public TextViewTest() {
super(TextViewActivity.class);
@@ -37,16 +38,22 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewActiv
@SmallTest
@Presubmit
public void testArray() throws Exception {
- TextView tv = new TextView(getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView = new TextView(getActivity());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
char[] c = new char[] { 'H', 'e', 'l', 'l', 'o', ' ',
'W', 'o', 'r', 'l', 'd', '!' };
- tv.setText(c, 1, 4);
- CharSequence oldText = tv.getText();
+ mTextView.setText(c, 1, 4);
+ CharSequence oldText = mTextView.getText();
- tv.setText(c, 4, 5);
- CharSequence newText = tv.getText();
+ mTextView.setText(c, 4, 5);
+ CharSequence newText = mTextView.getText();
assertTrue(newText == oldText);
@@ -67,12 +74,18 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewActiv
@SmallTest
public void testProcessTextActivityResultNonEditable() {
- final TextView tv = new TextView(getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView = new TextView(getActivity());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
CharSequence originalText = "This is some text.";
- tv.setText(originalText, TextView.BufferType.SPANNABLE);
- assertEquals(originalText, tv.getText().toString());
- tv.setTextIsSelectable(true);
- Selection.setSelection((Spannable) tv.getText(), 0, tv.getText().length());
+ mTextView.setText(originalText, TextView.BufferType.SPANNABLE);
+ assertEquals(originalText, mTextView.getText().toString());
+ mTextView.setTextIsSelectable(true);
+ Selection.setSelection((Spannable) mTextView.getText(), 0, mTextView.getText().length());
// We need to run this in the UI thread, as it will create a Toast.
getActivity().runOnUiThread(new Runnable() {
@@ -81,60 +94,79 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewActiv
CharSequence newText = "Text is replaced.";
Intent data = new Intent();
data.putExtra(Intent.EXTRA_PROCESS_TEXT, newText);
- tv.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
+ mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
}
});
getInstrumentation().waitForIdleSync();
// This is a TextView, which can't be modified. Hence no change should have been made.
- assertEquals(originalText, tv.getText().toString());
+ assertEquals(originalText, mTextView.getText().toString());
}
@SmallTest
public void testProcessTextActivityResultEditable() {
- EditText tv = new EditText(getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView = new EditText(getActivity());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
CharSequence originalText = "This is some text.";
- tv.setText(originalText, TextView.BufferType.SPANNABLE);
- assertEquals(originalText, tv.getText().toString());
- tv.setTextIsSelectable(true);
- Selection.setSelection(tv.getText(), 0, tv.getText().length());
+ mTextView.setText(originalText, TextView.BufferType.SPANNABLE);
+ assertEquals(originalText, mTextView.getText().toString());
+ mTextView.setTextIsSelectable(true);
+ Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
CharSequence newText = "Text is replaced.";
Intent data = new Intent();
data.putExtra(Intent.EXTRA_PROCESS_TEXT, newText);
- tv.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
+ mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, data);
- assertEquals(newText, tv.getText().toString());
+ assertEquals(newText, mTextView.getText().toString());
}
@SmallTest
public void testProcessTextActivityResultCancel() {
- EditText tv = new EditText(getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView = new EditText(getActivity());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
CharSequence originalText = "This is some text.";
- tv.setText(originalText, TextView.BufferType.SPANNABLE);
- assertEquals(originalText, tv.getText().toString());
- tv.setTextIsSelectable(true);
- Selection.setSelection(tv.getText(), 0, tv.getText().length());
+ mTextView.setText(originalText, TextView.BufferType.SPANNABLE);
+ assertEquals(originalText, mTextView.getText().toString());
+ mTextView.setTextIsSelectable(true);
+ Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
CharSequence newText = "Text is replaced.";
Intent data = new Intent();
data.putExtra(Intent.EXTRA_PROCESS_TEXT, newText);
- tv.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_CANCELED, data);
+ mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_CANCELED,
+ data);
- assertEquals(originalText, tv.getText().toString());
+ assertEquals(originalText, mTextView.getText().toString());
}
@SmallTest
public void testProcessTextActivityNoData() {
- EditText tv = new EditText(getActivity());
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTextView = new EditText(getActivity());
+ }
+ });
+ getInstrumentation().waitForIdleSync();
CharSequence originalText = "This is some text.";
- tv.setText(originalText, TextView.BufferType.SPANNABLE);
- assertEquals(originalText, tv.getText().toString());
- tv.setTextIsSelectable(true);
- Selection.setSelection(tv.getText(), 0, tv.getText().length());
+ mTextView.setText(originalText, TextView.BufferType.SPANNABLE);
+ assertEquals(originalText, mTextView.getText().toString());
+ mTextView.setTextIsSelectable(true);
+ Selection.setSelection(((EditText) mTextView).getText(), 0, mTextView.getText().length());
- tv.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, null);
+ mTextView.onActivityResult(TextView.PROCESS_TEXT_REQUEST_CODE, Activity.RESULT_OK, null);
- assertEquals(originalText, tv.getText().toString());
+ assertEquals(originalText, mTextView.getText().toString());
}
}