Introduce @hide ParcelableHandwritingGesture

This is a follow up CL to our previous CL [1], which introduced
HandwritingGesture and several subclasses.

One of the challenge is that we only wanted to make child classes
Parcelable, while keeping the abstract base class HandwritingGesture
non-Parcelable.  Although InputConnection has only one method

  InputConnection#performHandwritingGesture(
          HandwritingGesture, ResultReceiver),

in the IPC layer IRemoteInputConnection needed to define methods for
each subclass, e.g.

 * performHandwritingSelectGesture(in InputConnectionCommandHeader,
       in SelectGesture, ...)
 * performHandwritingInsertGesture(in InputConnectionCommandHeader,
       in InsertGesture, ...)
 * performHandwritingDeleteGesture(in InputConnectionCommandHeader,
       in DeleteGesture, ...)

because their abstract class HandwritingGesture is not Parcelable.

To avoid such method definitions (and lots of type-based dispatching /
merging logic), this CL introduces

  ParcelableHandwritingGesture

as a generic Parcelable container of HandwritingGesture subclasses.
You can freely use ParcelableHandwritingGesture in AIDL method
definitions as follows.

  performHandwritingGesture(in InputConnectionCommandHeader,
        in ParcelableHandwritingGesture, ...)

In the Java side, you can easily wrap objects as follows.

  var parcelableGesture = ParcelableHandwritingGesture.of(gesture);
  var unparceledGesture = parcelableGesture.get();

Note that this CL is still an internal cleanup. There must be no
developer observable behavior change.

 [1]: I53bcb62e03ac1c371feb60d1385c88c921754092
      3e3ff1a3d2

Bug: 234882948
Bug: 239783077
Test: presubmit
Test: atest FrameworksCoreTests:ParcelableHandwritingGestureTest
Change-Id: I158026087653f6772a78cc2394e678ae9741fb00
This commit is contained in:
Yohei Yukawa
2022-10-31 08:08:54 -07:00
parent a99bb97e67
commit e9490612d5
7 changed files with 284 additions and 127 deletions

View File

