From 3f1203fb786a5edfe7ec191494edd887f3b419dd Mon Sep 17 00:00:00 2001 From: sallyyuen Date: Mon, 10 Feb 2020 10:45:48 -0800 Subject: Implement ACTION_LONG_CLICK for accessibility Due to changes in R, the a11y framework no longer dispatches touch events for a long press. This prevents the activation of EditText's floating menu. We can re-enable it by implementing the proper a11y action ACTION_LONG_CLICK. The menu itself is diffult to access through TalkBack's linear navigation, but this is future work for a separate known issue. Start and stop the menu for editable TextViews, which includes EditTexts. Since touch events are no longer sent by a11y, separate the accessibility handling from the touch handling infrastructure for long clicks in Editor. We can't go through the main performLongClick code because it doesn't actually start the action mode but rather sets pending, which routes back to TextView. There's too little separation between the touch events and action logic. Whoever touches the performLongClick code may need to also make corresponding changes to the a11y path, but I suspect this won't happen often. Remove the onInitializeA11yNodeInfo override for EditText because this is handled by TextView. Bug: 148127445 Test: Tested text fields in various apps. ag/10602004. atest FrameworksCoreTests:TextViewActivityTest#testToolbarAppearsAccessibilityLongClick Change-Id: I3958e5b80e6156e03c99335e0d0b671438965ebb --- core/java/android/widget/TextView.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f3243aaf5b7d..4f5f13bc2ab1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12109,6 +12109,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } return true; + case AccessibilityNodeInfo.ACTION_LONG_CLICK: { + if (isLongClickable()) { + boolean handled; + if (isEnabled() && (mBufferType == BufferType.EDITABLE)) { + mEditor.mIsBeingLongClickedByAccessibility = true; + try { + handled = performLongClick(); + } finally { + mEditor.mIsBeingLongClickedByAccessibility = false; + } + } else { + handled = performLongClick(); + } + return handled; + } + } + return false; default: { return super.performAccessibilityActionInternal(action, arguments); } -- cgit v1.2.3 From 832edc3cc92584f7a41f84d471b6eeaadbeeccab Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Fri, 7 Feb 2020 14:38:23 -0800 Subject: Add unified API for inserting rich content (e.g. pasting an image) The new callback provides a single API that apps can implement to support the different ways in which rich content may be inserted. The API is added to TextView and unifies the following code paths: * paste from the clipboard (TextView.paste) * content insertion from the IME (InputConnection.commitContent) * drag and drop (Editor.onDrop) * autofill (TextView.autofill) Corresponding API in support lib: aosp/1200800 Bug: 152068298 Test: Manual and unit tests atest FrameworksCoreTests:TextViewRichContentReceiverTest atest FrameworksCoreTests:AutofillValueTest atest FrameworksCoreTests:TextViewActivityTest Change-Id: I6e03a398ccb6fa5526d0a282fc114f4e80285099 --- core/java/android/widget/TextView.java | 127 ++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 42 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e1783181457f..6ee4dd7b9fc7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -862,6 +862,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @UnsupportedAppUsage private Editor mEditor; + /** + * The default content insertion callback used by {@link TextView}. See + * {@link #setRichContentReceiver} for more info. + */ + public static final @NonNull RichContentReceiver DEFAULT_RICH_CONTENT_RECEIVER = + TextViewRichContentReceiver.INSTANCE; + + private RichContentReceiver mRichContentReceiver = DEFAULT_RICH_CONTENT_RECEIVER; + private static final int DEVICE_PROVISIONED_UNKNOWN = 0; private static final int DEVICE_PROVISIONED_NO = 1; private static final int DEVICE_PROVISIONED_YES = 2; @@ -8697,6 +8706,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); + int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; + if (targetSdkVersion > Build.VERSION_CODES.R) { + outAttrs.contentMimeTypes = mRichContentReceiver.getSupportedMimeTypes() + .toArray(new String[0]); + } return ic; } } @@ -11689,26 +11703,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void autofill(AutofillValue value) { - if (!value.isText() || !isTextEditable()) { - Log.w(LOG_TAG, value + " could not be autofilled into " + this); + if (!isTextEditable()) { + Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); return; } - - final CharSequence autofilledValue = value.getTextValue(); - - // First autofill it... - setText(autofilledValue, mBufferType, true, 0); - - // ...then move cursor to the end. - final CharSequence text = getText(); - if ((text instanceof Spannable)) { - Selection.setSelection((Spannable) text, text.length()); + ClipData clip; + if (value.isRichContent()) { + clip = value.getRichContentValue(); + } else if (value.isText()) { + clip = ClipData.newPlainText("", value.getTextValue()); + } else { + Log.w(LOG_TAG, "value of type " + value.describeContents() + + " cannot be autofilled into " + this); + return; } + mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_AUTOFILL, 0); } @Override public @AutofillType int getAutofillType() { - return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; + if (!isTextEditable()) { + return AUTOFILL_TYPE_NONE; + } + final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; + if (targetSdkVersion <= Build.VERSION_CODES.R) { + return AUTOFILL_TYPE_TEXT; + } + // TODO(b/147301047): Update autofill framework code to check the target SDK of the autofill + // provider and force the type AUTOFILL_TYPE_TEXT for providers that target older SDKs. + return mRichContentReceiver.supportsNonTextContent() ? AUTOFILL_TYPE_RICH_CONTENT + : AUTOFILL_TYPE_TEXT; } /** @@ -12286,11 +12310,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; // Returns true even if nothing was undone. case ID_PASTE: - paste(min, max, true /* withFormatting */); + paste(true /* withFormatting */); return true; case ID_PASTE_AS_PLAIN_TEXT: - paste(min, max, false /* withFormatting */); + paste(false /* withFormatting */); return true; case ID_CUT: @@ -12768,36 +12792,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); } - /** - * Paste clipboard content between min and max positions. - */ - private void paste(int min, int max, boolean withFormatting) { + private void paste(boolean withFormatting) { ClipboardManager clipboard = getClipboardManagerForUser(); ClipData clip = clipboard.getPrimaryClip(); - if (clip != null) { - boolean didFirst = false; - for (int i = 0; i < clip.getItemCount(); i++) { - final CharSequence paste; - if (withFormatting) { - paste = clip.getItemAt(i).coerceToStyledText(getContext()); - } else { - // Get an item as text and remove all spans by toString(). - final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); - paste = (text instanceof Spanned) ? text.toString() : text; - } - if (paste != null) { - if (!didFirst) { - Selection.setSelection(mSpannable, max); - ((Editable) mText).replace(min, max, paste); - didFirst = true; - } else { - ((Editable) mText).insert(getSelectionEnd(), "\n"); - ((Editable) mText).insert(getSelectionEnd(), paste); - } - } - } - sLastCutCopyOrTextChangedTime = 0; + if (clip == null) { + return; } + int flags = withFormatting ? 0 : RichContentReceiver.FLAG_CONVERT_TO_PLAIN_TEXT; + mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_MENU, flags); + sLastCutCopyOrTextChangedTime = 0; } private void shareSelectedText() { @@ -13578,6 +13581,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** + * Returns the callback that handles insertion of content into this view (e.g. pasting from + * the clipboard). See {@link #setRichContentReceiver} for more info. + * + * @return The callback that this view is using to handle insertion of content. Returns + * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if not custom callback has been + * {@link #setRichContentReceiver set}. + */ + @NonNull + public RichContentReceiver getRichContentReceiver() { + return mRichContentReceiver; + } + + /** + * Sets the callback to handle insertion of content into this view. + * + *

"Content" and "rich content" here refers to both text and non-text: plain text, styled + * text, HTML, images, videos, audio files, etc. + * + *

The callback configured here should typically wrap {@link #DEFAULT_RICH_CONTENT_RECEIVER} + * to provide consistent behavior for text content. + * + *

This callback will be invoked for the following scenarios: + *

    + *
  1. Paste from the clipboard ("Paste" and "Paste as plain text" actions in the + * insertion/selection menu) + *
  2. Content insertion from the keyboard ({@link InputConnection#commitContent}) + *
  3. Drag and drop ({@link View#onDragEvent}) + *
  4. Autofill, when the type for the field is + * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT} + *
+ * + * @param receiver The callback to use. This can be {@link #DEFAULT_RICH_CONTENT_RECEIVER} to + * reset to the default behavior. + */ + public void setRichContentReceiver(@NonNull RichContentReceiver receiver) { + mRichContentReceiver = Objects.requireNonNull(receiver, + "RichContentReceiver should not be null."); + } + private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { if (msgFormat == null) { Log.d(LOG_TAG, location); -- cgit v1.2.3 From 186b4eba0a198bd05d2de68d59028a543c724432 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Fri, 3 Apr 2020 17:57:19 -0700 Subject: Minor javadoc fixes for RichContentReceiver API Bug: 152068298 Test: Presubmit Change-Id: If86fa3d68ae2af616f234a3fe283f333c29888cd --- core/java/android/widget/TextView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6ee4dd7b9fc7..2f32ab544c5d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13586,7 +13586,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * the clipboard). See {@link #setRichContentReceiver} for more info. * * @return The callback that this view is using to handle insertion of content. Returns - * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if not custom callback has been + * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if no custom callback has been * {@link #setRichContentReceiver set}. */ @NonNull -- cgit v1.2.3 From 18e49a9bd4a46e7bed43c6f8e5b4ac62c53e4cb7 Mon Sep 17 00:00:00 2001 From: Nader Jawad Date: Tue, 14 Apr 2020 15:24:04 -0700 Subject: Updated TextView#setCompoundDrawableTintList documentation to refer to BlendMode.SRC_IN instead of PorterDuff.Mode.SRC_IN Fixes: 151952140 Test: N/A Change-Id: Ie4b9a585c2dae49e633f6741180b6749493d069a --- core/java/android/widget/TextView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2f32ab544c5d..97c7759796ae 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3281,7 +3281,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Applies a tint to the compound drawables. Does not modify the - * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. + * current tint mode, which is {@link BlendMode#SRC_IN} by default. *

* Subsequent calls to * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} -- cgit v1.2.3 From 90b8d4995ad1e9c99bc0e65604849b1ab64241a1 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Wed, 8 Apr 2020 16:30:19 -0700 Subject: Integrate ACTION_PROCESS_TEXT with RichContentReceiver Bug: 152068298 Bug: 152808432 Test: Ran existing/new tests for ACTION_PROCESS_TEXT atest FrameworksCoreTests:TextViewTest atest FrameworksCoreTests:TextViewProcessTextTest Change-Id: Idf88839f96087cdc403ea1b5413c238413e005d3 --- core/java/android/widget/TextView.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2f32ab544c5d..7f99ba21da44 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -22,6 +22,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; +import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT; import android.R; import android.annotation.CallSuper; @@ -2114,7 +2115,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); if (result != null) { if (isTextEditable()) { - replaceSelectionWithText(result); + ClipData clip = ClipData.newPlainText("", result); + mRichContentReceiver.onReceive(this, clip, SOURCE_PROCESS_TEXT, 0); if (mEditor != null) { mEditor.refreshTextActionMode(); } @@ -12788,10 +12790,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return length > 0; } - void replaceSelectionWithText(CharSequence text) { - ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); - } - private void paste(boolean withFormatting) { ClipboardManager clipboard = getClipboardManagerForUser(); ClipData clip = clipboard.getPrimaryClip(); -- cgit v1.2.3 From bc495089fdd87afaa3a7fb8d93aed714d4d29918 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Wed, 15 Apr 2020 14:34:47 -0700 Subject: Rename RichContentReceiver.SOURCE_MENU to SOURCE_CLIPBOARD Using "menu" was ambiguous since ACTION_PROCESS_TEXT is also triggered via the menu but has its own enum value. Bug: 152068298 Bug: 154151141 Test: ran tests atest CtsWidgetTestCases:TextViewRichContentReceiverTest Change-Id: I3805577e9d7bbf55d40339d37820cd7f510da37e --- core/java/android/widget/TextView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 830d9fa0ba53..7f2bfabe2fe5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12843,7 +12843,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } int flags = withFormatting ? 0 : RichContentReceiver.FLAG_CONVERT_TO_PLAIN_TEXT; - mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_MENU, flags); + mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_CLIPBOARD, flags); sLastCutCopyOrTextChangedTime = 0; } @@ -13649,7 +13649,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * *

