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(); + } + } + } +}