Merge "Make some NoCopySpans can pass to ContentCaptureService" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7063bae045
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user