This callback will be invoked for the following scenarios: *

    - *
  1. Paste from the clipboard ("Paste" and "Paste as plain text" actions in the + *
  2. Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the * insertion/selection menu) *
  3. Content insertion from the keyboard ({@link InputConnection#commitContent}) *
  4. Drag and drop ({@link View#onDragEvent}) -- cgit v1.2.3 From 9140d2b2e354487494253651a010fcb2f13c3311 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Mon, 1 Jun 2020 18:46:48 -0700 Subject: Set upper-limit for the single line edit text. Bug: 145128646 Test: atest EditTextTest#testSingleLineMaxLength Change-Id: I32b28dcc8c18386839b5adea5b73c77896036567 --- core/java/android/widget/TextView.java | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4b71d91b8f26..16224a74a7f8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -396,6 +396,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int EMS = LINES; private static final int PIXELS = 2; + // Maximum text length for single line input. + private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; + private static final RectF TEMP_RECTF = new RectF(); /** @hide */ @@ -1643,6 +1646,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setTransformationMethod(PasswordTransformationMethod.getInstance()); } + // For addressing b/145128646 + // For the performance reason, we limit characters for single line text field. + if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { + maxlength = MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT; + } + if (maxlength >= 0) { setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); } else { -- cgit v1.2.3 From 459f35ac86bed1f8e0f864d569b2ddc25b47de73 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Fri, 12 Jun 2020 19:10:40 -0700 Subject: Apply max char length when setSingleLine is called. Bug: 145128646 Test: atest android.widget.cts.EditTextTest Change-Id: I1f2fd6c97c5cdeb7be5017d4c6aeb19fd269e939 --- core/java/android/widget/TextView.java | 90 +++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 6 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 16224a74a7f8..eaa4c573b0e4 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -398,6 +398,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Maximum text length for single line input. private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000; + private InputFilter.LengthFilter mSingleLineLengthFilter = null; private static final RectF TEMP_RECTF = new RectF(); @@ -1602,7 +1603,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Same as setSingleLine(), but make sure the transformation method and the maximum number // of lines of height are unchanged for multi-line TextViews. setInputTypeSingleLine(singleLine); - applySingleLine(singleLine, singleLine, singleLine); + applySingleLine(singleLine, singleLine, singleLine, + // Does not apply automated max length filter since length filter will be resolved + // later in this function. + false + ); if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { ellipsize = ELLIPSIZE_END; @@ -1649,10 +1654,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // For addressing b/145128646 // For the performance reason, we limit characters for single line text field. if (bufferType == BufferType.EDITABLE && singleLine && maxlength == -1) { - maxlength = MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT; + mSingleLineLengthFilter = new InputFilter.LengthFilter( + MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); } - if (maxlength >= 0) { + if (mSingleLineLengthFilter != null) { + setFilters(new InputFilter[] { mSingleLineLengthFilter }); + } else if (maxlength >= 0) { setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); } else { setFilters(NO_FILTERS); @@ -6610,7 +6618,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mSingleLine != singleLine || forceUpdate) { // Change single line mode, but only change the transformation if // we are not in password mode. - applySingleLine(singleLine, !isPassword, true); + applySingleLine(singleLine, !isPassword, true, true); } if (!isSuggestionsEnabled()) { @@ -10254,6 +10262,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Note that the default conditions are not necessarily those that were in effect prior this * method, and you may want to reset these properties to your custom values. * + * Note that due to performance reasons, by setting single line for the EditText, the maximum + * text length is set to 5000 if no other character limitation are applied. + * * @attr ref android.R.styleable#TextView_singleLine */ @android.view.RemotableViewMethod @@ -10261,7 +10272,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Could be used, but may break backward compatibility. // if (mSingleLine == singleLine) return; setInputTypeSingleLine(singleLine); - applySingleLine(singleLine, true, true); + applySingleLine(singleLine, true, true, true); } /** @@ -10281,14 +10292,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void applySingleLine(boolean singleLine, boolean applyTransformation, - boolean changeMaxLines) { + boolean changeMaxLines, boolean changeMaxLength) { mSingleLine = singleLine; + if (singleLine) { setLines(1); setHorizontallyScrolling(true); if (applyTransformation) { setTransformationMethod(SingleLineTransformationMethod.getInstance()); } + + if (!changeMaxLength) return; + + // Single line length filter is only applicable editable text. + if (mBufferType != BufferType.EDITABLE) return; + + final InputFilter[] prevFilters = getFilters(); + for (InputFilter filter: getFilters()) { + // We don't add LengthFilter if already there. + if (filter instanceof InputFilter.LengthFilter) return; + } + + if (mSingleLineLengthFilter == null) { + mSingleLineLengthFilter = new InputFilter.LengthFilter( + MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT); + } + + final InputFilter[] newFilters = new InputFilter[prevFilters.length + 1]; + System.arraycopy(prevFilters, 0, newFilters, 0, prevFilters.length); + newFilters[prevFilters.length] = mSingleLineLengthFilter; + + setFilters(newFilters); + + // Since filter doesn't apply to existing text, trigger filter by setting text. + setText(getText()); } else { if (changeMaxLines) { setMaxLines(Integer.MAX_VALUE); @@ -10297,6 +10334,47 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (applyTransformation) { setTransformationMethod(null); } + + if (!changeMaxLength) return; + + // Single line length filter is only applicable editable text. + if (mBufferType != BufferType.EDITABLE) return; + + final InputFilter[] prevFilters = getFilters(); + if (prevFilters.length == 0) return; + + // Short Circuit: if mSingleLineLengthFilter is not allocated, nobody sets automated + // single line char limit filter. + if (mSingleLineLengthFilter == null) return; + + // If we need to remove mSingleLineLengthFilter, we need to allocate another array. + // Since filter list is expected to be small and want to avoid unnecessary array + // allocation, check if there is mSingleLengthFilter first. + int targetIndex = -1; + for (int i = 0; i < prevFilters.length; ++i) { + if (prevFilters[i] == mSingleLineLengthFilter) { + targetIndex = i; + break; + } + } + if (targetIndex == -1) return; // not found. Do nothing. + + if (prevFilters.length == 1) { + setFilters(NO_FILTERS); + return; + } + + // Create new array which doesn't include mSingleLengthFilter. + final InputFilter[] newFilters = new InputFilter[prevFilters.length - 1]; + System.arraycopy(prevFilters, 0, newFilters, 0, targetIndex); + System.arraycopy( + prevFilters, + targetIndex + 1, + newFilters, + targetIndex, + prevFilters.length - targetIndex - 1); + setFilters(newFilters); + mSingleLineLengthFilter = null; } } -- cgit v1.2.3 From dd9869aa4c0a58230ba51956eae245d4415cca63 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Tue, 11 Aug 2020 23:41:21 -0700 Subject: Update RichContentReceiver API (now OnReceiveContentCallback) Bug: 163718378 Bug: 165632620 Bug: 152068298 Test: Unit tests atest CtsWidgetTestCases:TextViewOnReceiveContentCallbackTest atest CtsViewTestCases:ViewTest atest FrameworksCoreTests:TextViewProcessTextTest Change-Id: I3fa65b47c920a9d1ddad88a79e60864dc8109753 --- core/java/android/widget/TextView.java | 120 +++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 50 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6f14dfb89e6b..06f027013c8f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,12 +17,15 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_CLIPBOARD; +import static android.view.OnReceiveContentCallback.Payload.SOURCE_PROCESS_TEXT; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; -import static android.widget.RichContentReceiver.SOURCE_PROCESS_TEXT; import android.R; import android.annotation.CallSuper; @@ -39,6 +42,7 @@ import android.annotation.RequiresPermission; import android.annotation.Size; import android.annotation.StringRes; import android.annotation.StyleRes; +import android.annotation.TestApi; import android.annotation.XmlRes; import android.app.Activity; import android.app.PendingIntent; @@ -149,6 +153,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.OnReceiveContentCallback; import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; @@ -426,7 +431,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide */ - static final int PROCESS_TEXT_REQUEST_CODE = 100; + @TestApi + public static final int PROCESS_TEXT_REQUEST_CODE = 100; /** * Return code of {@link #doKeyDown}. @@ -882,12 +888,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * The default content insertion callback used by {@link TextView}. See - * {@link #setRichContentReceiver} for more info. + * {@link #setOnReceiveContentCallback} for more info. */ - public static final @NonNull RichContentReceiver DEFAULT_RICH_CONTENT_RECEIVER = - TextViewRichContentReceiver.INSTANCE; - - private RichContentReceiver mRichContentReceiver = DEFAULT_RICH_CONTENT_RECEIVER; + private static final TextViewOnReceiveContentCallback DEFAULT_ON_RECEIVE_CONTENT_CALLBACK = + new TextViewOnReceiveContentCallback(); private static final int DEVICE_PROVISIONED_UNKNOWN = 0; private static final int DEVICE_PROVISIONED_NO = 1; @@ -2138,15 +2142,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide */ + @TestApi @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == PROCESS_TEXT_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); if (result != null) { if (isTextEditable()) { ClipData clip = ClipData.newPlainText("", result); - mRichContentReceiver.onReceive(this, clip, SOURCE_PROCESS_TEXT, 0); + OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder( + clip, SOURCE_PROCESS_TEXT) + .build(); + onReceiveContent(payload); if (mEditor != null) { mEditor.refreshTextActionMode(); } @@ -8722,9 +8731,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); - int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; - if (targetSdkVersion > Build.VERSION_CODES.R) { - outAttrs.contentMimeTypes = mRichContentReceiver.getSupportedMimeTypes() + // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types. + OnReceiveContentCallback receiver = getOnReceiveContentCallback(); + if (receiver != null) { + outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this) .toArray(new String[0]); } return ic; @@ -11827,7 +11837,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); return; } - ClipData clip; + final ClipData clip; if (value.isRichContent()) { clip = value.getRichContentValue(); } else if (value.isText()) { @@ -11837,22 +11847,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " cannot be autofilled into " + this); return; } - mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_AUTOFILL, 0); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_AUTOFILL).build(); + onReceiveContent(payload); } @Override public @AutofillType int getAutofillType() { - if (!isTextEditable()) { - return AUTOFILL_TYPE_NONE; - } - final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion; - if (targetSdkVersion <= Build.VERSION_CODES.R) { - return AUTOFILL_TYPE_TEXT; - } - // TODO(b/147301047): Update autofill framework code to check the target SDK of the autofill - // provider and force the type AUTOFILL_TYPE_TEXT for providers that target older SDKs. - return mRichContentReceiver.supportsNonTextContent() ? AUTOFILL_TYPE_RICH_CONTENT - : AUTOFILL_TYPE_TEXT; + return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; } /** @@ -12913,8 +12915,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (clip == null) { return; } - int flags = withFormatting ? 0 : RichContentReceiver.FLAG_CONVERT_TO_PLAIN_TEXT; - mRichContentReceiver.onReceive(this, clip, RichContentReceiver.SOURCE_CLIPBOARD, flags); + final OnReceiveContentCallback.Payload payload = + new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_CLIPBOARD) + .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) + .build(); + onReceiveContent(payload); sLastCutCopyOrTextChangedTime = 0; } @@ -13697,43 +13702,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns the callback that handles insertion of content into this view (e.g. pasting from - * the clipboard). See {@link #setRichContentReceiver} for more info. + * Returns the callback used for handling insertion of content into this view. See + * {@link #setOnReceiveContentCallback} for more info. * - * @return The callback that this view is using to handle insertion of content. Returns - * {@link #DEFAULT_RICH_CONTENT_RECEIVER} if no custom callback has been - * {@link #setRichContentReceiver set}. + * @return The callback for handling insertion of content. Returns null if no callback has been + * {@link #setOnReceiveContentCallback set}. */ - @NonNull - public RichContentReceiver getRichContentReceiver() { - return mRichContentReceiver; + @SuppressWarnings("unchecked") + @Nullable + @Override + public OnReceiveContentCallback getOnReceiveContentCallback() { + return (OnReceiveContentCallback) super.getOnReceiveContentCallback(); } /** * Sets the callback to handle insertion of content into this view. * - *

    "Content" and "rich content" here refers to both text and non-text: plain text, styled - * text, HTML, images, videos, audio files, etc. - * - *

    The callback configured here should typically wrap {@link #DEFAULT_RICH_CONTENT_RECEIVER} - * to provide consistent behavior for text content. - * *

    This callback will be invoked for the following scenarios: *

      *
    1. Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the * insertion/selection menu) - *
    2. Content insertion from the keyboard ({@link InputConnection#commitContent}) - *
    3. Drag and drop ({@link View#onDragEvent}) - *
    4. Autofill, when the type for the field is - * {@link android.view.View.AutofillType#AUTOFILL_TYPE_RICH_CONTENT} + *
    5. Content insertion from the keyboard (from {@link InputConnection#commitContent}) + *
    6. Drag and drop (drop events from {@link #onDragEvent(DragEvent)}) + *
    7. Autofill (from {@link #autofill(AutofillValue)}) + *
    8. {@link Intent#ACTION_PROCESS_TEXT} replacement *
    * - * @param receiver The callback to use. This can be {@link #DEFAULT_RICH_CONTENT_RECEIVER} to - * reset to the default behavior. + *

    The callback will only be invoked if the MIME type of the content is + * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback. + * If the content type is not supported by the callback, the default platform handling will be + * executed instead. + * + * @param callback The callback to use. This can be null to reset to the default behavior. */ - public void setRichContentReceiver(@NonNull RichContentReceiver receiver) { - mRichContentReceiver = Objects.requireNonNull(receiver, - "RichContentReceiver should not be null."); + @Override + public void setOnReceiveContentCallback( + @Nullable OnReceiveContentCallback callback) { + super.setOnReceiveContentCallback(callback); + } + + /** + * Handles the request to insert content using the configured callback or the default callback. + * + * @hide + */ + void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { + OnReceiveContentCallback receiver = getOnReceiveContentCallback(); + ClipDescription description = payload.getClip().getDescription(); + if (receiver != null && receiver.supports(this, description)) { + receiver.onReceiveContent(this, payload); + } else { + DEFAULT_ON_RECEIVE_CONTENT_CALLBACK.onReceiveContent(this, payload); + } } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { -- cgit v1.2.3 From 5670baef9b56cf5451a7b5d8f0853d5c9a781294 Mon Sep 17 00:00:00 2001 From: Sally Date: Thu, 24 Sep 2020 18:27:31 +0000 Subject: Add support for Force Bold Text This CL: 1) Saves the original unbolded typeface and returns this in getTypeface 2) Sets Paint's typeface to a bolded one if the Setting is enabled, or a config value is YES 3) Bolds by adding 300 to the current font weight (400 is normal, 700 is considered bold, weight is capped at 1000) 4) Resets the Paint typeface to the original value if the Setting is disabled, or the config value is not YES Bug: b/110991537 Test: manual; unit tests Change-Id: If21190fd5ad9d2e1721ffe464945f00ff20f62c6 --- core/java/android/widget/TextView.java | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6f14dfb89e6b..058ccb7938a4 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -735,6 +735,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; + // True if force bold text feature is enabled. This feature makes all text bolder. + private boolean mForceBoldTextEnabled; + private Typeface mOriginalTypeface; + // True if setKeyListener() has been explicitly called private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. @@ -1645,6 +1649,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener attributes.mTypefaceIndex = MONOSPACE; } + mForceBoldTextEnabled = getContext().getResources().getConfiguration().forceBoldText + == Configuration.FORCE_BOLD_TEXT_YES; applyTextAppearance(attributes); if (isPassword) { @@ -4267,6 +4273,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } } + if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_YES) { + mForceBoldTextEnabled = true; + setTypeface(getTypeface()); + } else if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_NO + || newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_UNDEFINED) { + mForceBoldTextEnabled = false; + setTypeface(getTypeface()); + } } /** @@ -4418,6 +4432,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_textStyle */ public void setTypeface(@Nullable Typeface tf) { + mOriginalTypeface = tf; + if (mForceBoldTextEnabled) { + int newWeight = tf != null ? tf.getWeight() + 300 : 400; + newWeight = Math.min(newWeight, 1000); + int typefaceStyle = tf != null ? tf.getStyle() : 0; + boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; + tf = Typeface.create(tf, newWeight, italic); + } if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); @@ -4441,7 +4463,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @InspectableProperty public Typeface getTypeface() { - return mTextPaint.getTypeface(); + return mOriginalTypeface; } /** -- cgit v1.2.3 From 7b384751ea2a19beb7a635c52545742b2171ae8d Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Thu, 6 Aug 2020 22:20:06 -0700 Subject: Use IME image API impl as fallback in TextViewOnReceiveContentCallback A bunch of apps implement the keyboard image API (see https://developer.android.com/guide/topics/text/image-keyboard). When image support in augmented autofill is released, we'd like it to work immediately in apps that have previously implemented the keyboard image API, without having to wait for these apps to move to the new unified content insertion API. To make this possible, this change adds logic to call the keyboard image API (InputConnection.commitContent) if the app implements it and if the app target SDK is <= S. This gives apps a full Android release to upgrade to the new content insertion API while augemented autofill will immediately be able to insert images without any changes in apps that have implemented the keyboard image API. Bug: 163400105 Bug: 152068298 Test: Manual and unit tests atest FrameworksCoreTests:TextViewOnReceiveContentCallbackTest atest CtsWidgetTestCases:TextViewOnReceiveContentCallbackTest Change-Id: I9280604e7ec7e8d08c1179e6bbf0068647a41040 --- core/java/android/widget/TextView.java | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 52a3f4145e7e..7bb2b7e92a00 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -80,6 +80,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; +import android.os.Handler; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; @@ -890,13 +891,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @UnsupportedAppUsage private Editor mEditor; - /** - * The default content insertion callback used by {@link TextView}. See - * {@link #setOnReceiveContentCallback} for more info. - */ - private static final TextViewOnReceiveContentCallback DEFAULT_ON_RECEIVE_CONTENT_CALLBACK = - new TextViewOnReceiveContentCallback(); - private static final int DEVICE_PROVISIONED_UNKNOWN = 0; private static final int DEVICE_PROVISIONED_NO = 1; private static final int DEVICE_PROVISIONED_YES = 2; @@ -13723,6 +13717,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + /** @hide */ + @Override + public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, + @NonNull EditorInfo editorInfo, @Nullable Handler handler) { + if (mEditor != null) { + mEditor.getDefaultOnReceiveContentCallback().setInputConnectionInfo(ic, editorInfo); + } + } + + /** @hide */ + @Override + public void onInputConnectionClosedInternal() { + if (mEditor != null) { + mEditor.getDefaultOnReceiveContentCallback().clearInputConnectionInfo(); + } + } + /** * Returns the callback used for handling insertion of content into this view. See * {@link #setOnReceiveContentCallback} for more info. @@ -13773,8 +13784,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ClipDescription description = payload.getClip().getDescription(); if (receiver != null && receiver.supports(this, description)) { receiver.onReceiveContent(this, payload); - } else { - DEFAULT_ON_RECEIVE_CONTENT_CALLBACK.onReceiveContent(this, payload); + } else if (mEditor != null) { + mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); } } -- cgit v1.2.3 From 265096d9dac9cdfdce11f24f1b840b3139ea6fc0 Mon Sep 17 00:00:00 2001 From: Shu Chen Date: Thu, 22 Oct 2020 12:26:52 +0800 Subject: Makes the multi-touch prevention only works for editable TextView. The original logic of preventing the multi-touch issue for the edtiable text view was introduced by ag/10930920. Turns out the non-editable text views are affected. The root cause of the issue is that: - the ACTION_DOWN/ACTION_UP events, as well as ACTION_PONITER_DOWN / ACTION_POINTER_UP, could be generated by different fingers, - and View#onTouchEvent() gets muted when isFromPrimePointer() == false, - so that no gesture is generated, e.g. click, long press, etc. Note that View#onTouchEvent() is necessary to be muted to prevent long press on text or scrolling while dragging the insertion handle view. Bug: 169288151 Change-Id: Ia763e94be727ad23bb13839f146293f1579d03ec --- core/java/android/widget/TextView.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7bb2b7e92a00..3fc0f4efd608 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11051,12 +11051,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener MotionEvent.actionToString(event.getActionMasked()), event.getX(), event.getY()); } - if (!isFromPrimePointer(event, false)) { - return true; - } - final int action = event.getActionMasked(); if (mEditor != null) { + if (!isFromPrimePointer(event, false)) { + return true; + } + mEditor.onTouchEvent(event); if (mEditor.mInsertionPointCursorController != null -- cgit v1.2.3 From 82a414a18454d1e34f369370b2c60a5fc50a1809 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Tue, 13 Oct 2020 13:11:54 -0700 Subject: Add View.onReceiveContent() and View.getOnReceiveContentMimeTypes() Added View.onReceiveContent() which will invoke the callback if one is set. Added View.getOnReceiveContentMimeTypes() to return the MIME types that the view can receive, removing the getSupportedMimeTypes() method from the callback interface. Changed the code to pass MIME types as a String[] instead of a Set in order to: * Avoid repeated conversion from Set to array in onCreateInputConnection. * Avoid misleading users of the API into using contains() to compare MIME types (ClipDescription.compareMimeTypes should be used in order to correctly handle wilcards such as "image/*"). Bug: 170191676 Bug: 152068298 Test: atest CtsViewTestCases:ViewTest Test: atest CtsViewTestCases:ViewOnReceiveContentTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentCallbackTest Test: atest FrameworksCoreTests:TextViewOnReceiveContentCallbackTest Test: atest FrameworksCoreTests:AutofillValueTest Change-Id: Id0c7f8f5fd3c7c44827a62dba1b2b205433e7540 --- core/java/android/widget/TextView.java | 68 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7bb2b7e92a00..a8a330377351 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8747,12 +8747,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); - // If a custom `OnReceiveContentCallback` is set, pass its supported MIME types. - OnReceiveContentCallback receiver = getOnReceiveContentCallback(); - if (receiver != null) { - outAttrs.contentMimeTypes = receiver.getSupportedMimeTypes(this) - .toArray(new String[0]); - } + outAttrs.contentMimeTypes = getOnReceiveContentMimeTypes(); return ic; } } @@ -13734,20 +13729,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - /** - * Returns the callback used for handling insertion of content into this view. See - * {@link #setOnReceiveContentCallback} for more info. - * - * @return The callback for handling insertion of content. Returns null if no callback has been - * {@link #setOnReceiveContentCallback set}. - */ - @SuppressWarnings("unchecked") - @Nullable - @Override - public OnReceiveContentCallback getOnReceiveContentCallback() { - return (OnReceiveContentCallback) super.getOnReceiveContentCallback(); - } - /** * Sets the callback to handle insertion of content into this view. * @@ -13761,32 +13742,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener *

  5. {@link Intent#ACTION_PROCESS_TEXT} replacement *
* - *

The callback will only be invoked if the MIME type of the content is - * {@link OnReceiveContentCallback#getSupportedMimeTypes declared as supported} by the callback. - * If the content type is not supported by the callback, the default platform handling will be - * executed instead. + *

This callback is only invoked for content whose MIME type matches a type specified via + * the {code mimeTypes} parameter. If the MIME type is not supported by the callback, the + * default platform handling will be executed instead (no-op for the default {@link View}). + * + *

Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC + * MIME types. As a result, you should always write your MIME types with lower case letters, or + * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower + * case. * + * @param mimeTypes The type of content for which the callback should be invoked. This may use + * wildcards such as "text/*", "image/*", etc. This must not be null or empty if a non-null + * callback is passed in. * @param callback The callback to use. This can be null to reset to the default behavior. */ + @SuppressWarnings("rawtypes") @Override public void setOnReceiveContentCallback( - @Nullable OnReceiveContentCallback callback) { - super.setOnReceiveContentCallback(callback); + @Nullable String[] mimeTypes, + @Nullable OnReceiveContentCallback callback) { + super.setOnReceiveContentCallback(mimeTypes, callback); } /** - * Handles the request to insert content using the configured callback or the default callback. + * Receives the given content. The default implementation invokes the callback set via + * {@link #setOnReceiveContentCallback}. If no callback is set or if the callback does not + * support the given content (based on the MIME type), executes the default platform handling + * (e.g. coerces content to text if the source is + * {@link OnReceiveContentCallback.Payload#SOURCE_CLIPBOARD} and this is an editable + * {@link TextView}). * - * @hide + * @param payload The content to insert and related metadata. + * + * @return Returns true if the content was handled in some way, false otherwise. Actual + * insertion may be processed asynchronously in the background and may or may not succeed even + * if this method returns true. For example, an app may not end up inserting an item if it + * exceeds the app's size limit for that type of content. */ - void onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { - OnReceiveContentCallback receiver = getOnReceiveContentCallback(); - ClipDescription description = payload.getClip().getDescription(); - if (receiver != null && receiver.supports(this, description)) { - receiver.onReceiveContent(this, payload); + @Override + public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { + if (super.onReceiveContent(payload)) { + return true; } else if (mEditor != null) { - mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); + return mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); } + return false; } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { -- cgit v1.2.3 From 72f07d6a8a32db4a0dedd7682a0b3385be2b9cd6 Mon Sep 17 00:00:00 2001 From: Mathew Inwood Date: Tue, 27 Oct 2020 11:47:29 +0000 Subject: Add maxTargetSdk restriction to unused APIs. These are APIs that have @UnsupportedAppUsage but for which we don't have any evidence of them currently being used, so should be safe to remove from the unsupported list. Bug: 170729553 Test: Treehugger Change-Id: I4c8fd0006f950de9955242e93968fb0996ceb372 --- core/java/android/widget/TextView.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3fc0f4efd608..d8b4c748b283 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4364,7 +4364,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener shouldRequestLayout); } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void setRawTextSize(float size, boolean shouldRequestLayout) { if (size != mTextPaint.getTextSize()) { mTextPaint.setTextSize(size); @@ -7876,7 +7876,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return drawableState; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Path getUpdatedHighlightPath() { Path highlight = null; Paint highlightPaint = mHighlightPaint; @@ -12359,7 +12359,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * be {@code null} if no text is set */ @Nullable - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private CharSequence getTextForAccessibility() { // If the text is empty, we must be showing the hint text. if (TextUtils.isEmpty(mText)) { @@ -12501,7 +12501,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) CharSequence getTransformedText(int start, int end) { return removeSuggestionSpans(mTransformed.subSequence(start, end)); } @@ -12989,7 +12989,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return x; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. @@ -13178,7 +13178,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Deletes the range of text [start, end[. * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected void deleteText_internal(int start, int end) { ((Editable) mText).delete(start, end); } @@ -13230,7 +13230,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ @Override - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public CharSequence getIterableTextForAccessibility() { return mText; } -- cgit v1.2.3 From 050275cd83f4a66dc3705714ccbe8db025626ce8 Mon Sep 17 00:00:00 2001 From: Hongwei Wang Date: Wed, 28 Oct 2020 19:38:11 +0000 Subject: Revert "Add maxTargetSdk restriction to unused APIs." This reverts commit 72f07d6a8a32db4a0dedd7682a0b3385be2b9cd6. Reason for revert: Droidcop-triggered revert due to breakage https://android-build.googleplex.com/builds/quarterdeck?testMethod=testAppZygotePreload&testClass=android.app.cts.ServiceTest&atpConfigName=suite%2Ftest-mapping-presubmit-retry_cloud-tf&testModule=CtsAppTestCases&fkbb=6936597&lkbb=6936969&lkgb=6936551&testResults=true&branch=git_master&target=cf_x86_phone-userdebug>, bug b/171886397 Bug: 171886397 Change-Id: Ibe0f0430a3451477c1ee8ef56a596e91ea1e7672 --- core/java/android/widget/TextView.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d8b4c748b283..3fc0f4efd608 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4364,7 +4364,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener shouldRequestLayout); } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage private void setRawTextSize(float size, boolean shouldRequestLayout) { if (size != mTextPaint.getTextSize()) { mTextPaint.setTextSize(size); @@ -7876,7 +7876,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return drawableState; } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage private Path getUpdatedHighlightPath() { Path highlight = null; Paint highlightPaint = mHighlightPaint; @@ -12359,7 +12359,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * be {@code null} if no text is set */ @Nullable - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage private CharSequence getTextForAccessibility() { // If the text is empty, we must be showing the hint text. if (TextUtils.isEmpty(mText)) { @@ -12501,7 +12501,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage CharSequence getTransformedText(int start, int end) { return removeSuggestionSpans(mTransformed.subSequence(start, end)); } @@ -12989,7 +12989,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return x; } - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. @@ -13178,7 +13178,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Deletes the range of text [start, end[. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage protected void deleteText_internal(int start, int end) { ((Editable) mText).delete(start, end); } @@ -13230,7 +13230,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ @Override - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage public CharSequence getIterableTextForAccessibility() { return mText; } -- cgit v1.2.3 From 8e742f928e0b3d242a290fb46d80a2c892dd18a3 Mon Sep 17 00:00:00 2001 From: Mathew Inwood Date: Tue, 27 Oct 2020 11:47:29 +0000 Subject: Add maxTargetSdk restriction to unused APIs. These are APIs that have @UnsupportedAppUsage but for which we don't have any evidence of them currently being used, so should be safe to remove from the unsupported list. This is a resubmit of ag/12929664 with some APIs excluded that caused test failures; see bugs 171886397, 171888296, 171864568. APIs excluded: Landroid/bluetooth/le/ScanRecord;->parseFromBytes([B)Landroid/bluetooth/le/ScanRecord; Landroid/os/Process;->myPpid()I Landroid/os/SharedMemory;->getFd()I Landroid/hardware/input/InputManager;->INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH:I Bug: 170729553 Test: Treehugger Change-Id: I8285daa8530260251ecad6f3f38f98e263629ca7 --- core/java/android/widget/TextView.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 5280a48596b4..3ac78bafdedc 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4364,7 +4364,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener shouldRequestLayout); } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void setRawTextSize(float size, boolean shouldRequestLayout) { if (size != mTextPaint.getTextSize()) { mTextPaint.setTextSize(size); @@ -7876,7 +7876,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return drawableState; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private Path getUpdatedHighlightPath() { Path highlight = null; Paint highlightPaint = mHighlightPaint; @@ -12354,7 +12354,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * be {@code null} if no text is set */ @Nullable - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private CharSequence getTextForAccessibility() { // If the text is empty, we must be showing the hint text. if (TextUtils.isEmpty(mText)) { @@ -12496,7 +12496,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) CharSequence getTransformedText(int start, int end) { return removeSuggestionSpans(mTransformed.subSequence(start, end)); } @@ -12984,7 +12984,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return x; } - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. @@ -13173,7 +13173,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Deletes the range of text [start, end[. * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) protected void deleteText_internal(int start, int end) { ((Editable) mText).delete(start, end); } @@ -13225,7 +13225,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ @Override - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public CharSequence getIterableTextForAccessibility() { return mText; } -- cgit v1.2.3 From a25346bf0cec67f9a1e8644bd0dff718adb73d36 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Wed, 28 Oct 2020 16:46:07 -0700 Subject: Update onReceiveContent() logic for app vs platform processing Previously onReceiveContent() would only invoke the app-configured callback if the MIME type of the content matched one of the declared MIME types for the callback. This change updates onReceiveContent() to always invoke the listener if one is set (regardless of the MIME type of the content). To delegate processing to the platform, the app's listener can return some or all of the passed-in content. To make this easy for apps to implement, the Payload class and its Builder now provide some convenience methods to conditionally partition the content. Reasons for this change: * Checking the MIME types could be an expensive operation. On SDKs prior to S, ClipData does not keep track of the MIME types of individual items, so for a ClipData that contains multiple items, checking the MIME types requires making at least one RPC call per item. * Allowing the listener to delegate processing to the platform via its return value enables us to limit the API surface (we don't need to expose TextViewOnReceiveContentListener as a public API, nor equivalent classes for other types of views such as WebView). * An app that wants to customize the platform behavior for coercing content to text would previously need to declare "*/*" as the MIME type for the callback (in order to be invoked for all content). But this would make it impossible for features to know whether the app would actually accept a particular type of content or just coerce it to text (e.g. should the soft keyboard show GIF suggestions when the declared MIME type is "*/*"). With the new logic the app's listener is always invoked and can decide which content to process vs delegate to the platform vs reject completely. Bug: 170191676 Bug: 152068298 Test: atest CtsViewTestCases:ViewOnReceiveContentTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentTest Test: atest FrameworksCoreTests:TextViewOnReceiveContentTest Change-Id: Ie48b6fe0b2ae4b014c371b5dc40248221947c6bf --- core/java/android/widget/TextView.java | 91 ++++++++++------------------------ 1 file changed, 27 insertions(+), 64 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3ac78bafdedc..9485753ce906 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,10 +17,10 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.view.OnReceiveContentCallback.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; -import static android.view.OnReceiveContentCallback.Payload.SOURCE_AUTOFILL; -import static android.view.OnReceiveContentCallback.Payload.SOURCE_CLIPBOARD; -import static android.view.OnReceiveContentCallback.Payload.SOURCE_PROCESS_TEXT; +import static android.view.OnReceiveContentListener.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; +import static android.view.OnReceiveContentListener.Payload.SOURCE_AUTOFILL; +import static android.view.OnReceiveContentListener.Payload.SOURCE_CLIPBOARD; +import static android.view.OnReceiveContentListener.Payload.SOURCE_PROCESS_TEXT; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; @@ -154,7 +154,7 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.OnReceiveContentCallback; +import android.view.OnReceiveContentListener.Payload; import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; @@ -2151,10 +2151,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (result != null) { if (isTextEditable()) { ClipData clip = ClipData.newPlainText("", result); - OnReceiveContentCallback.Payload payload = - new OnReceiveContentCallback.Payload.Builder( - clip, SOURCE_PROCESS_TEXT) - .build(); + Payload payload = new Payload.Builder(clip, SOURCE_PROCESS_TEXT).build(); onReceiveContent(payload); if (mEditor != null) { mEditor.refreshTextActionMode(); @@ -11858,8 +11855,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " cannot be autofilled into " + this); return; } - final OnReceiveContentCallback.Payload payload = - new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_AUTOFILL).build(); + final Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build(); onReceiveContent(payload); } @@ -12926,8 +12922,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (clip == null) { return; } - final OnReceiveContentCallback.Payload payload = - new OnReceiveContentCallback.Payload.Builder(clip, SOURCE_CLIPBOARD) + final Payload payload = new Payload.Builder(clip, SOURCE_CLIPBOARD) .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) .build(); onReceiveContent(payload); @@ -13717,7 +13712,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onInputConnectionOpenedInternal(@NonNull InputConnection ic, @NonNull EditorInfo editorInfo, @Nullable Handler handler) { if (mEditor != null) { - mEditor.getDefaultOnReceiveContentCallback().setInputConnectionInfo(ic, editorInfo); + mEditor.getDefaultOnReceiveContentListener().setInputConnectionInfo(this, ic, + editorInfo); } } @@ -13725,68 +13721,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public void onInputConnectionClosedInternal() { if (mEditor != null) { - mEditor.getDefaultOnReceiveContentCallback().clearInputConnectionInfo(); + mEditor.getDefaultOnReceiveContentListener().clearInputConnectionInfo(); } } /** - * Sets the callback to handle insertion of content into this view. + * Receives the given content. Clients wishing to provide custom behavior should configure a + * listener via {@link #setOnReceiveContentListener}. * - *

This callback will be invoked for the following scenarios: - *

    - *
  1. Paste from the clipboard (e.g. "Paste" or "Paste as plain text" action in the - * insertion/selection menu) - *
  2. Content insertion from the keyboard (from {@link InputConnection#commitContent}) - *
  3. Drag and drop (drop events from {@link #onDragEvent(DragEvent)}) - *
  4. Autofill (from {@link #autofill(AutofillValue)}) - *
  5. {@link Intent#ACTION_PROCESS_TEXT} replacement - *
+ *

If a listener is set, invokes the listener. If the listener returns a non-null result, + * executes the default platform handling for the portion of the content returned by the + * listener. * - *

This callback is only invoked for content whose MIME type matches a type specified via - * the {code mimeTypes} parameter. If the MIME type is not supported by the callback, the - * default platform handling will be executed instead (no-op for the default {@link View}). - * - *

Note: MIME type matching in the Android framework is case-sensitive, unlike formal RFC - * MIME types. As a result, you should always write your MIME types with lower case letters, or - * use {@link android.content.Intent#normalizeMimeType} to ensure that it is converted to lower - * case. - * - * @param mimeTypes The type of content for which the callback should be invoked. This may use - * wildcards such as "text/*", "image/*", etc. This must not be null or empty if a non-null - * callback is passed in. - * @param callback The callback to use. This can be null to reset to the default behavior. - */ - @SuppressWarnings("rawtypes") - @Override - public void setOnReceiveContentCallback( - @Nullable String[] mimeTypes, - @Nullable OnReceiveContentCallback callback) { - super.setOnReceiveContentCallback(mimeTypes, callback); - } - - /** - * Receives the given content. The default implementation invokes the callback set via - * {@link #setOnReceiveContentCallback}. If no callback is set or if the callback does not - * support the given content (based on the MIME type), executes the default platform handling - * (e.g. coerces content to text if the source is - * {@link OnReceiveContentCallback.Payload#SOURCE_CLIPBOARD} and this is an editable - * {@link TextView}). + *

If no listener is set, executes the default platform behavior. For non-editable TextViews + * the default behavior is a no-op (returns the passed-in content without acting on it). For + * editable TextViews the default behavior coerces all content to text and inserts into the + * view. * * @param payload The content to insert and related metadata. * - * @return Returns true if the content was handled in some way, false otherwise. Actual - * insertion may be processed asynchronously in the background and may or may not succeed even - * if this method returns true. For example, an app may not end up inserting an item if it - * exceeds the app's size limit for that type of content. + * @return The portion of the passed-in content that was not handled (may be all, some, or none + * of the passed-in content). */ @Override - public boolean onReceiveContent(@NonNull OnReceiveContentCallback.Payload payload) { - if (super.onReceiveContent(payload)) { - return true; - } else if (mEditor != null) { - return mEditor.getDefaultOnReceiveContentCallback().onReceiveContent(this, payload); + public @Nullable Payload onReceiveContent(@NonNull Payload payload) { + Payload remaining = super.onReceiveContent(payload); + if (remaining != null && mEditor != null) { + return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, remaining); } - return false; + return remaining; } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { -- cgit v1.2.3 From ba819fcef4766e87f493989e4503abbb06635dd9 Mon Sep 17 00:00:00 2001 From: Sally Date: Tue, 3 Nov 2020 19:44:41 +0000 Subject: Change forceBoldText to fontWeightAdjustment in TextView Use the weight adjustment instead of a binary on/off Bug: b/170966021, b/110991537 Test: Manual with bold text toggle, atest CtsWidgetTestCases:TextViewTest, CtsTextTestCases Change-Id: Ic60ca7eed497bfc798d653a07824a9e5fabe5bc2 --- core/java/android/widget/TextView.java | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9485753ce906..fb13807495e6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -742,8 +742,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mLocalesChanged = false; private int mTextSizeUnit = -1; - // True if force bold text feature is enabled. This feature makes all text bolder. - private boolean mForceBoldTextEnabled; + // This is used to reflect the current user preference for changing font weight and making text + // more bold. + private int mFontWeightAdjustment; private Typeface mOriginalTypeface; // True if setKeyListener() has been explicitly called @@ -1647,8 +1648,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener attributes.mTypefaceIndex = MONOSPACE; } - mForceBoldTextEnabled = getContext().getResources().getConfiguration().forceBoldText - == Configuration.FORCE_BOLD_TEXT_YES; + mFontWeightAdjustment = getContext().getResources().getConfiguration().fontWeightAdjustment; applyTextAppearance(attributes); if (isPassword) { @@ -4273,12 +4273,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener invalidate(); } } - if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_YES) { - mForceBoldTextEnabled = true; - setTypeface(getTypeface()); - } else if (newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_NO - || newConfig.forceBoldText == Configuration.FORCE_BOLD_TEXT_UNDEFINED) { - mForceBoldTextEnabled = false; + if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) { + mFontWeightAdjustment = newConfig.fontWeightAdjustment; setTypeface(getTypeface()); } } @@ -4433,12 +4429,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setTypeface(@Nullable Typeface tf) { mOriginalTypeface = tf; - if (mForceBoldTextEnabled) { - int newWeight = tf != null ? tf.getWeight() + 300 : 400; - newWeight = Math.min(newWeight, 1000); - int typefaceStyle = tf != null ? tf.getStyle() : 0; - boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; - tf = Typeface.create(tf, newWeight, italic); + if (mFontWeightAdjustment != 0 + && mFontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { + if (tf == null) { + tf = Typeface.DEFAULT; + } else { + int newWeight = Math.min( + Math.max(tf.getWeight() + mFontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN), + FontStyle.FONT_WEIGHT_MAX); + int typefaceStyle = tf != null ? tf.getStyle() : 0; + boolean italic = (typefaceStyle & Typeface.ITALIC) != 0; + tf = Typeface.create(tf, newWeight, italic); + } } if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); -- cgit v1.2.3 From 53bf38e9ffe7f699ce87ed621fbee7c4a0fc144a Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Mon, 16 Nov 2020 16:26:00 -0800 Subject: Add View.performReceiveContent in addition to View.onReceiveContent Bug: 170191676 Bug: 152068298 Test: atest CtsViewTestCases:ViewOnReceiveContentTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentTest Test: atest FrameworksCoreTests:TextViewOnReceiveContentTest Change-Id: I3a3a3ec3b386c5af9c7e997fb32201fefa564545 --- core/java/android/widget/TextView.java | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fb13807495e6..98f808784803 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2152,7 +2152,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (isTextEditable()) { ClipData clip = ClipData.newPlainText("", result); Payload payload = new Payload.Builder(clip, SOURCE_PROCESS_TEXT).build(); - onReceiveContent(payload); + performReceiveContent(payload); if (mEditor != null) { mEditor.refreshTextActionMode(); } @@ -11858,7 +11858,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } final Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build(); - onReceiveContent(payload); + performReceiveContent(payload); } @Override @@ -12927,7 +12927,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final Payload payload = new Payload.Builder(clip, SOURCE_CLIPBOARD) .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) .build(); - onReceiveContent(payload); + performReceiveContent(payload); sLastCutCopyOrTextChangedTime = 0; } @@ -13728,17 +13728,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Receives the given content. Clients wishing to provide custom behavior should configure a - * listener via {@link #setOnReceiveContentListener}. + * Default {@link TextView} implementation for receiving content. Apps wishing to provide + * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. * - *

If a listener is set, invokes the listener. If the listener returns a non-null result, - * executes the default platform handling for the portion of the content returned by the - * listener. - * - *

If no listener is set, executes the default platform behavior. For non-editable TextViews - * the default behavior is a no-op (returns the passed-in content without acting on it). For - * editable TextViews the default behavior coerces all content to text and inserts into the - * view. + *

For non-editable TextViews the default behavior is a no-op (returns the passed-in + * content without acting on it). For editable TextViews the default behavior coerces all + * content to text and inserts into the view. * * @param payload The content to insert and related metadata. * @@ -13747,11 +13742,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public @Nullable Payload onReceiveContent(@NonNull Payload payload) { - Payload remaining = super.onReceiveContent(payload); - if (remaining != null && mEditor != null) { - return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, remaining); + if (mEditor != null) { + return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); } - return remaining; + return payload; } private static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { -- cgit v1.2.3 From 10f027f69f21c59d29358b8ec5978595b64c3dbf Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Fri, 27 Nov 2020 15:24:54 -0800 Subject: Move/rename OnReceiveContentListener.Payload to android.view.ContentInfo Bug: 174125311 Test: atest CtsViewTestCases:ViewOnReceiveContentTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentTest Test: atest FrameworksCoreTests:TextViewOnReceiveContentTest Change-Id: If3988dfe9dbed6bcd5e1f1566cb3ecdb111aef3f --- core/java/android/widget/TextView.java | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 98f808784803..2357f368c428 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,10 +17,10 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; -import static android.view.OnReceiveContentListener.Payload.FLAG_CONVERT_TO_PLAIN_TEXT; -import static android.view.OnReceiveContentListener.Payload.SOURCE_AUTOFILL; -import static android.view.OnReceiveContentListener.Payload.SOURCE_CLIPBOARD; -import static android.view.OnReceiveContentListener.Payload.SOURCE_PROCESS_TEXT; +import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT; +import static android.view.ContentInfo.SOURCE_AUTOFILL; +import static android.view.ContentInfo.SOURCE_CLIPBOARD; +import static android.view.ContentInfo.SOURCE_PROCESS_TEXT; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_RENDERING_INFO_KEY; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; @@ -146,6 +146,7 @@ import android.util.TypedValue; import android.view.AccessibilityIterators.TextSegmentIterator; import android.view.ActionMode; import android.view.Choreographer; +import android.view.ContentInfo; import android.view.ContextMenu; import android.view.DragEvent; import android.view.Gravity; @@ -154,7 +155,6 @@ import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.OnReceiveContentListener.Payload; import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; @@ -2151,7 +2151,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (result != null) { if (isTextEditable()) { ClipData clip = ClipData.newPlainText("", result); - Payload payload = new Payload.Builder(clip, SOURCE_PROCESS_TEXT).build(); + ContentInfo payload = + new ContentInfo.Builder(clip, SOURCE_PROCESS_TEXT).build(); performReceiveContent(payload); if (mEditor != null) { mEditor.refreshTextActionMode(); @@ -11857,7 +11858,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " cannot be autofilled into " + this); return; } - final Payload payload = new Payload.Builder(clip, SOURCE_AUTOFILL).build(); + final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); performReceiveContent(payload); } @@ -12924,7 +12925,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (clip == null) { return; } - final Payload payload = new Payload.Builder(clip, SOURCE_CLIPBOARD) + final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_CLIPBOARD) .setFlags(withFormatting ? 0 : FLAG_CONVERT_TO_PLAIN_TEXT) .build(); performReceiveContent(payload); @@ -13740,8 +13741,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @return The portion of the passed-in content that was not handled (may be all, some, or none * of the passed-in content). */ + @Nullable @Override - public @Nullable Payload onReceiveContent(@NonNull Payload payload) { + public ContentInfo onReceiveContent(@NonNull ContentInfo payload) { if (mEditor != null) { return mEditor.getDefaultOnReceiveContentListener().onReceiveContent(this, payload); } -- cgit v1.2.3 From b11b963ed14d952cc2eb85e272780f92fa5b8b09 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Mon, 16 Nov 2020 10:41:58 -0800 Subject: Prepare switching font loading to system server. Currently, fonts are loaded in Zygote. This CL adds a preparation to switch it to system server and bindApplication(), so that system server will be able to update font map at runtime. (1) Zygote will be initialized without fonts. (2) System server will maintain a serialized font map in ashmem. (3) Apps will load font map from the ashmem in bindApplication(). The change is guarded by Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION, and the new behavior is disabled by default. I tested with ENABLE_LAZY_TYPEFACE_INITIALIZATION = true. Bug: 172891184 Test: atest FrameworksCoreTests:TypefaceTest Test: atest CtsGraphicsTestCases Test: atest CtsTextTestCases Test: atest CtsWidgetTestCases Change-Id: I40832962a4b27f6160c4dc6268689c52f6a4dd33 --- core/java/android/widget/TextView.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2357f368c428..02a930017906 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -964,6 +964,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ public static void preloadFontCache() { + if (Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) { + return; + } Paint p = new Paint(); p.setAntiAlias(true); // Ensure that the Typeface is loaded here. -- cgit v1.2.3 From c9f90b56df50d8682dee5afddebe3e8a89690fbd Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Mon, 14 Dec 2020 22:04:57 -0800 Subject: Hide ContentInfo.partition from the public API Per API Council feedback, this method will not be exposed in the public API. App developers will instead use the support library for this. Rather than removing the method completely, it is marked with @hide so that it can still be used in system code (e.g. RemoteInputView). Bug: 174125311 Test: atest CtsViewTestCases:ContentInfoTest Change-Id: I83b55bea38c0d2b3dc60a16658616e46ebe4cb48 --- core/java/android/widget/TextView.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 02a930017906..40a372c8c2df 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13736,8 +13736,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * custom behavior should configure a listener via {@link #setOnReceiveContentListener}. * *

For non-editable TextViews the default behavior is a no-op (returns the passed-in - * content without acting on it). For editable TextViews the default behavior coerces all - * content to text and inserts into the view. + * content without acting on it). + * + *

For editable TextViews the default behavior is to insert text into the view, coercing + * non-text content to text as needed. The MIME types "text/plain" and "text/html" have + * well-defined behavior for this, while other MIME types have reasonable fallback behavior + * (see {@link ClipData.Item#coerceToStyledText}). * * @param payload The content to insert and related metadata. * -- cgit v1.2.3 From e751e1036a0467e3aa2c8ecd23266b65eccea4d7 Mon Sep 17 00:00:00 2001 From: Kohsuke Yatoh Date: Thu, 3 Dec 2020 00:10:19 +0000 Subject: Add InputConnection#performSpellCheck(). IME can use this method to redo spell checking after it has learned a new user dictionary word. Bug: 166304720 Test: atest CtsInputMethodTestCases:SpellCheckerTest Change-Id: I956cd46f25bb77b7e1a6a3e1478c0d7efa1a056a --- core/java/android/widget/TextView.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 02a930017906..ffe1b382cfda 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8913,6 +8913,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // intentionally empty } + /** @hide */ + public void onPerformSpellCheck() { + if (mEditor != null && mEditor.mSpellChecker != null) { + mEditor.mSpellChecker.onPerformSpellCheck(); + } + } + /** * Called by the framework in response to a private command from the * current method, provided by it calling -- cgit v1.2.3 From dc211bfac3e8f6e75f8b9e7a6f31c112f30d4901 Mon Sep 17 00:00:00 2001 From: Taran Singh Date: Wed, 18 Nov 2020 15:04:27 -0800 Subject: Prevent Fullscreen IME when app is in portrait. When an app is running in portrait orientation, regardless of what orientation display is in, IME shouldn't use fullscreen-mode. Setting IME_FLAG_NO_FULLSCREEN in EditorInfo makes sure IME doesn't go fullscreen. Bug: 157870379 Test: Manually using steps in bug Change-Id: I5a5e73e1dec776665f28a7e2eb091b555198001b --- core/java/android/widget/TextView.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 12c91fac0c65..977a0e89a143 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -17,6 +17,7 @@ package android.widget; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.ContentInfo.FLAG_CONVERT_TO_PLAIN_TEXT; import static android.view.ContentInfo.SOURCE_AUTOFILL; import static android.view.ContentInfo.SOURCE_CLIPBOARD; @@ -8738,6 +8739,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; } } + if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { + outAttrs.internalImeOptions |= EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT; + } if (isMultilineInputType(outAttrs.inputType)) { // Multi-line text editors should always show an enter key. outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; -- cgit v1.2.3 From 7c2e5bb7c5e6ee426ad05cca5d70b1bc7d6be11c Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Mon, 28 Dec 2020 15:09:29 -0800 Subject: Remove AUTOFILL_TYPE_RICH_CONTENT and related code This reverts the autofill API changes made in ag/9884022 (only the autofill-related changes in that CL, not the rest). This is because non-text content in autofill is now handled via a separate code path rather than via an autofill type (the new pipe for rich content was added in ag/12642608). Bug: 168837034 Test: Manual (image suggestions in augmented autofill) Test: atest FrameworksCoreTests:AutofillValueTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentTest Change-Id: I261148646d21474ea10662a3e6a5d8b0ba6708f4 --- core/java/android/widget/TextView.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 977a0e89a143..4edfc5f309b2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11862,16 +11862,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "cannot autofill non-editable TextView: " + this); return; } - final ClipData clip; - if (value.isRichContent()) { - clip = value.getRichContentValue(); - } else if (value.isText()) { - clip = ClipData.newPlainText("", value.getTextValue()); - } else { + if (!value.isText()) { Log.w(LOG_TAG, "value of type " + value.describeContents() + " cannot be autofilled into " + this); return; } + final ClipData clip = ClipData.newPlainText("", value.getTextValue()); final ContentInfo payload = new ContentInfo.Builder(clip, SOURCE_AUTOFILL).build(); performReceiveContent(payload); } -- cgit v1.2.3 From 30fe6aa9648e6138f7cf2e58105a8affbec91364 Mon Sep 17 00:00:00 2001 From: Lan Wei Date: Fri, 11 Dec 2020 18:15:13 +0800 Subject: IME API: InputConnecton#setImeTemporarilyConsumesInput(boolean) Add API to set IME will temporarily consumes the input, for this case, the cursor will be invisible in the app. Bug: 175362887 Test: atest FrameworksCoreTests:TextViewTest Test: atest CtsWidgetTestCases:TextViewTest Change-Id: Ic04cacfd73f1f4cb254bb16caf6b04c00c91a318 --- core/java/android/widget/TextView.java | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 977a0e89a143..8be9021a44f2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -494,6 +494,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextUtils.TruncateAt mEllipsize; + // A flag to indicate the cursor was hidden by IME. + private boolean mImeTemporarilyConsumesInput; + + // Whether cursor is visible without regard to {@link mImeTemporarilyConsumesInput}. + // {code true} is the default value. + private boolean mCursorVisibleFromAttr = true; + static class Drawables { static final int LEFT = 0; static final int TOP = 1; @@ -10496,7 +10503,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Set whether the cursor is visible. The default is true. Note that this property only - * makes sense for editable TextView. + * makes sense for editable TextView. If IME is temporarily consuming the input, the cursor will + * be always invisible, visibility will be updated as the last state when IME does not consume + * the input anymore. * * @see #isCursorVisible() * @@ -10504,6 +10513,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @android.view.RemotableViewMethod public void setCursorVisible(boolean visible) { + mCursorVisibleFromAttr = visible; + updateCursorVisibleInternal(); + } + + /** + * Sets the IME is temporarily consuming the input and make the cursor invisible if + * {@code imeTemporarilyConsumesInput} is {@code true}. Otherwise, make the cursor visible. + * + * @param imeTemporarilyConsumesInput {@code true} if IME is temporarily consuming the input + * + * @hide + */ + public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { + mImeTemporarilyConsumesInput = imeTemporarilyConsumesInput; + updateCursorVisibleInternal(); + } + + private void updateCursorVisibleInternal() { + boolean visible = mCursorVisibleFromAttr && !mImeTemporarilyConsumesInput; if (visible && mEditor == null) return; // visible is the default value with no edit data createEditorIfNeeded(); if (mEditor.mCursorVisible != visible) { @@ -10518,7 +10546,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * @return whether or not the cursor is visible (assuming this TextView is editable) + * @return whether or not the cursor is visible (assuming this TextView is editable). This + * method may return {@code false} when the IME is temporarily consuming the input even if the + * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} + * is called. * * @see #setCursorVisible(boolean) * -- cgit v1.2.3 From e8f7c4ba7c7a39c7294eed0a37e9b69ca93310f6 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Wed, 9 Sep 2020 15:45:38 -0700 Subject: Add MIME types to ViewStructure and autofill/cc ViewNode classes Bug: 168253885 Test: atest FrameworksCoreTests:TextViewOnReceiveContentTest Test: Manual CTS-Coverage-Bug: 170893906 Change-Id: Iac8f73a7a022f20f59ecef9be89e3189ca106e4c --- core/java/android/widget/TextView.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4edfc5f309b2..2a21c3aa2e57 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11686,6 +11686,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } + String[] mimeTypes = getOnReceiveContentMimeTypes(); + if (mimeTypes == null && mEditor != null) { + // If the app hasn't set a listener for receiving content on this view (ie, + // getOnReceiveContentMimeTypes() returns null), check if it implements the + // keyboard image API and, if possible, use those MIME types as fallback. + // This fallback is only in place for autofill, not other mechanisms for + // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER + // in TextViewOnReceiveContentListener for more info. + mimeTypes = mEditor.getDefaultOnReceiveContentListener() + .getFallbackMimeTypesForAutofill(this); + } + structure.setOnReceiveContentMimeTypes(mimeTypes); } if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL -- cgit v1.2.3 From a5fa9bc0f7d861b9a083aab3a43b60da1e19b9af Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Wed, 30 Dec 2020 00:05:02 +0800 Subject: Add initial View's APIs for auto translation The initial APIs for auto translation flow. The implementation of sending request and dealing spans will on the follow up changes. Bug: 172969740 Test: manual build pass. Use a sample app to call api get expected result. Change-Id: Ief45190026e6205ff18366c1aaba8f424a4df00c --- core/java/android/widget/TextView.java | 120 +++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b8660255acc1..8cfbca88c596 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -129,6 +129,7 @@ import android.text.method.TextKeyListener; import android.text.method.TimeKeyListener; import android.text.method.TransformationMethod; import android.text.method.TransformationMethod2; +import android.text.method.TranslationTransformationMethod; import android.text.method.WordIterator; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; @@ -193,6 +194,7 @@ import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; +import android.view.translation.TranslationRequest; import android.widget.RemoteViews.RemoteView; import com.android.internal.annotations.VisibleForTesting; @@ -732,6 +734,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private MovementMethod mMovement; private TransformationMethod mTransformation; + private TranslationTransformationMethod mTranslationTransformation; @UnsupportedAppUsage private boolean mAllowTransformationLengthChange; @UnsupportedAppUsage @@ -13814,4 +13817,121 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.d(LOG_TAG, location + ": " + String.format(msgFormat, msgArgs)); } } + + /** + * Provides a {@link TranslationRequest} that represents the content to be translated via + * translation service. + * + *

NOTE: When overriding the method, it should not translate the password. We also suggest + * that not translating the text is selectable or editable. We use the transformation method to + * implement showing the translated text. The TextView does not support the transformation + * method text length change. If the text is selectable or editable, it will crash while + * selecting the text. To support it, it needs broader changes to text APIs, we only allow to + * translate non selectable and editable text now. + * + * @hide + */ + @Nullable + @Override + public TranslationRequest onCreateTranslationRequest() { + if (mText == null || mText.length() == 0) { + return null; + } + // Not translate password, editable text and not important for translation + // TODO(b/177214256): support selectable text translation. It needs to broader changes to + // text selection apis, not support in S. + boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); + if (isTextEditable() || isPassword || isTextSelectable()) { + return null; + } + // TODO(b/176488462): apply the view's important for translation property + // TODO(b/174283799): remove the spans from the mText and save the spans informatopn + TranslationRequest request = + new TranslationRequest.Builder() + .setAutofillId(getAutofillId()) + .setTranslationText(mText) + .build(); + return request; + } + + /** + * Provides the implementation that pauses the ongoing Ui translation, it will show the original + * text instead of the translated text and restore the original transformation method. + * + *

NOTE: If this method is overridden, other translation related methods such as + * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} + * should also be overridden. + * + * @hide + */ + @Override + public void onPauseUiTranslation() { + // Restore to original text content. + if (mTranslationTransformation != null) { + setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); + } + } + + /** + * Provides the implementation that restoes the paused Ui translation, it will show the + * translated text again if the text had been translated. This method will replace the current + * tansformation method with {@link TranslationTransformationMethod}. + * + *

NOTE: If this method is overridden, other translation related methods such as + * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} + * should also be overridden. + * + * @hide + */ + @Override + public void onRestoreUiTranslation() { + if (mTranslationTransformation != null) { + setTransformationMethod(mTranslationTransformation); + } else { + Log.w(LOG_TAG, "onResumeTranslatedText(): no translated text."); + } + } + + /** + * Provides the implementation that finishes the current Ui translation and it's no longer to + * show the translated text. This method restores the original transformation method and resets + * the saved {@link TranslationTransformationMethod}. + * + *

NOTE: If this method is overridden, other translation related methods such as + * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete} + * should also be overridden. + * + * @hide + */ + @Override + public void onFinishUiTranslation() { + // Restore to original text content and clear TranslationTransformation + if (mTranslationTransformation != null) { + setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); + mTranslationTransformation = null; + } + } + + /** + * Default {@link TextView} implementation after the translation request is done by the + * translation service, it's ok to show the translated text. This method will save the original + * transformation method and replace the current transformation method with + * {@link TranslationTransformationMethod}. + * + *

NOTE: If this method is overridden, other translation related methods such as + * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation} + * should also be overridden. + * + * @hide + */ + @Override + public void onTranslationComplete(@NonNull TranslationRequest data) { + // Show the translated text. + TransformationMethod originalTranslationMethod = mTranslationTransformation != null + ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation; + mTranslationTransformation = + new TranslationTransformationMethod(data, originalTranslationMethod); + // TODO(b/178353965): well-handle setTransformationMethod. + setTransformationMethod(mTranslationTransformation); + } } -- cgit v1.2.3 From 2b4bd4c6fba5500e2c72d4d08208403f501895d2 Mon Sep 17 00:00:00 2001 From: Taran Singh Date: Fri, 29 Jan 2021 16:29:04 +0000 Subject: Make EditorInfo.internalImeOptions parcelable. Followup to I5a5e73e1dec776665f28a7e2eb091b555198001b. internalImeOptions field should be parcelable. Fix: 157870379 Test: Manually using steps in bug Change-Id: I12b442b8b2d8cf83aa4cc789133b42251ad2c191 --- core/java/android/widget/TextView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8cfbca88c596..796607065944 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8750,7 +8750,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT) { - outAttrs.internalImeOptions |= EditorInfo.IME_FLAG_APP_WINDOW_PORTRAIT; + outAttrs.internalImeOptions |= EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT; } if (isMultilineInputType(outAttrs.inputType)) { // Multi-line text editors should always show an enter key. -- cgit v1.2.3 From 66b21086e39ee2f41abaa10a122124b5182be0b8 Mon Sep 17 00:00:00 2001 From: Lan Wei Date: Fri, 5 Feb 2021 08:59:11 +0800 Subject: Rename InputConnecton#setImeTemporarilyConsumesInput Rename InputConnecton#setImeTemporarilyConsumesInput as InputConnecton#setImeConsumesInput Bug: 175362887 Bug: 179143513 Test: atest FrameworksCoreTests:TextViewTest Test: atest CtsWidgetTestCases:TextViewTest Change-Id: I8ac1ea4d53f747b0086ed415ff90793dfc6155bc --- core/java/android/widget/TextView.java | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8cfbca88c596..946a05420df9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -497,9 +497,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private TextUtils.TruncateAt mEllipsize; // A flag to indicate the cursor was hidden by IME. - private boolean mImeTemporarilyConsumesInput; + private boolean mImeIsConsumingInput; - // Whether cursor is visible without regard to {@link mImeTemporarilyConsumesInput}. + // Whether cursor is visible without regard to {@link mImeConsumesInput}. // {code true} is the default value. private boolean mCursorVisibleFromAttr = true; @@ -10506,8 +10506,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Set whether the cursor is visible. The default is true. Note that this property only - * makes sense for editable TextView. If IME is temporarily consuming the input, the cursor will - * be always invisible, visibility will be updated as the last state when IME does not consume + * makes sense for editable TextView. If IME is consuming the input, the cursor will always be + * invisible, visibility will be updated as the last state when IME does not consume * the input anymore. * * @see #isCursorVisible() @@ -10521,20 +10521,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Sets the IME is temporarily consuming the input and make the cursor invisible if - * {@code imeTemporarilyConsumesInput} is {@code true}. Otherwise, make the cursor visible. + * Sets the IME is consuming the input and make the cursor invisible if {@code imeConsumesInput} + * is {@code true}. Otherwise, make the cursor visible. * - * @param imeTemporarilyConsumesInput {@code true} if IME is temporarily consuming the input + * @param imeConsumesInput {@code true} if IME is consuming the input * * @hide */ - public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) { - mImeTemporarilyConsumesInput = imeTemporarilyConsumesInput; + public void setImeConsumesInput(boolean imeConsumesInput) { + mImeIsConsumingInput = imeConsumesInput; updateCursorVisibleInternal(); } private void updateCursorVisibleInternal() { - boolean visible = mCursorVisibleFromAttr && !mImeTemporarilyConsumesInput; + boolean visible = mCursorVisibleFromAttr && !mImeIsConsumingInput; if (visible && mEditor == null) return; // visible is the default value with no edit data createEditorIfNeeded(); if (mEditor.mCursorVisible != visible) { @@ -10550,7 +10550,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @return whether or not the cursor is visible (assuming this TextView is editable). This - * method may return {@code false} when the IME is temporarily consuming the input even if the + * method may return {@code false} when the IME is consuming the input even if the * {@code mEditor.mCursorVisible} attribute is {@code true} or {@code #setCursorVisible(true)} * is called. * -- cgit v1.2.3 From 4d5c48d9880d6bb0d5c2d55a145f435d725ebbdc Mon Sep 17 00:00:00 2001 From: Oli Lan Date: Thu, 4 Feb 2021 18:06:16 +0000 Subject: Add ClipDescription method to determine if the clip is styled text. This adds a new method to ClipDescription to allow callers to determine if the associated ClipData is styled text, without accessing the ClipData directly. This information is used by editors such as TextView or WebView when they show a selection toolbar, in order to determine if a "Paste as plain text" option should be shown. Currently, ClipData must be accessed in order to do this, so a clipboard access occurs whenever text is selected. Avoiding these clipboard accesses is desirable because a user-visible message may in future be shown whenever ClipData is accessed (see b/167676460), which may cause user confusion. Bug: 167660455 Test: new tests added to ClipDescriptionTest in CTS Change-Id: I48bea4d11a68b198d60016b364a3cfa0b0bf277e --- core/java/android/widget/TextView.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fe37c5350511..0f2089a5463f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -12940,17 +12940,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); - final ClipDescription description = clipData.getDescription(); + final ClipDescription description = + getClipboardManagerForUser().getPrimaryClipDescription(); final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); - final CharSequence text = clipData.getItemAt(0).getText(); - if (isPlainType && (text instanceof Spanned)) { - Spanned spanned = (Spanned) text; - if (TextUtils.hasStyleSpan(spanned)) { - return true; - } - } - return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); + return (isPlainType && description.isStyledText()) + || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); } boolean canProcessText() { -- cgit v1.2.3 From 41c60e0ccb0a5b952df3325c85b47cf2d7e74f39 Mon Sep 17 00:00:00 2001 From: Stevie Kideckel Date: Thu, 11 Feb 2021 17:13:40 +0000 Subject: Expose tint lists and other runtime functionality to RemoteViews Bug: 180086170 Test: sample app using new methods, atest Change-Id: I5aa5476d95d6c9fb992c4548d97f577234d747a7 --- core/java/android/widget/TextView.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0f2089a5463f..ca0747fadf14 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4756,6 +4756,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #getJustificationMode() */ @Layout.JustificationMode + @android.view.RemotableViewMethod public void setJustificationMode(@Layout.JustificationMode int justificationMode) { mJustificationMode = justificationMode; if (mLayout != null) { @@ -5232,6 +5233,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */ + @android.view.RemotableViewMethod public void setGravity(int gravity) { if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { gravity |= Gravity.START; @@ -5826,6 +5828,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @attr ref android.R.styleable#TextView_lineHeight */ + @android.view.RemotableViewMethod public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { Preconditions.checkArgumentNonnegative(lineHeight); @@ -10277,6 +10280,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #setTransformationMethod(TransformationMethod) * @attr ref android.R.styleable#TextView_textAllCaps */ + @android.view.RemotableViewMethod public void setAllCaps(boolean allCaps) { if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); -- cgit v1.2.3 From 41de37482ecfa80aed077fcc1d2196fad30afca6 Mon Sep 17 00:00:00 2001 From: Lan Wei Date: Wed, 3 Feb 2021 16:44:31 +0800 Subject: Update the cursor visibility correctly after dismissing popup window The cursor visibilty should be resumed to previous without regard to whether IME is temporarily cousming input. Test: atest FrameworksCoreTests:SuggestionsPopupWindowTest Bug: 179088361 Bug: 175362887 Change-Id: Ibcae7ef47d942b47c00734bd55c6712e0edc3990 --- core/java/android/widget/TextView.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0f2089a5463f..66d7463cf267 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -500,7 +500,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mImeIsConsumingInput; // Whether cursor is visible without regard to {@link mImeConsumesInput}. - // {code true} is the default value. + // {@code true} is the default value. private boolean mCursorVisibleFromAttr = true; static class Drawables { @@ -10564,6 +10564,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mEditor == null ? true : mEditor.mCursorVisible; } + /** + * @return whether cursor is visible without regard to {@code mImeIsConsumingInput}. + * {@code true} is the default value. + * + * @see #setCursorVisible(boolean) + * @hide + */ + public boolean isCursorVisibleFromAttr() { + return mCursorVisibleFromAttr; + } + private boolean canMarquee() { int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); return width > 0 && (mLayout.getLineWidth(0) > width -- cgit v1.2.3 From 59716789f48d5c572c5e2dea7b8f4e21894a7002 Mon Sep 17 00:00:00 2001 From: Adam He Date: Tue, 2 Feb 2021 16:34:31 -0800 Subject: Full refactor of Translation APIs. * Added ViewTranslationRequest/Response * Added TranslationRequest/ResponseValue * Removed single TranslationData. * Moved service.TRequest -> view.TRequest. Bug: 176208267 Bug: 177371091 Test: atst CtsTranslationTestCases Change-Id: I25d4bce385d64a8eb9c2a316bd6b5a23e14fb14e --- core/java/android/widget/TextView.java | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ca0747fadf14..fdc66fcb81d8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -194,7 +194,9 @@ import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; -import android.view.translation.TranslationRequest; +import android.view.translation.TranslationRequestValue; +import android.view.translation.ViewTranslationRequest; +import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; import com.android.internal.annotations.VisibleForTesting; @@ -13817,7 +13819,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Provides a {@link TranslationRequest} that represents the content to be translated via + * Provides a {@link ViewTranslationRequest} that represents the content to be translated via * translation service. * *

NOTE: When overriding the method, it should not translate the password. We also suggest @@ -13831,7 +13833,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Nullable @Override - public TranslationRequest onCreateTranslationRequest() { + public ViewTranslationRequest onCreateTranslationRequest() { if (mText == null || mText.length() == 0) { return null; } @@ -13843,11 +13845,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return null; } // TODO(b/176488462): apply the view's important for translation property - // TODO(b/174283799): remove the spans from the mText and save the spans informatopn - TranslationRequest request = - new TranslationRequest.Builder() - .setAutofillId(getAutofillId()) - .setTranslationText(mText) + // TODO(b/174283799): remove the spans from the mText and save the spans information + // TODO: use fixed ids for request texts. + ViewTranslationRequest request = + new ViewTranslationRequest.Builder(getAutofillId()) + .setValue(ViewTranslationRequest.ID_TEXT, + TranslationRequestValue.forText(mText)) .build(); return request; } @@ -13923,12 +13926,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ @Override - public void onTranslationComplete(@NonNull TranslationRequest data) { + public void onTranslationComplete(@NonNull ViewTranslationResponse response) { // Show the translated text. TransformationMethod originalTranslationMethod = mTranslationTransformation != null ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation; mTranslationTransformation = - new TranslationTransformationMethod(data, originalTranslationMethod); + new TranslationTransformationMethod(response, originalTranslationMethod); // TODO(b/178353965): well-handle setTransformationMethod. setTransformationMethod(mTranslationTransformation); } -- cgit v1.2.3 From 4f2bc622b4c774276d5e47a50d29b14d7e7ced52 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Tue, 9 Mar 2021 13:38:38 -0800 Subject: Use getLineMax for desired text width calculation Bug: 178205254 Test: atest TextViewTest Change-Id: Ide45d75595571293bfa225935482d204160c6289 --- core/java/android/widget/TextView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ca0747fadf14..784d34b58cf7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9314,7 +9314,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } for (int i = 0; i < n; i++) { - max = Math.max(max, layout.getLineWidth(i)); + max = Math.max(max, layout.getLineMax(i)); } return (int) Math.ceil(max); -- cgit v1.2.3 From 3cda8bd49fba84b76eba8962b93b3ec704214e40 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Wed, 10 Mar 2021 22:25:16 +0800 Subject: Add debug logs to help clarify the issue We receive translation doesn't work issues but we don't have enough log information to help debugging the issue. To help clarify issue - Add more information when dumpsys activity information - We use Log.isLoggable() to control if the debug log need to be printed. But for the development phase, we enable print log on the debuggable ROM, we will delete it before platform release. Bug: 181975692 Test: manual. adb shell dumpsys activity --translation (Can find dumpsys result of sample app in bug 182433547) Change-Id: I4a9a1667a544fc4d1654c5f297d365a8288b1f4a --- core/java/android/widget/TextView.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fdc66fcb81d8..9e97f9aaed42 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,6 +195,7 @@ import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.view.translation.TranslationRequestValue; +import android.view.translation.UiTranslationController; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; @@ -13835,6 +13836,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public ViewTranslationRequest onCreateTranslationRequest() { if (mText == null || mText.length() == 0) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request for the empty text."); + } return null; } // Not translate password, editable text and not important for translation @@ -13842,6 +13847,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // text selection apis, not support in S. boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); if (isTextEditable() || isPassword || isTextSelectable()) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() + + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable()); + } return null; } // TODO(b/176488462): apply the view's important for translation property @@ -13870,6 +13880,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Restore to original text content. if (mTranslationTransformation != null) { setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); + } else { + // TODO(b/182433547): remove before S release + Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text."); } } @@ -13889,7 +13902,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTranslationTransformation != null) { setTransformationMethod(mTranslationTransformation); } else { - Log.w(LOG_TAG, "onResumeTranslatedText(): no translated text."); + // TODO(b/182433547): remove before S release + Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text."); } } @@ -13910,6 +13924,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTranslationTransformation != null) { setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); mTranslationTransformation = null; + } else { + // TODO(b/182433547): remove before S release + Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text."); } } -- cgit v1.2.3 From 4b7b90e79182805dd52fb0b779a0b4ab08983ab0 Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Fri, 12 Mar 2021 09:41:53 -0800 Subject: Integrate OnReceiveContentListener in View.onDragEvent OnReceiveContentListener was already integrated with drag-and-drop in TextView. The change extends support to all subclasses of View. If an OnReceiveContentListener is set on the view, the default implementation of View.onDragEvent will now return true for an ACTION_DRAG_STARTED event and will call View.performReceiveContent for an ACTION_DROP event. Bug: 182617122 Test: atest CtsWindowManagerDeviceTestCases:CrossAppDragAndDropTests Test: atest CtsViewTestCases:ViewOnReceiveContentTest Test: atest CtsWidgetTestCases:TextViewOnReceiveContentTest Test: Manually using ReceiveContentDemo Change-Id: I98fb703220ebd1953b20dcab60d22847cc5000d8 --- core/java/android/widget/TextView.java | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fdc66fcb81d8..71b5b3d67649 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13059,11 +13059,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return getLayout().getOffsetForHorizontal(line, x); } + /** + * Handles drag events sent by the system following a call to + * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int) + * startDragAndDrop()}. + * + *

If this text view is not editable, delegates to the default {@link View#onDragEvent} + * implementation. + * + *

If this text view is editable, accepts all drag actions (returns true for an + * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event and all + * subsequent drag events). While the drag is in progress, updates the cursor position + * to follow the touch location. Once a drop event is received, handles content insertion + * via {@link #performReceiveContent}. + * + * @param event The {@link android.view.DragEvent} sent by the system. + * The {@link android.view.DragEvent#getAction()} method returns an action type constant + * defined in DragEvent, indicating the type of drag event represented by this object. + * @return Returns true if this text view is editable and delegates to super otherwise. + * See {@link View#onDragEvent}. + */ @Override public boolean onDragEvent(DragEvent event) { + if (mEditor == null || !mEditor.hasInsertionController()) { + // If this TextView is not editable, defer to the default View implementation. This + // will check for the presence of an OnReceiveContentListener and accept/reject + // drag events depending on whether the listener is/isn't set. + return super.onDragEvent(event); + } switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: - return mEditor != null && mEditor.hasInsertionController(); + return true; case DragEvent.ACTION_DRAG_ENTERED: TextView.this.requestFocus(); -- cgit v1.2.3 From a749f2aadb68334e811ac37c129ee329eb54b0fe Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Mon, 15 Mar 2021 22:31:53 +0800 Subject: Update View translation APIs. This change doesn't contain the virtual view part and the API dispatchTranslationRequests, it will be done in another change. Bug: 178046780 Test: manual Test: atest CtsTranslationTestCases CTS-Coverage-Bug: 177960696 Change-Id: I65fe1db19c9dff21c0caca425fbb7d08559e730b --- core/java/android/widget/TextView.java | 175 ++++++++++++++------------------- 1 file changed, 73 insertions(+), 102 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 940a3c9cccdf..6733c0d3a8e1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,7 +195,9 @@ import android.view.textclassifier.TextLinks; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; import android.view.translation.TranslationRequestValue; +import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationController; +import android.view.translation.ViewTranslationCallback; import android.view.translation.ViewTranslationRequest; import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; @@ -203,6 +205,7 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastMath; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; @@ -737,7 +740,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private MovementMethod mMovement; private TransformationMethod mTransformation; - private TranslationTransformationMethod mTranslationTransformation; + private TextViewTranslationCallback mDefaultTranslationCallback; @UnsupportedAppUsage private boolean mAllowTransformationLengthChange; @UnsupportedAppUsage @@ -13857,136 +13860,104 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Provides a {@link ViewTranslationRequest} that represents the content to be translated via - * translation service. + * Returns a {@link ViewTranslationRequest} which represents the content to be translated. * - *

NOTE: When overriding the method, it should not translate the password. We also suggest - * that not translating the text is selectable or editable. We use the transformation method to - * implement showing the translated text. The TextView does not support the transformation - * method text length change. If the text is selectable or editable, it will crash while - * selecting the text. To support it, it needs broader changes to text APIs, we only allow to - * translate non selectable and editable text now. + *

NOTE: When overriding the method, it should not translate the password. If the subclass + * uses {@link TransformationMethod} to display the translated result, it's also not recommend + * to translate text is selectable or editable. * - * @hide + * @param supportedFormats the supported translation format. The value could be {@link + * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. + * @return the {@link ViewTranslationRequest} which contains the information to be translated. */ @Nullable @Override - public ViewTranslationRequest onCreateTranslationRequest() { - if (mText == null || mText.length() == 0) { + public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) { + if (supportedFormats == null || supportedFormats.length == 0) { // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { - Log.w(LOG_TAG, "Cannot create translation request for the empty text."); + Log.w(LOG_TAG, "Do not provide the support translation formats."); } return null; } - // Not translate password, editable text and not important for translation - // TODO(b/177214256): support selectable text translation. It needs to broader changes to - // text selection apis, not support in S. - boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); - if (isTextEditable() || isPassword || isTextSelectable()) { - // TODO(b/182433547): remove before S release - if (UiTranslationController.DEBUG) { - Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() - + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable()); + ViewTranslationRequest.Builder requestBuilder = + new ViewTranslationRequest.Builder(getAutofillId()); + // Support Text translation + if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { + if (mText == null || mText.length() == 0) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request for the empty text."); + } + return null; } - return null; - } - // TODO(b/176488462): apply the view's important for translation property - // TODO(b/174283799): remove the spans from the mText and save the spans information - // TODO: use fixed ids for request texts. - ViewTranslationRequest request = - new ViewTranslationRequest.Builder(getAutofillId()) - .setValue(ViewTranslationRequest.ID_TEXT, - TranslationRequestValue.forText(mText)) - .build(); - return request; - } - - /** - * Provides the implementation that pauses the ongoing Ui translation, it will show the original - * text instead of the translated text and restore the original transformation method. - * - *

NOTE: If this method is overridden, other translation related methods such as - * {@link onRestoreUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} - * should also be overridden. - * - * @hide - */ - @Override - public void onPauseUiTranslation() { - // Restore to original text content. - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onPauseUiTranslation(): no translated text."); + boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); + // TODO(b/177214256): support selectable text translation. + // We use the TransformationMethod to implement showing the translated text. The + // TextView does not support the text length change for TransformationMethod. If the + // text is selectable or editable, it will crash while selecting the text. To support + // it, it needs broader changes to text APIs, we only allow to translate non selectable + // and editable text in S. + if (isTextEditable() || isPassword || isTextSelectable()) { + // TODO(b/182433547): remove before S release + if (UiTranslationController.DEBUG) { + Log.w(LOG_TAG, "Cannot create translation request. editable = " + + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " + + isTextSelectable()); + } + return null; + } + // TODO(b/176488462): apply the view's important for translation + requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, + TranslationRequestValue.forText(mText)); } + return requestBuilder.build(); } /** - * Provides the implementation that restoes the paused Ui translation, it will show the - * translated text again if the text had been translated. This method will replace the current - * tansformation method with {@link TranslationTransformationMethod}. - * - *

NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onFinishUiTranslation}, {@link onTranslationComplete} - * should also be overridden. + * Returns a {@link ViewTranslationCallback} that is used to display the translated information. + * The default implementation will use a {@link TransformationMethod} that allow to replace the + * current {@link TransformationMethod} to transform the original text to the translated text + * display. * - * @hide + * @return a {@link ViewTranslationCallback} that is used to control how to display the + * translated information or {@code null} if this View doesn't support translation. */ + @Nullable @Override - public void onRestoreUiTranslation() { - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation); - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onRestoreUiTranslation(): no translated text."); - } + public ViewTranslationCallback getViewTranslationCallback() { + return getDefaultViewTranslationCallback(); } - /** - * Provides the implementation that finishes the current Ui translation and it's no longer to - * show the translated text. This method restores the original transformation method and resets - * the saved {@link TranslationTransformationMethod}. - * - *

NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onTranslationComplete} - * should also be overridden. - * - * @hide - */ - @Override - public void onFinishUiTranslation() { - // Restore to original text content and clear TranslationTransformation - if (mTranslationTransformation != null) { - setTransformationMethod(mTranslationTransformation.getOriginalTransformationMethod()); - mTranslationTransformation = null; - } else { - // TODO(b/182433547): remove before S release - Log.w(LOG_TAG, "onFinishUiTranslation(): no translated text."); + private ViewTranslationCallback getDefaultViewTranslationCallback() { + if (mDefaultTranslationCallback == null) { + mDefaultTranslationCallback = new TextViewTranslationCallback(); } + return mDefaultTranslationCallback; } /** - * Default {@link TextView} implementation after the translation request is done by the - * translation service, it's ok to show the translated text. This method will save the original - * transformation method and replace the current transformation method with - * {@link TranslationTransformationMethod}. * - *

NOTE: If this method is overridden, other translation related methods such as - * {@link onPauseUiTranslation}, {@link onRestoreUiTranslation}, {@link onFinishUiTranslation} - * should also be overridden. + * Called when the content from {@link #createTranslationRequest} had been translated by the + * TranslationService. The default implementation will replace the current + * {@link TransformationMethod} to transform the original text to the translated text display. * - * @hide + * @param response a {@link ViewTranslationResponse} that contains the translated information + * which can be shown in the view. */ @Override - public void onTranslationComplete(@NonNull ViewTranslationResponse response) { - // Show the translated text. - TransformationMethod originalTranslationMethod = mTranslationTransformation != null - ? mTranslationTransformation.getOriginalTransformationMethod() : mTransformation; - mTranslationTransformation = + public void onTranslationResponse(@NonNull ViewTranslationResponse response) { + // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default + // implementation if the view has overridden getViewTranslationCallback. + TextViewTranslationCallback callback = + (TextViewTranslationCallback) getDefaultViewTranslationCallback(); + TranslationTransformationMethod oldTranslationMethod = + callback.getTranslationTransformation(); + TransformationMethod originalTranslationMethod = oldTranslationMethod != null + ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; + TranslationTransformationMethod newTranslationMethod = new TranslationTransformationMethod(response, originalTranslationMethod); // TODO(b/178353965): well-handle setTransformationMethod. - setTransformationMethod(mTranslationTransformation); + callback.setTranslationTransformation(newTranslationMethod); } } -- cgit v1.2.3 From f51da99ac179cfa35f89c26245440dc2a85f60e3 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Tue, 23 Mar 2021 23:46:17 +0800 Subject: Implement dispatchRequestTranslation for getting translation information. This change doesn't contain the virtual view part and the API, it will be done in the next change. Bug: 178046780 Test: manual Test: atest CtsTranslationTestCases CTS-Coverage-Bug: 177960696 Change-Id: Idba66a882a90168ecdd93423c0d5d054ab040dad --- core/java/android/widget/TextView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6733c0d3a8e1..ffaa31552a6a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13872,7 +13872,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Nullable @Override - public ViewTranslationRequest createTranslationRequest(@NonNull int[] supportedFormats) { + public ViewTranslationRequest onCreateTranslationRequest(@NonNull int[] supportedFormats) { if (supportedFormats == null || supportedFormats.length == 0) { // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { @@ -13938,7 +13938,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * - * Called when the content from {@link #createTranslationRequest} had been translated by the + * Called when the content from {@link #onCreateTranslationRequest} had been translated by the * TranslationService. The default implementation will replace the current * {@link TransformationMethod} to transform the original text to the translated text display. * -- cgit v1.2.3 From f3ebaed6e4d7d949acd5bc66d322a4f3d77f3a9b Mon Sep 17 00:00:00 2001 From: Nikita Dubrovsky Date: Fri, 26 Mar 2021 10:29:32 -0700 Subject: Rename getOnReceiveContentMimeTypes (drop "On") Bug: 179143943 Test: Presubmit Change-Id: Ib2109f0a45c25ae6c4507a9199bf8b34fc394af5 --- core/java/android/widget/TextView.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 6733c0d3a8e1..c878717cd607 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8773,7 +8773,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); - outAttrs.contentMimeTypes = getOnReceiveContentMimeTypes(); + outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); return ic; } } @@ -11741,10 +11741,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } - String[] mimeTypes = getOnReceiveContentMimeTypes(); + String[] mimeTypes = getReceiveContentMimeTypes(); if (mimeTypes == null && mEditor != null) { // If the app hasn't set a listener for receiving content on this view (ie, - // getOnReceiveContentMimeTypes() returns null), check if it implements the + // getReceiveContentMimeTypes() returns null), check if it implements the // keyboard image API and, if possible, use those MIME types as fallback. // This fallback is only in place for autofill, not other mechanisms for // inserting content. See AUTOFILL_NON_TEXT_REQUIRES_ON_RECEIVE_CONTENT_LISTENER @@ -11752,7 +11752,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mimeTypes = mEditor.getDefaultOnReceiveContentListener() .getFallbackMimeTypesForAutofill(this); } - structure.setOnReceiveContentMimeTypes(mimeTypes); + structure.setReceiveContentMimeTypes(mimeTypes); } if (!isPassword || viewFor == VIEW_STRUCTURE_FOR_AUTOFILL -- cgit v1.2.3 From 71106418814942cf29940ea428ea4e5b0755be80 Mon Sep 17 00:00:00 2001 From: Ahaan Ugale Date: Thu, 15 Apr 2021 18:49:42 -0700 Subject: AutoTranslate: Pad view text for compatibility reasons. Some apps operate on the view text based on layout calculations made on the transformed text. This can cause issues (even crashes) if the transformed text is longer than the original, as is often the case with Translate. A previous temporary fix, I685ae4a9752c817db81f0d736f1d746b2e4a9839, ellipsized the translated text. This change fixes it properly by instead padding the original text. A followup change will make this controllable by API instead of padding every TextView. The en space character, U+2002, is used for the padding here. It seems to work well in practice for multiple languages. We don't make this configurable through API as it could make debugging harder (issues triggered by server-side changes). Bug: 179693024 Test: atest CtsTranslationTestCases Test: manual - toggling between original and translated, scrolling, new views appearing, multiple apps Change-Id: I9d7db5d2bc200e4042baf3267796f00cfc298f19 --- core/java/android/widget/TextView.java | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 40c79cf086fc..1953a7687cb9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -770,6 +770,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final boolean mUseInternationalizedInput; // True if fallback fonts that end up getting used should be allowed to affect line spacing. /* package */ boolean mUseFallbackLineSpacing; + // True if the view text can be padded for compat reasons, when the view is translated. + private final boolean mUseTextPaddingForUiTranslation; @ViewDebug.ExportedProperty(category = "text") @UnsupportedAppUsage @@ -1480,6 +1482,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; + // TODO(b/179693024): Use a ChangeId instead. + mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R; if (inputMethod != null) { Class c; @@ -2372,6 +2376,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @ViewDebug.CapturedViewProperty @InspectableProperty public CharSequence getText() { + if (mUseTextPaddingForUiTranslation + && mDefaultTranslationCallback != null + && mDefaultTranslationCallback.isTextPaddingEnabled() + && mDefaultTranslationCallback.isShowingTranslation()) { + return mDefaultTranslationCallback.getPaddedText(mText, mTransformed); + } return mText; } -- cgit v1.2.3 From f6a52811a1a30784b99c3caeec3225062092219d Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Fri, 9 Apr 2021 12:17:19 +0800 Subject: API feedback: Translation View API refinement. Bug: 178046780 Test: manual Test: atest android.translation.cts.UiTranslationManagerTest CTS-Coverage-Bug: 177960696 Change-Id: I557ca4b3b4e7324af12e81dd8b5ac8a04a17b6b8 --- core/java/android/widget/TextView.java | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1953a7687cb9..07a9a5fad6e2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13870,7 +13870,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Returns a {@link ViewTranslationRequest} which represents the content to be translated. + * Collects a {@link ViewTranslationRequest} which represents the content to be translated in + * the view. * *

NOTE: When overriding the method, it should not translate the password. If the subclass * uses {@link TransformationMethod} to display the translated result, it's also not recommend @@ -13880,15 +13881,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * android.view.translation.TranslationSpec#DATA_FORMAT_TEXT}. * @return the {@link ViewTranslationRequest} which contains the information to be translated. */ - @Nullable @Override - public ViewTranslationRequest onCreateTranslationRequest(@NonNull int[] supportedFormats) { + public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, + @NonNull Consumer requestsCollector) { if (supportedFormats == null || supportedFormats.length == 0) { // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { Log.w(LOG_TAG, "Do not provide the support translation formats."); } - return null; + return; } ViewTranslationRequest.Builder requestBuilder = new ViewTranslationRequest.Builder(getAutofillId()); @@ -13899,7 +13900,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (UiTranslationController.DEBUG) { Log.w(LOG_TAG, "Cannot create translation request for the empty text."); } - return null; + return; } boolean isPassword = isAnyPasswordInputType() || hasPasswordTransformationMethod(); // TODO(b/177214256): support selectable text translation. @@ -13914,14 +13915,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable()); + return; } - return null; } // TODO(b/176488462): apply the view's important for translation requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, TranslationRequestValue.forText(mText)); } - return requestBuilder.build(); + requestsCollector.accept(requestBuilder.build()); } /** @@ -13932,6 +13933,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @return a {@link ViewTranslationCallback} that is used to control how to display the * translated information or {@code null} if this View doesn't support translation. + * + * @hide */ @Nullable @Override @@ -13948,15 +13951,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * - * Called when the content from {@link #onCreateTranslationRequest} had been translated by the - * TranslationService. The default implementation will replace the current + * Called when the content from {@link #onCreateViewTranslationRequest} had been translated by + * the TranslationService. The default implementation will replace the current * {@link TransformationMethod} to transform the original text to the translated text display. * * @param response a {@link ViewTranslationResponse} that contains the translated information * which can be shown in the view. */ @Override - public void onTranslationResponse(@NonNull ViewTranslationResponse response) { + public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) { + // set ViewTranslationResponse + super.onViewTranslationResponse(response); // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default // implementation if the view has overridden getViewTranslationCallback. TextViewTranslationCallback callback = -- cgit v1.2.3 From a35ebcf88e0a9458f243df3ab4ed85e7ecccddc5 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 27 Apr 2021 19:20:16 -0700 Subject: Fix stale EditorInfo upon EditText#setText() This is a follow up to our previous CL [1], which added initial surrounding text support into EditorInfo and let TextView support it. What we overlooked was that calling TextView#setText() would trigger TextView#onCreateInputConnection() before TextView#mText is updated to the new value. As a result, EditorInfo is initialized with a stale surrounding text information. With this CL, EditorInfo will correctly be initialized with the new text specified to TextView#setText(). One complicated thing in this CL is InputMethodManager also has some issues about how to drop spurious InputMethodService#onUpdateSelection() while InputConnection is being replaced with a new instance. I ended up having to introduce a hacky boolean Editor#mHasPendingRestartInputForSetText to work around this in the TextView/EditText. This workaround is expected to be removed/revisited as part of Bug 186582769. [1]: Ie04f2349b1157408aa8ed9044aea12ce99132cb4 c486acc4d29a0e441ddcd05b3fc0c919aee7fbd0 Fix: 161330778 Test: atest CtsInputMethodTestCases:EditTextImeSupportTest Change-Id: Iee3b754eea319861b2eb955d6cc95ba13006b55e --- core/java/android/widget/TextView.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 07a9a5fad6e2..1a37b595287d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6285,11 +6285,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener || needEditableForNotification) { createEditorIfNeeded(); mEditor.forgetUndoRedo(); + mEditor.scheduleRestartInputForSetText(); Editable t = mEditableFactory.newEditable(text); text = t; setFilters(t, mFilters); - InputMethodManager imm = getInputMethodManager(); - if (imm != null) imm.restartInput(this); } else if (precomputed != null) { if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); @@ -6408,8 +6407,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener notifyListeningManagersAfterTextChanged(); } - // SelectionModifierCursorController depends on textCanBeSelected, which depends on text - if (mEditor != null) mEditor.prepareCursorControllers(); + if (mEditor != null) { + // SelectionModifierCursorController depends on textCanBeSelected, which depends on text + mEditor.prepareCursorControllers(); + + mEditor.maybeFireScheduledRestartInputForSetText(); + } } /** -- cgit v1.2.3 From 9753899a97020f3402b9f409f3d133690ace03c4 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Thu, 13 May 2021 23:02:12 +0800 Subject: Translate TextView content description if exist Screen reader(Talkback) will read content description first if the developers set. We only translate TextView text, this may cause the screen reader will not read the translated text if the developers set content description. To fix the issue, we also send the content description to translate. When the translated text is shown, we also set content description with translated content description and reset to original content description if show original text. Bug: 187134784 Test: atest CtsTranslationTestCases Change-Id: I6986384260627a0539780b7293d47666c442d852 --- core/java/android/widget/TextView.java | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1a37b595287d..e0238b9bce13 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13924,6 +13924,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // TODO(b/176488462): apply the view's important for translation requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, TranslationRequestValue.forText(mText)); + if (!TextUtils.isEmpty(getContentDescription())) { + requestBuilder.setValue(ViewTranslationRequest.ID_CONTENT_DESCRIPTION, + TranslationRequestValue.forText(getContentDescription())); + } } requestsCollector.accept(requestBuilder.build()); } -- cgit v1.2.3 From 7f2343de99be99f27bf1d38422b199ea1a2666bf Mon Sep 17 00:00:00 2001 From: Qi Wang Date: Tue, 18 May 2021 18:50:34 +0800 Subject: Fix a bug in SpellCheckSpan update logic. The the text is changed, we need to remove all affected SpellCheckSpan. However, the previous check logic can not work correctly, and may leave some out-of-date SpellCheckSpan, which causes problem when the SpellChecker tries to apply suggestions on those spans later. Also fixed the bug that the offset is not calculated correctly when we validate spell checker suggestions. Fix: 188107864 Change-Id: I8dc806d96b6a75255277d6de013305557835cb6d Test: atest CtsInputMethodTestCases:SpellCheckerTest Test: atest android.widget.cts.TextViewTest --- core/java/android/widget/TextView.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 1a37b595287d..374dc1cba5db 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10746,12 +10746,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Editable text = (Editable) mText; T[] spans = text.getSpans(start, end, type); - final int length = spans.length; - for (int i = 0; i < length; i++) { - final int spanStart = text.getSpanStart(spans[i]); - final int spanEnd = text.getSpanEnd(spans[i]); - if (spanEnd == start || spanStart == end) break; - text.removeSpan(spans[i]); + ArrayList spansToRemove = new ArrayList<>(); + for (T span : spans) { + final int spanStart = text.getSpanStart(span); + final int spanEnd = text.getSpanEnd(span); + if (spanEnd == start || spanStart == end) continue; + spansToRemove.add(span); + } + for (T span : spansToRemove) { + text.removeSpan(span); } } -- cgit v1.2.3 From 18b1d3bde4b2437cdeeacd3544274a8f8e80ed81 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Wed, 14 Apr 2021 23:24:42 +0800 Subject: Refine the ViewTranslationCallback usage. Currently, TextView uses its default implementation even developers uses setViewTranslationCallback() to set their customized ViewTranslationCallback, we should only set default TextView implementation if developers don't set it. The onViewTranslationResponse() will call getViewTranslationCallback instead of getting TextView default implementation directly. This can make sure we can get the expected ViewTranslationCallback. Bug: 183467275 Test: manual Test: atest CtsTranslationTestCases Change-Id: I41417140f8985aec6c80f1bca3cfba804727d5df --- core/java/android/widget/TextView.java | 66 +++++++++++++--------------------- 1 file changed, 24 insertions(+), 42 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e0238b9bce13..9959510d5ac0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -740,7 +740,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private MovementMethod mMovement; private TransformationMethod mTransformation; - private TextViewTranslationCallback mDefaultTranslationCallback; @UnsupportedAppUsage private boolean mAllowTransformationLengthChange; @UnsupportedAppUsage @@ -2376,11 +2375,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @ViewDebug.CapturedViewProperty @InspectableProperty public CharSequence getText() { - if (mUseTextPaddingForUiTranslation - && mDefaultTranslationCallback != null - && mDefaultTranslationCallback.isTextPaddingEnabled() - && mDefaultTranslationCallback.isShowingTranslation()) { - return mDefaultTranslationCallback.getPaddedText(mText, mTransformed); + if (mUseTextPaddingForUiTranslation) { + ViewTranslationCallback callback = getViewTranslationCallback(); + if (callback != null && callback instanceof TextViewTranslationCallback) { + TextViewTranslationCallback defaultCallback = + (TextViewTranslationCallback) callback; + if (defaultCallback.isTextPaddingEnabled() + && defaultCallback.isShowingTranslation()) { + return defaultCallback.getPaddedText(mText, mTransformed); + } + } } return mText; } @@ -13932,30 +13936,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener requestsCollector.accept(requestBuilder.build()); } - /** - * Returns a {@link ViewTranslationCallback} that is used to display the translated information. - * The default implementation will use a {@link TransformationMethod} that allow to replace the - * current {@link TransformationMethod} to transform the original text to the translated text - * display. - * - * @return a {@link ViewTranslationCallback} that is used to control how to display the - * translated information or {@code null} if this View doesn't support translation. - * - * @hide - */ - @Nullable - @Override - public ViewTranslationCallback getViewTranslationCallback() { - return getDefaultViewTranslationCallback(); - } - - private ViewTranslationCallback getDefaultViewTranslationCallback() { - if (mDefaultTranslationCallback == null) { - mDefaultTranslationCallback = new TextViewTranslationCallback(); - } - return mDefaultTranslationCallback; - } - /** * * Called when the content from {@link #onCreateViewTranslationRequest} had been translated by @@ -13969,17 +13949,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) { // set ViewTranslationResponse super.onViewTranslationResponse(response); - // TODO(b/183467275): Use the overridden ViewTranslationCallback instead of our default - // implementation if the view has overridden getViewTranslationCallback. - TextViewTranslationCallback callback = - (TextViewTranslationCallback) getDefaultViewTranslationCallback(); - TranslationTransformationMethod oldTranslationMethod = - callback.getTranslationTransformation(); - TransformationMethod originalTranslationMethod = oldTranslationMethod != null - ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; - TranslationTransformationMethod newTranslationMethod = - new TranslationTransformationMethod(response, originalTranslationMethod); - // TODO(b/178353965): well-handle setTransformationMethod. - callback.setTranslationTransformation(newTranslationMethod); + // TODO(b/178353965): move to ViewTranslationCallback.onShow() + ViewTranslationCallback callback = getViewTranslationCallback(); + if (callback instanceof TextViewTranslationCallback) { + TextViewTranslationCallback textViewDefaultCallback = + (TextViewTranslationCallback) callback; + TranslationTransformationMethod oldTranslationMethod = + textViewDefaultCallback.getTranslationTransformation(); + TransformationMethod originalTranslationMethod = oldTranslationMethod != null + ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; + TranslationTransformationMethod newTranslationMethod = + new TranslationTransformationMethod(response, originalTranslationMethod); + // TODO(b/178353965): well-handle setTransformationMethod. + textViewDefaultCallback.setTranslationTransformation(newTranslationMethod); + } } } -- cgit v1.2.3 From fb12492de73fdb4e932fd5f10a1691bd746123c7 Mon Sep 17 00:00:00 2001 From: Ahaan Ugale Date: Mon, 14 Jun 2021 23:21:47 -0700 Subject: ContentCapture: Notify when Composing region changes. Sending the notification from the InputConnection layer reduces the number of events that are handled (merged) by ContentCapture. We are also able to handle composing span being removed; at the TextView layer, we cannot do that as we don't know if the composing span will be added back, which happens on typing). Bug: 184311217 Test: manual - append chars, delete chars, replace text, move cursor to composing text, move cursor to remove composing span, drag cursor Test: atest android.contentcaptureservice.cts.LoginActivityTest Change-Id: I0e9d153cf7ba2734f38cb1b6044eb7b670fb34ad --- core/java/android/widget/TextView.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3c4fd5e93580..cd560d75d913 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -10832,11 +10832,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + notifyContentCaptureTextChanged(); + } + + /** + * Notifies the ContentCapture service that the text of the view has changed (only if + * ContentCapture has been notified of this view's existence already). + * + * @hide + */ + public void notifyContentCaptureTextChanged() { // TODO(b/121045053): should use a flag / boolean to keep status of SHOWN / HIDDEN instead // of using isLaidout(), so it's not called in cases where it's laid out but a // notifyAppeared was not sent. - - // ContentCapture if (isLaidOut() && isImportantForContentCapture() && getNotifiedContentCaptureAppeared()) { final ContentCaptureManager cm = mContext.getSystemService(ContentCaptureManager.class); if (cm != null && cm.isContentCaptureEnabled()) { -- cgit v1.2.3 From a9da1722cc6994d32b5eb2ca5618dba7013d447b Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Thu, 10 Jun 2021 18:34:19 +0800 Subject: Bug fix: Move the implementation out from View.onTranslationResponse. Keep onTranslationResponse() only to save TranslationResponse. We can move set TransformationMethod logic to TextViewTranslationCallback. With the current implementation, if the developers overrides onTranslationResponse(), they don't have a chance to use the TextView default ViewTranslationCallback implementation because they don't set the TranslationTransformation, they must implement their solution. If we move logic to TextViewViewTranslationCallback and the developers only overrides onTranslationResponse(), they still have a chance to use the default TextViewViewTranslationCallback if the developers set the TranslationResponse for View. Bug: 178353965 Test: manual to make sure translation still works. Test: atest CtsTranslationTestCases Change-Id: Iada7f3efbbc7705ecf962c78e275ed942816707f --- core/java/android/widget/TextView.java | 31 ------------------------------- 1 file changed, 31 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3c4fd5e93580..9032d62bbb49 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -129,7 +129,6 @@ import android.text.method.TextKeyListener; import android.text.method.TimeKeyListener; import android.text.method.TransformationMethod; import android.text.method.TransformationMethod2; -import android.text.method.TranslationTransformationMethod; import android.text.method.WordIterator; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; @@ -199,7 +198,6 @@ import android.view.translation.TranslationSpec; import android.view.translation.UiTranslationController; import android.view.translation.ViewTranslationCallback; import android.view.translation.ViewTranslationRequest; -import android.view.translation.ViewTranslationResponse; import android.widget.RemoteViews.RemoteView; import com.android.internal.annotations.VisibleForTesting; @@ -13938,33 +13936,4 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } requestsCollector.accept(requestBuilder.build()); } - - /** - * - * Called when the content from {@link #onCreateViewTranslationRequest} had been translated by - * the TranslationService. The default implementation will replace the current - * {@link TransformationMethod} to transform the original text to the translated text display. - * - * @param response a {@link ViewTranslationResponse} that contains the translated information - * which can be shown in the view. - */ - @Override - public void onViewTranslationResponse(@NonNull ViewTranslationResponse response) { - // set ViewTranslationResponse - super.onViewTranslationResponse(response); - // TODO(b/178353965): move to ViewTranslationCallback.onShow() - ViewTranslationCallback callback = getViewTranslationCallback(); - if (callback instanceof TextViewTranslationCallback) { - TextViewTranslationCallback textViewDefaultCallback = - (TextViewTranslationCallback) callback; - TranslationTransformationMethod oldTranslationMethod = - textViewDefaultCallback.getTranslationTransformation(); - TransformationMethod originalTranslationMethod = oldTranslationMethod != null - ? oldTranslationMethod.getOriginalTransformationMethod() : mTransformation; - TranslationTransformationMethod newTranslationMethod = - new TranslationTransformationMethod(response, originalTranslationMethod); - // TODO(b/178353965): well-handle setTransformationMethod. - textViewDefaultCallback.setTranslationTransformation(newTranslationMethod); - } - } } -- cgit v1.2.3 From 4a6d59b696d5284464a772d4478aecf26fdd4d6a Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Mon, 21 Jun 2021 21:32:45 +0800 Subject: Fix TextViewTranslationTest test fail. The fail will only be found in the release build, we put the return in the wrong place. Bug: 189359744 Test: local disable flag and TextViewTranslationTest pass. Change-Id: Idd377145b3757abaf528a90a25b6c73e9940f9f8 --- core/java/android/widget/TextView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3c4fd5e93580..4daa2e311e3d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13925,8 +13925,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " + isTextSelectable()); - return; } + return; } // TODO(b/176488462): apply the view's important for translation requestBuilder.setValue(ViewTranslationRequest.ID_TEXT, -- cgit v1.2.3 From 987a2b41503c4b186e1a895b5812b3b11003d206 Mon Sep 17 00:00:00 2001 From: Mihir Patel Date: Tue, 22 Jun 2021 11:35:32 -0700 Subject: Guarding against null pointer exception when setting view structure info for content capture Test: Manual, CTS Bug: 189329389 Change-Id: Iaaacd59dc377079847951ab635df9bb602467390 --- core/java/android/widget/TextView.java | 35 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 3c4fd5e93580..dc16ded08917 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11848,23 +11848,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Get the text and trim it to the range we are reporting. CharSequence text = getText(); - if (expandedTopChar > 0 || expandedBottomChar < text.length()) { - text = text.subSequence(expandedTopChar, expandedBottomChar); - } - if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { - structure.setText(text); - } else { - structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); - - final int[] lineOffsets = new int[bottomLine - topLine + 1]; - final int[] lineBaselines = new int[bottomLine - topLine + 1]; - final int baselineOffset = getBaselineOffset(); - for (int i = topLine; i <= bottomLine; i++) { - lineOffsets[i - topLine] = layout.getLineStart(i); - lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; + if (text != null) { + if (expandedTopChar > 0 || expandedBottomChar < text.length()) { + text = text.subSequence(expandedTopChar, expandedBottomChar); + } + + if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { + structure.setText(text); + } else { + structure.setText(text, + selStart - expandedTopChar, + selEnd - expandedTopChar); + + final int[] lineOffsets = new int[bottomLine - topLine + 1]; + final int[] lineBaselines = new int[bottomLine - topLine + 1]; + final int baselineOffset = getBaselineOffset(); + for (int i = topLine; i <= bottomLine; i++) { + lineOffsets[i - topLine] = layout.getLineStart(i); + lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; + } + structure.setTextLines(lineOffsets, lineBaselines); } - structure.setTextLines(lineOffsets, lineBaselines); } } -- cgit v1.2.3 From bec597e4ccd11c1be42c7980c0d2a8bbf3670d24 Mon Sep 17 00:00:00 2001 From: Joanne Chung Date: Mon, 21 Jun 2021 22:43:03 +0800 Subject: Disable log default dumps in debug ROM We enable the logs dump in debug rom, we should only dump logs when the debug flag is on. Bug: 182433547 Test: Make sure the flag is disable after test and enable during test. Change-Id: I0a719ee9c545b4e66f3414d7f57f3d959004071e --- core/java/android/widget/TextView.java | 3 --- 1 file changed, 3 deletions(-) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 83fd7b49c2bd..94f45ec4524a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13901,7 +13901,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onCreateViewTranslationRequest(@NonNull int[] supportedFormats, @NonNull Consumer requestsCollector) { if (supportedFormats == null || supportedFormats.length == 0) { - // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { Log.w(LOG_TAG, "Do not provide the support translation formats."); } @@ -13912,7 +13911,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Support Text translation if (ArrayUtils.contains(supportedFormats, TranslationSpec.DATA_FORMAT_TEXT)) { if (mText == null || mText.length() == 0) { - // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { Log.w(LOG_TAG, "Cannot create translation request for the empty text."); } @@ -13926,7 +13924,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // it, it needs broader changes to text APIs, we only allow to translate non selectable // and editable text in S. if (isTextEditable() || isPassword || isTextSelectable()) { - // TODO(b/182433547): remove before S release if (UiTranslationController.DEBUG) { Log.w(LOG_TAG, "Cannot create translation request. editable = " + isTextEditable() + ", isPassword = " + isPassword + ", selectable = " -- cgit v1.2.3 From cbe8bc36eb6f180dbdfb764679105612c5d0b363 Mon Sep 17 00:00:00 2001 From: Ahaan Ugale Date: Thu, 12 Aug 2021 14:08:52 -0700 Subject: Fix OOB crash in ContentCapture for translated views When a view is partially visible on the screen, ContentCapture reports only the visible portion (+ a few additional lines). The offsets calculated for this can be out of bounds if the view's displayed text is longer from the original text. Fix: 196414491 Test: manual - translate app to lang with more characters and trigger a relayout (by scrolling for an app with ListView) Test: atest CtsContentCaptureServiceTestCases Change-Id: Iae98133c48cc67a0b00f1b0ab8b93e5adb293423 (cherry picked from commit 273a0eabc8faa46349c63a6836ae4004709aeb0b) --- core/java/android/widget/TextView.java | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'core/java/android/widget/TextView.java') diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ca6e735f86b4..f5c1bcf2de42 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11857,6 +11857,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (text != null) { if (expandedTopChar > 0 || expandedBottomChar < text.length()) { + // Cap the offsets to avoid an OOB exception. That can happen if the + // displayed/layout text, on which these offsets are calculated, is longer + // than the original text (such as when the view is translated by the + // platform intelligence). + // TODO(b/196433694): Figure out how to better handle the offset + // calculations for this case (so we don't unnecessarily cutoff the original + // text, for example). + expandedTopChar = Math.min(expandedTopChar, text.length()); + expandedBottomChar = Math.min(expandedBottomChar, text.length()); text = text.subSequence(expandedTopChar, expandedBottomChar); } -- cgit v1.2.3