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

Commit f290a87c authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Make some NoCopySpans can pass to ContentCaptureService" into sc-dev am: 7063bae0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14891918

Change-Id: I5dc35c9b31952db6f44e21cb9166e83630aa8644
parents c7081f37 7063bae0
Loading
Loading
Loading
Loading
+106 −10
Original line number Diff line number Diff line
@@ -25,8 +25,12 @@ import android.annotation.SystemApi;
import android.graphics.Insets;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.view.inputmethod.BaseInputConnection;

import com.android.internal.util.Preconditions;

@@ -132,6 +136,9 @@ public final class ContentCaptureEvent implements Parcelable {
    @Retention(RetentionPolicy.SOURCE)
    public @interface EventType{}

    /** @hide */
    public static final int MAX_INVALID_VALUE = -1;

    private final int mSessionId;
    private final int mType;
    private final long mEventTime;
@@ -143,6 +150,11 @@ public final class ContentCaptureEvent implements Parcelable {
    private @Nullable ContentCaptureContext mClientContext;
    private @Nullable Insets mInsets;

    private int mComposingStart = MAX_INVALID_VALUE;
    private int mComposingEnd = MAX_INVALID_VALUE;
    private int mSelectionStartIndex = MAX_INVALID_VALUE;
    private int mSelectionEndIndex = MAX_INVALID_VALUE;

    /** Only used in the main Content Capture session, no need to parcel */
    private boolean mTextHasComposingSpan;

@@ -246,19 +258,75 @@ public final class ContentCaptureEvent implements Parcelable {

    /** @hide */
    @NonNull
    public ContentCaptureEvent setText(@Nullable CharSequence text, boolean hasComposingSpan) {
    public ContentCaptureEvent setText(@Nullable CharSequence text) {
        mText = text;
        mTextHasComposingSpan = hasComposingSpan;
        return this;
    }

    /**
     * The value is not parcelled, become false after parcelled.
     * @hide
     */
    /** @hide */
    @NonNull
    public ContentCaptureEvent setComposingIndex(int start, int end) {
        mComposingStart = start;
        mComposingEnd = end;
        return this;
    }

    /** @hide */
    @NonNull
    public boolean hasComposingSpan() {
        return mComposingStart > MAX_INVALID_VALUE;
    }

    /** @hide */
    @NonNull
    public boolean getTextHasComposingSpan() {
        return mTextHasComposingSpan;
    public ContentCaptureEvent setSelectionIndex(int start, int end) {
        mSelectionStartIndex = start;
        mSelectionEndIndex = end;
        return this;
    }

    private int getComposingStart() {
        return mComposingStart;
    }

    private int getComposingEnd() {
        return mComposingEnd;
    }

    private int getSelectionStart() {
        return mSelectionStartIndex;
    }

    private int getSelectionEnd() {
        return mSelectionEndIndex;
    }

    private void restoreComposingSpan() {
        if (mComposingStart <= MAX_INVALID_VALUE
                || mComposingEnd <= MAX_INVALID_VALUE) {
            return;
        }
        if (mText instanceof Spannable) {
            BaseInputConnection.setComposingSpans((Spannable) mText, mComposingStart,
                    mComposingEnd);
        } else {
            Log.w(TAG, "Text is not a Spannable.");
        }
    }

    private void restoreSelectionSpans() {
        if (mSelectionStartIndex <= MAX_INVALID_VALUE
                || mSelectionEndIndex <= MAX_INVALID_VALUE) {
            return;
        }

        if (mText instanceof SpannableString) {
            SpannableString ss = (SpannableString) mText;
            ss.setSpan(Selection.SELECTION_START, mSelectionStartIndex, mSelectionStartIndex, 0);
            ss.setSpan(Selection.SELECTION_END, mSelectionEndIndex, mSelectionEndIndex, 0);
        } else {
            Log.w(TAG, "Text is not a SpannableString.");
        }
    }

    /** @hide */
@@ -374,7 +442,9 @@ public final class ContentCaptureEvent implements Parcelable {
            throw new IllegalArgumentException("mergeEvent(): got "
                    + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
        } else if (eventType == TYPE_VIEW_TEXT_CHANGED) {
            setText(event.getText(), event.getTextHasComposingSpan());
            setText(event.getText());
            setComposingIndex(event.getComposingStart(), event.getComposingEnd());
            setSelectionIndex(event.getSelectionStart(), event.getSelectionEnd());
        } else {
            Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
                    + ") does not support this event type.");
@@ -409,6 +479,14 @@ public final class ContentCaptureEvent implements Parcelable {
        if (mInsets != null) {
            pw.print(", insets="); pw.println(mInsets);
        }
        if (mComposingStart > MAX_INVALID_VALUE) {
            pw.print(", composing("); pw.print(mComposingStart);
            pw.print(", "); pw.print(mComposingEnd); pw.print(")");
        }
        if (mSelectionStartIndex > MAX_INVALID_VALUE) {
            pw.print(", selection("); pw.print(mSelectionStartIndex);
            pw.print(", "); pw.print(mSelectionEndIndex); pw.print(")");
        }
    }

    @NonNull
@@ -443,6 +521,12 @@ public final class ContentCaptureEvent implements Parcelable {
        if (mInsets != null) {
            string.append(", insets=").append(mInsets);
        }
        if (mComposingStart > MAX_INVALID_VALUE) {
            string.append(", hasComposing");
        }
        if (mSelectionStartIndex > MAX_INVALID_VALUE) {
            string.append(", hasSelection");
        }
        return string.append(']').toString();
    }

@@ -469,6 +553,12 @@ public final class ContentCaptureEvent implements Parcelable {
        if (mType == TYPE_VIEW_INSETS_CHANGED) {
            parcel.writeParcelable(mInsets, flags);
        }
        if (mType == TYPE_VIEW_TEXT_CHANGED) {
            parcel.writeInt(mComposingStart);
            parcel.writeInt(mComposingEnd);
            parcel.writeInt(mSelectionStartIndex);
            parcel.writeInt(mSelectionEndIndex);
        }
    }

    public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR =
@@ -493,7 +583,7 @@ public final class ContentCaptureEvent implements Parcelable {
            if (node != null) {
                event.setViewNode(node);
            }
            event.setText(parcel.readCharSequence(), false);
            event.setText(parcel.readCharSequence());
            if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
                event.setParentSessionId(parcel.readInt());
            }
@@ -503,6 +593,12 @@ public final class ContentCaptureEvent implements Parcelable {
            if (type == TYPE_VIEW_INSETS_CHANGED) {
                event.setInsets(parcel.readParcelable(null));
            }
            if (type == TYPE_VIEW_TEXT_CHANGED) {
                event.setComposingIndex(parcel.readInt(), parcel.readInt());
                event.restoreComposingSpan();
                event.setSelectionIndex(parcel.readInt(), parcel.readInt());
                event.restoreSelectionSpans();
            }
            return event;
        }

+20 −7
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -347,8 +348,8 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
            //    2.2 last event doesn't have composing span: add.
            // Otherwise, merge.
            final CharSequence text = event.getText();
            final boolean textHasComposingSpan = event.getTextHasComposingSpan();
            if (textHasComposingSpan) {
            final boolean hasComposingSpan = event.hasComposingSpan();
            if (hasComposingSpan) {
                ContentCaptureEvent lastEvent = null;
                for (int index = mEvents.size() - 1; index >= 0; index--) {
                    final ContentCaptureEvent tmpEvent = mEvents.get(index);
@@ -357,7 +358,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
                        break;
                    }
                }
                if (lastEvent != null && lastEvent.getTextHasComposingSpan()) {
                if (lastEvent != null && lastEvent.hasComposingSpan()) {
                    final CharSequence lastText = lastEvent.getText();
                    final boolean bothNonEmpty = !TextUtils.isEmpty(lastText)
                            && !TextUtils.isEmpty(text);
@@ -705,12 +706,24 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
        // a copy of its content so that its value will not be changed by subsequent updates
        // in the TextView.
        final CharSequence eventText = stringOrSpannedStringWithoutNoCopySpans(text);
        final boolean textHasComposingSpan =
                text instanceof Spannable && BaseInputConnection.getComposingSpanStart(
                        (Spannable) text) >= 0;

        final int composingStart;
        final int composingEnd;
        if (text instanceof Spannable) {
            composingStart = BaseInputConnection.getComposingSpanStart((Spannable) text);
            composingEnd = BaseInputConnection.getComposingSpanEnd((Spannable) text);
        } else {
            composingStart = ContentCaptureEvent.MAX_INVALID_VALUE;
            composingEnd = ContentCaptureEvent.MAX_INVALID_VALUE;
        }

        final int startIndex = Selection.getSelectionStart(text);
        final int endIndex = Selection.getSelectionEnd(text);
        mHandler.post(() -> sendEvent(
                new ContentCaptureEvent(sessionId, TYPE_VIEW_TEXT_CHANGED)
                        .setAutofillId(id).setText(eventText, textHasComposingSpan)));
                        .setAutofillId(id).setText(eventText)
                        .setComposingIndex(composingStart, composingEnd)
                        .setSelectionIndex(startIndex, endIndex)));
    }

    private CharSequence stringOrSpannedStringWithoutNoCopySpans(CharSequence source) {
+8 −8
Original line number Diff line number Diff line
@@ -236,13 +236,13 @@ public class ContentCaptureEventTest {
    @Test
    public void testMergeEvent_typeViewTextChanged() {
        final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED)
                .setText("test", false);
                .setText("test");
        final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED)
                .setText("empty", true);
                .setText("composing").setComposingIndex(0, 1);

        event.mergeEvent(event2);
        assertThat(event.getText()).isEqualTo(event2.getText());
        assertThat(event.getTextHasComposingSpan()).isEqualTo(event2.getTextHasComposingSpan());
        assertThat(event.hasComposingSpan()).isEqualTo(event2.hasComposingSpan());
    }

    @Test
@@ -283,18 +283,18 @@ public class ContentCaptureEventTest {
    @Test
    public void testMergeEvent_differentEventTypes() {
        final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED)
                .setText("test", false).setAutofillId(new AutofillId(1));
                .setText("test").setAutofillId(new AutofillId(1));
        final ContentCaptureEvent event2 = new ContentCaptureEvent(17, TYPE_VIEW_TEXT_CHANGED)
                .setText("empty", true).setAutofillId(new AutofillId(2));
                .setText("composing").setAutofillId(new AutofillId(2)).setComposingIndex(0, 1);

        event.mergeEvent(event2);
        assertThat(event.getText()).isEqualTo("test");
        assertThat(event.getTextHasComposingSpan()).isFalse();
        assertThat(event.hasComposingSpan()).isFalse();
        assertThat(event.getId()).isEqualTo(new AutofillId(1));

        event2.mergeEvent(event);
        assertThat(event2.getText()).isEqualTo("empty");
        assertThat(event2.getTextHasComposingSpan()).isTrue();
        assertThat(event2.getText()).isEqualTo("composing");
        assertThat(event2.hasComposingSpan()).isTrue();
        assertThat(event2.getId()).isEqualTo(new AutofillId(2));
    }