@@ -26,18 +26,12 @@ import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
@@ -637,50 +631,19 @@ final class IRemoteInputConnectionInvoker {
}
/**
* Invokes one of {@link IRemoteInputConnection#performHandwritingSelectGesture},
* {@link IRemoteInputConnection#performHandwritingSelectRangeGesture},
* {@link IRemoteInputConnection#performHandwritingDeleteGesture},
* {@link IRemoteInputConnection#performHandwritingDeleteRangeGesture},
* {@link IRemoteInputConnection#performHandwritingInsertGesture},
* {@link IRemoteInputConnection#performHandwritingRemoveSpaceGesture},
* {@link IRemoteInputConnection#performHandwritingJoinOrSplitGesture}.
* Invokes {@link IRemoteInputConnection#performHandwritingGesture(
* InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
*/
@AnyThread
public void performHandwritingGesture(
@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@Nullable IntConsumer consumer) {
public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture,
@Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
ResultReceiver resultReceiver = null;
if (consumer != null) {
Objects.requireNonNull(executor);
resultReceiver = new IntResultReceiver(executor, consumer);
}
try {
if (gesture instanceof SelectGesture) {
mConnection.performHandwritingSelectGesture(
createHeader(), (SelectGesture) gesture, resultReceiver);
} else if (gesture instanceof SelectRangeGesture) {
mConnection.performHandwritingSelectRangeGesture(
createHeader(), (SelectRangeGesture) gesture, resultReceiver);
} else if (gesture instanceof InsertGesture) {
mConnection.performHandwritingInsertGesture(
createHeader(), (InsertGesture) gesture, resultReceiver);
} else if (gesture instanceof DeleteGesture) {
mConnection.performHandwritingDeleteGesture(
createHeader(), (DeleteGesture) gesture, resultReceiver);
} else if (gesture instanceof DeleteRangeGesture) {
mConnection.performHandwritingDeleteRangeGesture(
createHeader(), (DeleteRangeGesture) gesture, resultReceiver);
} else if (gesture instanceof RemoveSpaceGesture) {
mConnection.performHandwritingRemoveSpaceGesture(
createHeader(), (RemoveSpaceGesture) gesture, resultReceiver);
} else if (gesture instanceof JoinOrSplitGesture) {
mConnection.performHandwritingJoinOrSplitGesture(
createHeader(), (JoinOrSplitGesture) gesture, resultReceiver);
} else if (consumer != null && executor != null) {
executor.execute(()
-> consumer.accept(InputConnection.HANDWRITING_GESTURE_RESULT_UNSUPPORTED));
}
mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver);
} catch (RemoteException e) {
if (consumer != null && executor != null) {
executor.execute(() -> consumer.accept(

View File

@@ -32,6 +32,7 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
@@ -418,7 +419,8 @@ final class RemoteInputConnection implements InputConnection {
public void performHandwritingGesture(
@NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
@Nullable IntConsumer consumer) {
mInvoker.performHandwritingGesture(gesture, executor, consumer);
mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor,
consumer);
}
@AnyThread

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.inputmethod;
parcelable ParcelableHandwritingGesture;

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.inputmethod;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
/**
* A generic container of parcelable {@link HandwritingGesture}.
*
* @hide
*/
public final class ParcelableHandwritingGesture implements Parcelable {
@NonNull
private final HandwritingGesture mGesture;
@NonNull
private final Parcelable mGestureAsParcelable;
private ParcelableHandwritingGesture(@NonNull HandwritingGesture gesture) {
mGesture = gesture;
// For fail-fast.
mGestureAsParcelable = (Parcelable) gesture;
}
/**
* Creates {@link ParcelableHandwritingGesture} from {@link HandwritingGesture}, which also
* implements {@link Parcelable}.
*
* @param gesture {@link HandwritingGesture} object to be stored.
* @return {@link ParcelableHandwritingGesture} to be stored in {@link Parcel}.
*/
@NonNull
public static ParcelableHandwritingGesture of(@NonNull HandwritingGesture gesture) {
return new ParcelableHandwritingGesture(Objects.requireNonNull(gesture));
}
/**
* @return {@link HandwritingGesture} object stored in this container.
*/
@NonNull
public HandwritingGesture get() {
return mGesture;
}
private static HandwritingGesture createFromParcelInternal(
@HandwritingGesture.GestureType int gestureType, @NonNull Parcel parcel) {
switch (gestureType) {
case HandwritingGesture.GESTURE_TYPE_NONE:
throw new UnsupportedOperationException("GESTURE_TYPE_NONE is not supported");
case HandwritingGesture.GESTURE_TYPE_SELECT:
return SelectGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_SELECT_RANGE:
return SelectRangeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_INSERT:
return InsertGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE:
return DeleteGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
return DeleteRangeGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_JOIN_OR_SPLIT:
return JoinOrSplitGesture.CREATOR.createFromParcel(parcel);
case HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE:
return RemoveSpaceGesture.CREATOR.createFromParcel(parcel);
default:
throw new UnsupportedOperationException("Unknown type=" + gestureType);
}
}
public static final Creator<ParcelableHandwritingGesture> CREATOR = new Parcelable.Creator<>() {
@Override
public ParcelableHandwritingGesture createFromParcel(Parcel in) {
final int gestureType = in.readInt();
return new ParcelableHandwritingGesture(createFromParcelInternal(gestureType, in));
}
@Override
public ParcelableHandwritingGesture[] newArray(int size) {
return new ParcelableHandwritingGesture[size];
}
};
@Override
public int describeContents() {
return mGestureAsParcelable.describeContents();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mGesture.getGestureType());
mGestureAsParcelable.writeToParcel(dest, flags);
}
}

View File

@@ -982,62 +982,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
@Dispatching(cancellable = true)
@Override
public void performHandwritingSelectGesture(
InputConnectionCommandHeader header, SelectGesture gesture,
public void performHandwritingGesture(
InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingSelectRangeGesture(
InputConnectionCommandHeader header, SelectRangeGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingInsertGesture(
InputConnectionCommandHeader header, InsertGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingDeleteGesture(
InputConnectionCommandHeader header, DeleteGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingDeleteRangeGesture(
InputConnectionCommandHeader header, DeleteRangeGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingRemoveSpaceGesture(
InputConnectionCommandHeader header, RemoveSpaceGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
@Dispatching(cancellable = true)
@Override
public void performHandwritingJoinOrSplitGesture(
InputConnectionCommandHeader header, JoinOrSplitGesture gesture,
ResultReceiver resultReceiver) {
performHandwritingGestureInternal(header, gesture, resultReceiver);
}
private <T extends HandwritingGesture> void performHandwritingGestureInternal(
InputConnectionCommandHeader header, T gesture, ResultReceiver resultReceiver) {
dispatchWithTracing("performHandwritingGesture", () -> {
if (header.mSessionId != mCurrentSessionId.get()) {
if (resultReceiver != null) {
@@ -1059,7 +1006,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
// TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if
// editor doesn't return any type.
ic.performHandwritingGesture(
gesture,
gestureContainer.get(),
resultReceiver != null ? Runnable::run : null,
resultReceiver != null
? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */)

View File

@@ -21,15 +21,9 @@ import android.os.ResultReceiver;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.DeleteRangeGesture;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
import android.view.inputmethod.ParcelableHandwritingGesture;
import android.view.inputmethod.TextAttribute;
import com.android.internal.infra.AndroidFuture;
@@ -94,26 +88,8 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
void performPrivateCommand(in InputConnectionCommandHeader header, String action,
in Bundle data);
void performHandwritingSelectGesture(in InputConnectionCommandHeader header,
in SelectGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingSelectRangeGesture(in InputConnectionCommandHeader header,
in SelectRangeGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingInsertGesture(in InputConnectionCommandHeader header,
in InsertGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingDeleteGesture(in InputConnectionCommandHeader header,
in DeleteGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingDeleteRangeGesture(in InputConnectionCommandHeader header,
in DeleteRangeGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingRemoveSpaceGesture(in InputConnectionCommandHeader header,
in RemoveSpaceGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingJoinOrSplitGesture(in InputConnectionCommandHeader header,
in JoinOrSplitGesture gesture, in ResultReceiver resultReceiver);
void performHandwritingGesture(in InputConnectionCommandHeader header,
in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver);
void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.inputmethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import android.annotation.NonNull;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ParcelableHandwritingGestureTest {
@Test
public void testCreationFailWithNullPointerException() {
assertThrows(NullPointerException.class, () -> ParcelableHandwritingGesture.of(null));
}
@Test
public void testInvalidTypeHeader() {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
// GESTURE_TYPE_NONE is not a supported header.
parcel.writeInt(HandwritingGesture.GESTURE_TYPE_NONE);
final Parcel initializedParcel = parcel;
assertThrows(UnsupportedOperationException.class,
() -> ParcelableHandwritingGesture.CREATOR.createFromParcel(initializedParcel));
} finally {
if (parcel != null) {
parcel.recycle();
}
}
}
@Test
public void testSelectGesture() {
verifyEqualityAfterUnparcel(new SelectGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
.setSelectionArea(new RectF(1, 2, 3, 4))
.setFallbackText("")
.build());
}
@Test
public void testSelectRangeGesture() {
verifyEqualityAfterUnparcel(new SelectRangeGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
.setSelectionStartArea(new RectF(1, 2, 3, 4))
.setSelectionEndArea(new RectF(5, 6, 7, 8))
.setFallbackText("")
.build());
}
@Test
public void testInsertGestureGesture() {
verifyEqualityAfterUnparcel(new InsertGesture.Builder()
.setTextToInsert("text")
.setInsertionPoint(new PointF(1, 1)).setFallbackText("")
.build());
}
@Test
public void testDeleteGestureGesture() {
verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
.setDeletionArea(new RectF(1, 2, 3, 4))
.setFallbackText("")
.build());
}
@Test
public void testDeleteRangeGestureGesture() {
verifyEqualityAfterUnparcel(new DeleteRangeGesture.Builder()
.setGranularity(HandwritingGesture.GRANULARITY_WORD)
.setDeletionStartArea(new RectF(1, 2, 3, 4))
.setDeletionEndArea(new RectF(5, 6, 7, 8))
.setFallbackText("")
.build());
}
@Test
public void testRemoveSpaceGestureGesture() {
verifyEqualityAfterUnparcel(new RemoveSpaceGesture.Builder()
.setPoints(new PointF(1f, 2f), new PointF(3f, 4f))
.setFallbackText("")
.build());
}
@Test
public void testJoinOrSplitGestureGesture() {
verifyEqualityAfterUnparcel(new JoinOrSplitGesture.Builder()
.setJoinOrSplitPoint(new PointF(1f, 2f))
.setFallbackText("")
.build());
}
static void verifyEqualityAfterUnparcel(@NonNull HandwritingGesture gesture) {
assertEquals(gesture, cloneViaParcel(ParcelableHandwritingGesture.of(gesture)).get());
}
private static ParcelableHandwritingGesture cloneViaParcel(
@NonNull ParcelableHandwritingGesture original) {
Parcel parcel = null;
try {
parcel = Parcel.obtain();
original.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel);
} finally {
if (parcel != null) {
parcel.recycle();
}
}
}
}