Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce3b05ab authored by Siyamed Sinir's avatar Siyamed Sinir
Browse files

Handle long text for share/cut/copy operations.

When TextView or EditText contains text that is larger than the
total parcelable limit, some of the FloatingToolbar operations would
crash.

This CL changes the behavior as follows:
- Show a toast message if cut or copy operation fails because it cannot
  set the primary clip.
- Trim the text for share and process_text actions
- A simple app with an EditText and a long text in it, would not open
since Autofill value was being sent over IPC. Trimmed the text that is
sent for Autofill feature.
- Trim the value send to accessibility services

Test: bit CtsWidgetTestCases:.TextViewTest
Test: bit FrameworksCoreTests:android.widget.TextViewTest
Test: bit FrameworksCoreTests:android.text.TextUtilsTest
Test: Manual sample app test

Bug: 8013261
Change-Id: Ia0df6b4eb4c13071a1bf75cedac7241c7239663c
parent 417510bd
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.text;

import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PluralsRes;
@@ -2024,6 +2025,45 @@ public class TextUtils {
        builder.append(end);
    }

    /**
     * Intent size limitations prevent sending over a megabyte of data. Limit
     * text length to 100K characters - 200KB.
     */
    private static final int PARCEL_SAFE_TEXT_LENGTH = 100000;

    /**
     * Trims the text to {@link #PARCEL_SAFE_TEXT_LENGTH} length. Returns the string as it is if
     * the length() is smaller than {@link #PARCEL_SAFE_TEXT_LENGTH}. Used for text that is parceled
     * into a {@link Parcelable}.
     *
     * @hide
     */
    @Nullable
    public static <T extends CharSequence> T trimToParcelableSize(@Nullable T text) {
        return trimToSize(text, PARCEL_SAFE_TEXT_LENGTH);
    }

    /**
     * Trims the text to {@code size} length. Returns the string as it is if the length() is
     * smaller than {@code size}. If chars at {@code size-1} and {@code size} is a surrogate
     * pair, returns a CharSequence of length {@code size-1}.
     *
     * @param size length of the result, should be greater than 0
     *
     * @hide
     */
    @Nullable
    public static <T extends CharSequence> T trimToSize(@Nullable T text,
            @IntRange(from = 1) int size) {
        Preconditions.checkArgument(size > 0);
        if (TextUtils.isEmpty(text) || text.length() <= size) return text;
        if (Character.isHighSurrogate(text.charAt(size - 1))
                && Character.isLowSurrogate(text.charAt(size))) {
            size = size - 1;
        }
        return (T) text.subSequence(0, size);
    }

    private static Object sLock = new Object();

    private static char[] sTemp = null;
+3 −1
Original line number Diff line number Diff line
@@ -6449,7 +6449,9 @@ public class Editor {

        private boolean fireIntent(Intent intent) {
            if (intent != null && Intent.ACTION_PROCESS_TEXT.equals(intent.getAction())) {
                intent.putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText());
                String selectedText = mTextView.getSelectedText();
                selectedText = TextUtils.trimToParcelableSize(selectedText);
                intent.putExtra(Intent.EXTRA_PROCESS_TEXT, selectedText);
                mEditor.mPreserveSelection = true;
                mTextView.startActivityForResult(intent, TextView.PROCESS_TEXT_REQUEST_CODE);
                return true;
+40 −8
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;

import android.R;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
@@ -10328,10 +10329,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE;
    }

    /**
     * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K
     * {@code char}s if longer.
     *
     * @return current text, {@code null} if the text is not editable
     *
     * @see View#getAutofillValue()
     */
    @Override
    @Nullable
    public AutofillValue getAutofillValue() {
        return isTextEditable() ? AutofillValue.forText(getText()) : null;
        if (isTextEditable()) {
            final CharSequence text = TextUtils.trimToParcelableSize(getText());
            return AutofillValue.forText(text);
        }
        return null;
    }

    /** @hide */
@@ -10745,7 +10758,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        }

        // Otherwise, return whatever text is being displayed.
        return mTransformed;
        return TextUtils.trimToParcelableSize(mTransformed);
    }

    void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
@@ -10830,13 +10843,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                return true;

            case ID_CUT:
                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
                final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max));
                if (setPrimaryClip(cutData)) {
                    deleteText_internal(min, max);
                } else {
                    Toast.makeText(getContext(),
                            com.android.internal.R.string.failed_to_copy_to_clipboard,
                            Toast.LENGTH_SHORT).show();
                }
                return true;

            case ID_COPY:
                setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
                final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
                if (setPrimaryClip(copyData)) {
                    stopTextActionMode();
                } else {
                    Toast.makeText(getContext(),
                            com.android.internal.R.string.failed_to_copy_to_clipboard,
                            Toast.LENGTH_SHORT).show();
                }
                return true;

            case ID_REPLACE:
@@ -11192,17 +11217,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
            sharingIntent.setType("text/plain");
            sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
            selectedText = TextUtils.trimToParcelableSize(selectedText);
            sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
            getContext().startActivity(Intent.createChooser(sharingIntent, null));
            Selection.setSelection((Spannable) mText, getSelectionEnd());
        }
    }

    private void setPrimaryClip(ClipData clip) {
    @CheckResult
    private boolean setPrimaryClip(ClipData clip) {
        ClipboardManager clipboard =
                (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        try {
            clipboard.setPrimaryClip(clip);
        } catch (Throwable t) {
            return false;
        }
        sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
        return true;
    }

    /**
+3 −0
Original line number Diff line number Diff line
@@ -2599,6 +2599,9 @@
    <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. -->
    <string name="copy">Copy</string>

    <!-- Error shown by TextView/EditText when cut/copy operation fails because text is too long to copy into the clipboard. -->
    <string name="failed_to_copy_to_clipboard">Failed to copy to clipboard</string>

    <!-- Item on EditText context menu. This action is used to paste from the clipboard into the eidt field -->
    <string name="paste">Paste</string>

+2 −0
Original line number Diff line number Diff line
@@ -2538,6 +2538,8 @@
  <java-symbol type="id" name="suggestionContainer" />
  <java-symbol type="id" name="addToDictionaryButton" />
  <java-symbol type="id" name="deleteButton" />
  <!-- TextView -->
  <java-symbol type="string" name="failed_to_copy_to_clipboard" />

  <java-symbol type="id" name="notification_material_reply_container" />
  <java-symbol type="id" name="notification_material_reply_text_1" />
Loading