From e9490612d5d436016f81195f2ef79c1e326b8960 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 31 Oct 2022 08:08:54 -0700 Subject: [PATCH] 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 3e3ff1a3d298f74246597cb10529e52e96b0d7b9 Bug: 234882948 Bug: 239783077 Test: presubmit Test: atest FrameworksCoreTests:ParcelableHandwritingGestureTest Change-Id: I158026087653f6772a78cc2394e678ae9741fb00 --- .../IRemoteInputConnectionInvoker.java | 49 +----- .../RemoteInputConnection.java | 4 +- .../ParcelableHandwritingGesture.aidl | 19 +++ .../ParcelableHandwritingGesture.java | 109 ++++++++++++++ .../RemoteInputConnectionImpl.java | 59 +------- .../inputmethod/IRemoteInputConnection.aidl | 30 +--- .../ParcelableHandwritingGestureTest.java | 141 ++++++++++++++++++ 7 files changed, 284 insertions(+), 127 deletions(-) create mode 100644 core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl create mode 100644 core/java/android/view/inputmethod/ParcelableHandwritingGesture.java create mode 100644 core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index 4f09beec81dda..49123aa7daa8b 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -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( diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 09e86c4c96501..7d8dd5e82c5af 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -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 diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl new file mode 100644 index 0000000000000..ffadf820325ec --- /dev/null +++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl @@ -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; diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java new file mode 100644 index 0000000000000..e4066fcfd61df --- /dev/null +++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java @@ -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 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); + } +} diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index f2b70997de63d..e8e7f3a644275 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -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 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 */) diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl index ea5c9a33b7623..f38cac7d3bba0 100644 --- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl +++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl @@ -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); diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java new file mode 100644 index 0000000000000..79aeaa39bc7c3 --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java @@ -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(); + } + } + } +}