From 4cf7ba6342868d03922a8ff460951c4f8eb6ae80 Mon Sep 17 00:00:00 2001 From: Justin Ghan Date: Mon, 17 Apr 2023 14:53:49 -0700 Subject: [PATCH] Hide hint text when starting handwriting Bug: 277153261 Test: atest android.view.stylus.HandwritingInitiatorTest Change-Id: I64007e8ea9a52a76cff78b04bc5b307c117b77c8 --- .../android/view/HandwritingInitiator.java | 7 ++ core/java/android/widget/TextView.java | 24 +++- .../view/stylus/HandwritingInitiatorTest.java | 114 ++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 77f3b1da92b41..dd4f9644da964 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.Rect; import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; @@ -305,6 +306,9 @@ public class HandwritingInitiator { mImm.startStylusHandwriting(view); mState.mHasInitiatedHandwriting = true; mState.mShouldInitHandwriting = false; + if (view instanceof TextView) { + ((TextView) view).hideHint(); + } } /** @@ -323,6 +327,9 @@ public class HandwritingInitiator { mState.mHasInitiatedHandwriting = true; mState.mShouldInitHandwriting = false; } + if (view instanceof TextView) { + ((TextView) view).hideHint(); + } return true; } return false; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 67c9f8ca00489..a30193abe76ce 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -805,6 +805,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharSequence mHint; @UnsupportedAppUsage private Layout mHintLayout; + private boolean mHideHint; private MovementMethod mMovement; @@ -7110,6 +7111,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); + mHideHint = false; + if (a11yTextChangeType == AccessibilityUtils.TEXT) { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); @@ -7268,6 +7271,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void setHintInternal(CharSequence hint) { + mHideHint = false; mHint = TextUtils.stringOrSpannedString(hint); if (mLayout != null) { @@ -7308,6 +7312,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mHint; } + /** + * Temporarily hides the hint text until the text is modified, or the hint text is modified, or + * the view gains or loses focus. + * + * @hide + */ + public void hideHint() { + if (isShowingHint()) { + mHideHint = true; + invalidate(); + } + } + /** * Returns if the text is constrained to a single horizontally scrolling line ignoring new * line characters instead of letting it wrap onto multiple lines. @@ -8904,7 +8921,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Layout layout = mLayout; - if (mHint != null && mText.length() == 0) { + if (mHint != null && !mHideHint && mText.length() == 0) { if (mHintTextColor != null) { color = mCurHintTextColor; } @@ -11223,7 +11240,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean isShowingHint() { - return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); + return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint) && !mHideHint; } /** @@ -12367,6 +12384,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); + mHideHint = false; clearGesturePreviewHighlight(); } @@ -12507,6 +12525,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } + mHideHint = false; + if (mEditor != null) mEditor.onFocusChanged(focused, direction); if (focused) { diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 8ae63816ba163..c0125afef2e8f 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -23,14 +23,20 @@ import static android.view.stylus.HandwritingTestUtil.createView; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.HandwritingInitiator; @@ -38,7 +44,9 @@ import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -47,6 +55,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; /** * Tests for {@link HandwritingInitiator} @@ -543,6 +552,111 @@ public class HandwritingInitiatorTest { assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1); } + @Test + public void startHandwriting_hidesHint() { + EditText editText = + new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext()); + editText.setHint("hint"); + editText.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + verifyEditTextDrawsText(editText, "hint"); + + mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0)); + mHandwritingInitiator.startHandwriting(editText); + + verifyEditTextDrawsText(editText, null); + } + + @Test + public void startHandwriting_clearFocus_restoresHint() { + EditText editText = + new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext()); + editText.setHint("hint"); + editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024)); + editText.requestFocus(); + + verifyEditTextDrawsText(editText, "hint"); + + mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0)); + mHandwritingInitiator.startHandwriting(editText); + + verifyEditTextDrawsText(editText, null); + + editText.clearFocus(); + + verifyEditTextDrawsText(editText, "hint"); + } + + @Test + public void startHandwriting_setHint_restoresHint() { + EditText editText = + new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext()); + editText.setHint("hint"); + editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024)); + + verifyEditTextDrawsText(editText, "hint"); + + mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0)); + mHandwritingInitiator.startHandwriting(editText); + + verifyEditTextDrawsText(editText, null); + + editText.setHint("new hint"); + + verifyEditTextDrawsText(editText, "new hint"); + } + + @Test + public void startHandwriting_setText_restoresHint() { + EditText editText = + new EditText(InstrumentationRegistry.getInstrumentation().getTargetContext()); + editText.setHint("hint"); + editText.setLayoutParams(new ViewGroup.LayoutParams(1024, 1024)); + + verifyEditTextDrawsText(editText, "hint"); + + mHandwritingInitiator.onTouchEvent(createStylusEvent(ACTION_DOWN, 0, 0, 0)); + mHandwritingInitiator.startHandwriting(editText); + + verifyEditTextDrawsText(editText, null); + + editText.setText("a"); + editText.setText(""); + + verifyEditTextDrawsText(editText, "hint"); + } + + private void verifyEditTextDrawsText(EditText editText, String text) { + editText.measure( + View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(1024, View.MeasureSpec.AT_MOST)); + Canvas canvas = prepareMockCanvas(editText); + editText.draw(canvas); + if (text != null) { + ArgumentCaptor textCaptor = ArgumentCaptor.forClass(CharSequence.class); + verify(canvas).drawText( + textCaptor.capture(), anyInt(), anyInt(), anyFloat(), anyFloat(), any()); + assertThat(textCaptor.getValue().toString()).isEqualTo(text); + } else { + verify(canvas, never()).drawText( + any(CharSequence.class), anyInt(), anyInt(), anyFloat(), anyFloat(), any()); + } + } + + private Canvas prepareMockCanvas(View view) { + Canvas canvas = mock(Canvas.class); + when(canvas.getClipBounds(any())).thenAnswer(invocation -> { + Rect outRect = invocation.getArgument(0); + outRect.top = 0; + outRect.left = 0; + outRect.right = view.getMeasuredWidth(); + outRect.bottom = view.getMeasuredHeight(); + return true; + }); + return canvas; + } + private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS;