diff options
-rw-r--r-- | core/java/android/text/method/LinkMovementMethod.java | 63 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 35 | ||||
-rw-r--r-- | core/tests/coretests/src/android/widget/TextViewTest.java | 98 |
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()); } } |