Merge "Make some NoCopySpans can pass to ContentCaptureService" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-15 18:52:36 +00:00
committed by Android (Google) Code Review
3 changed files with 134 additions and 25 deletions

View File

@@ -25,8 +25,12 @@ import android.annotation.SystemApi;
import android.graphics.Insets; import android.graphics.Insets;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.Log; import android.util.Log;
import android.view.autofill.AutofillId; import android.view.autofill.AutofillId;
import android.view.inputmethod.BaseInputConnection;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
@@ -132,6 +136,9 @@ public final class ContentCaptureEvent implements Parcelable {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface EventType{} public @interface EventType{}
/** @hide */
public static final int MAX_INVALID_VALUE = -1;
private final int mSessionId; private final int mSessionId;
private final int mType; private final int mType;
private final long mEventTime; private final long mEventTime;
@@ -143,6 +150,11 @@ public final class ContentCaptureEvent implements Parcelable {
private @Nullable ContentCaptureContext mClientContext; private @Nullable ContentCaptureContext mClientContext;
private @Nullable Insets mInsets; 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 */ /** Only used in the main Content Capture session, no need to parcel */
private boolean mTextHasComposingSpan; private boolean mTextHasComposingSpan;
@@ -246,19 +258,75 @@ public final class ContentCaptureEvent implements Parcelable {
/** @hide */ /** @hide */
@NonNull @NonNull
public ContentCaptureEvent setText(@Nullable CharSequence text, boolean hasComposingSpan) { public ContentCaptureEvent setText(@Nullable CharSequence text) {
mText = text; mText = text;
mTextHasComposingSpan = hasComposingSpan;
return this; return this;
} }
/** /** @hide */
* The value is not parcelled, become false after parcelled.
* @hide
*/
@NonNull @NonNull
public boolean getTextHasComposingSpan() { public ContentCaptureEvent setComposingIndex(int start, int end) {
return mTextHasComposingSpan; mComposingStart = start;
mComposingEnd = end;
return this;
}
/** @hide */
@NonNull
public boolean hasComposingSpan() {
return mComposingStart > MAX_INVALID_VALUE;
}
/** @hide */
@NonNull
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 */ /** @hide */
@@ -374,7 +442,9 @@ public final class ContentCaptureEvent implements Parcelable {
throw new IllegalArgumentException("mergeEvent(): got " throw new IllegalArgumentException("mergeEvent(): got "
+ "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event); + "TYPE_VIEW_DISAPPEARED event with neither id or ids: " + event);
} else if (eventType == TYPE_VIEW_TEXT_CHANGED) { } 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 { } else {
Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType) Log.e(TAG, "mergeEvent(" + getTypeAsString(eventType)
+ ") does not support this event type."); + ") does not support this event type.");
@@ -409,6 +479,14 @@ public final class ContentCaptureEvent implements Parcelable {
if (mInsets != null) { if (mInsets != null) {
pw.print(", insets="); pw.println(mInsets); 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 @NonNull
@@ -443,6 +521,12 @@ public final class ContentCaptureEvent implements Parcelable {
if (mInsets != null) { if (mInsets != null) {
string.append(", insets=").append(mInsets); 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(); return string.append(']').toString();
} }
@@ -469,6 +553,12 @@ public final class ContentCaptureEvent implements Parcelable {
if (mType == TYPE_VIEW_INSETS_CHANGED) { if (mType == TYPE_VIEW_INSETS_CHANGED) {
parcel.writeParcelable(mInsets, flags); 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 = public static final @android.annotation.NonNull Parcelable.Creator<ContentCaptureEvent> CREATOR =
@@ -493,7 +583,7 @@ public final class ContentCaptureEvent implements Parcelable {
if (node != null) { if (node != null) {
event.setViewNode(node); event.setViewNode(node);
} }
event.setText(parcel.readCharSequence(), false); event.setText(parcel.readCharSequence());
if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) { if (type == TYPE_SESSION_STARTED || type == TYPE_SESSION_FINISHED) {
event.setParentSessionId(parcel.readInt()); event.setParentSessionId(parcel.readInt());
} }
@@ -503,6 +593,12 @@ public final class ContentCaptureEvent implements Parcelable {
if (type == TYPE_VIEW_INSETS_CHANGED) { if (type == TYPE_VIEW_INSETS_CHANGED) {
event.setInsets(parcel.readParcelable(null)); 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; return event;
} }

View File

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

View File

@@ -236,13 +236,13 @@ public class ContentCaptureEventTest {
@Test @Test
public void testMergeEvent_typeViewTextChanged() { public void testMergeEvent_typeViewTextChanged() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_TEXT_CHANGED) 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) final ContentCaptureEvent event2 = new ContentCaptureEvent(43, TYPE_VIEW_TEXT_CHANGED)
.setText("empty", true); .setText("composing").setComposingIndex(0, 1);
event.mergeEvent(event2); event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo(event2.getText()); assertThat(event.getText()).isEqualTo(event2.getText());
assertThat(event.getTextHasComposingSpan()).isEqualTo(event2.getTextHasComposingSpan()); assertThat(event.hasComposingSpan()).isEqualTo(event2.hasComposingSpan());
} }
@Test @Test
@@ -283,18 +283,18 @@ public class ContentCaptureEventTest {
@Test @Test
public void testMergeEvent_differentEventTypes() { public void testMergeEvent_differentEventTypes() {
final ContentCaptureEvent event = new ContentCaptureEvent(42, TYPE_VIEW_DISAPPEARED) 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) 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); event.mergeEvent(event2);
assertThat(event.getText()).isEqualTo("test"); assertThat(event.getText()).isEqualTo("test");
assertThat(event.getTextHasComposingSpan()).isFalse(); assertThat(event.hasComposingSpan()).isFalse();
assertThat(event.getId()).isEqualTo(new AutofillId(1)); assertThat(event.getId()).isEqualTo(new AutofillId(1));
event2.mergeEvent(event); event2.mergeEvent(event);
assertThat(event2.getText()).isEqualTo("empty"); assertThat(event2.getText()).isEqualTo("composing");
assertThat(event2.getTextHasComposingSpan()).isTrue(); assertThat(event2.hasComposingSpan()).isTrue();
assertThat(event2.getId()).isEqualTo(new AutofillId(2)); assertThat(event2.getId()).isEqualTo(new AutofillId(2));
} }