Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e3ff1a3 authored by Taran Singh's avatar Taran Singh
Browse files

Introduce Scribe Rich gestures API

During a stylus handwriting session, user can perform a
stylus gesture operation like  Select, delete, insert on an
area of text. IME needs APIs to perform these gestures on text.
This CL introduces API signature for Select, delete, insert gestures.

Design doc: go/scribe-gestures-api

Bug: 239783077
Bug: 210039666
Test: Manually build & compile HandwritingIme

Change-Id: I53bcb62e03ac1c371feb60d1385c88c921754092
parent 1fa3c5ed
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -52985,6 +52985,22 @@ package android.view.inputmethod {
    method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
  }
  public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public android.graphics.RectF getDeletionArea();
    method public int getGranularity();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.DeleteGesture> CREATOR;
  }
  public static final class DeleteGesture.Builder {
    ctor public DeleteGesture.Builder();
    method @NonNull public android.view.inputmethod.DeleteGesture build();
    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setDeletionArea(@NonNull android.graphics.RectF);
    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setFallbackText(@Nullable String);
    method @NonNull public android.view.inputmethod.DeleteGesture.Builder setGranularity(int);
  }
  public final class EditorBoundsInfo implements android.os.Parcelable {
    method public int describeContents();
    method @Nullable public android.graphics.RectF getEditorBounds();
@@ -53079,6 +53095,12 @@ package android.view.inputmethod {
    field public int token;
  }
  public abstract class HandwritingGesture {
    method @Nullable public String getFallbackText();
    field public static final int GRANULARITY_CHARACTER = 2; // 0x2
    field public static final int GRANULARITY_WORD = 1; // 0x1
  }
  public final class InlineSuggestion implements android.os.Parcelable {
    method public int describeContents();
    method @NonNull public android.view.inputmethod.InlineSuggestionInfo getInfo();
@@ -53169,6 +53191,7 @@ package android.view.inputmethod {
    method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
    method public boolean performContextMenuAction(int);
    method public boolean performEditorAction(int);
    method public default void performHandwritingGesture(@NonNull android.view.inputmethod.HandwritingGesture, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.IntConsumer);
    method public boolean performPrivateCommand(String, android.os.Bundle);
    method public default boolean performSpellCheck();
    method public boolean reportFullscreenMode(boolean);
@@ -53390,6 +53413,38 @@ package android.view.inputmethod {
    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
  }
  public final class InsertGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
    method public int describeContents();
    method @Nullable public android.graphics.PointF getInsertionPoint();
    method @Nullable public String getTextToInsert();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InsertGesture> CREATOR;
  }
  public static final class InsertGesture.Builder {
    ctor public InsertGesture.Builder();
    method @NonNull public android.view.inputmethod.InsertGesture build();
    method @NonNull public android.view.inputmethod.InsertGesture.Builder setFallbackText(@Nullable String);
    method @NonNull public android.view.inputmethod.InsertGesture.Builder setInsertionPoint(@NonNull android.graphics.PointF);
    method @NonNull public android.view.inputmethod.InsertGesture.Builder setTextToInsert(@NonNull String);
  }
  public final class SelectGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
    method public int describeContents();
    method public int getGranularity();
    method @NonNull public android.graphics.RectF getSelectionArea();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SelectGesture> CREATOR;
  }
  public static final class SelectGesture.Builder {
    ctor public SelectGesture.Builder();
    method @NonNull public android.view.inputmethod.SelectGesture build();
    method @NonNull public android.view.inputmethod.SelectGesture.Builder setFallbackText(@Nullable String);
    method @NonNull public android.view.inputmethod.SelectGesture.Builder setGranularity(int);
    method @NonNull public android.view.inputmethod.SelectGesture.Builder setSelectionArea(@NonNull android.graphics.RectF);
  }
  public final class SurroundingText implements android.os.Parcelable {
    ctor public SurroundingText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
    method public int describeContents();
+28 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.inputmethodservice;

import android.annotation.AnyThread;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
@@ -24,9 +25,13 @@ import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;

@@ -35,6 +40,8 @@ import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.InputConnectionCommandHeader;

import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

/**
 * A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to
@@ -591,6 +598,27 @@ final class IRemoteInputConnectionInvoker {
        }
    }

    @AnyThread
    public void performHandwritingGesture(
            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
            @Nullable IntConsumer consumer) {
        // TODO(b/210039666): implement resultReceiver
        try {
            if (gesture instanceof SelectGesture) {
                mConnection.performHandwritingSelectGesture(
                        createHeader(), (SelectGesture) gesture, null);
            } else if (gesture instanceof InsertGesture) {
                mConnection.performHandwritingInsertGesture(
                        createHeader(), (InsertGesture) gesture, null);
            } else if (gesture instanceof DeleteGesture) {
                mConnection.performHandwritingDeleteGesture(
                        createHeader(), (DeleteGesture) gesture, null);
            }
        } catch (RemoteException e) {
            // TODO(b/210039666): return result
        }
    }

    /**
     * Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
     * int, AndroidFuture)}.
+11 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.inputmethodservice;

import android.annotation.AnyThread;
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +29,7 @@ import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
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.SurroundingText;
@@ -41,6 +43,8 @@ import com.android.internal.inputmethod.InputConnectionProtoDumper;

import java.lang.ref.WeakReference;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;

/**
 * Takes care of remote method invocations of {@link InputConnection} in the IME side.
@@ -410,6 +414,13 @@ final class RemoteInputConnection implements InputConnection {
        return mInvoker.performPrivateCommand(action, data);
    }

    @AnyThread
    public void performHandwritingGesture(
            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
            @Nullable IntConsumer consumer) {
        mInvoker.performHandwritingGesture(gesture, executor, consumer);
    }

    @AnyThread
    public boolean requestCursorUpdates(int cursorUpdateMode) {
        if (mCancellationGroup.isCanceled()) {
+19 −0
Original line number Diff line number Diff line
/*
 * 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 DeleteGesture;
 No newline at end of file
+193 −0
Original line number Diff line number Diff line
/*
 * 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.annotation.Nullable;
import android.annotation.SuppressLint;
import android.graphics.RectF;
import android.os.Parcel;
import android.os.Parcelable;
import android.widget.TextView;

import java.util.Objects;

/**
 * A sub-class of {@link HandwritingGesture} for deleting an area of text.
 * This class holds the information required for deletion of text in
 * toolkit widgets like {@link TextView}.
 */
public final class DeleteGesture extends HandwritingGesture implements Parcelable {

    private @Granularity int mGranularity;
    private RectF mArea;

    private DeleteGesture(@Granularity int granularity, RectF area, String fallbackText) {
        mArea = area;
        mGranularity = granularity;
        mFallbackText = fallbackText;
    }

    private DeleteGesture(@NonNull final Parcel source) {
        mFallbackText = source.readString8();
        mGranularity = source.readInt();
        mArea = source.readTypedObject(RectF.CREATOR);
    }

    /**
     * Returns Granular level on which text should be operated.
     * @see HandwritingGesture#GRANULARITY_CHARACTER
     * @see HandwritingGesture#GRANULARITY_WORD
     */
    @Granularity
    public int getGranularity() {
        return mGranularity;
    }

    /**
     * Returns the deletion area {@link RectF} in screen coordinates.
     *
     * Getter for deletion area set with {@link DeleteGesture.Builder#setDeletionArea(RectF)}.
     * {@code null} if area was not set.
     */
    @NonNull
    public RectF getDeletionArea() {
        return mArea;
    }

    /**
     * Builder for {@link DeleteGesture}. This class is not designed to be thread-safe.
     */
    public static final class Builder {
        private int mGranularity;
        private RectF mArea;
        private String mFallbackText;

        /**
         * Set text deletion granularity. Intersecting words/characters will be
         * included in the operation.
         * @param granularity {@link HandwritingGesture#GRANULARITY_WORD} or
         * {@link HandwritingGesture#GRANULARITY_CHARACTER}.
         * @return {@link Builder}.
         */
        @NonNull
        @SuppressLint("MissingGetterMatchingBuilder")
        public Builder setGranularity(@Granularity int granularity) {
            mGranularity = granularity;
            return this;
        }

        /**
         * Set rectangular single/multiline text deletion area intersecting with text.
         *
         * The resulting deletion would be performed for all text intersecting rectangle. The
         * deletion includes the first word/character in the rectangle, and the last
         * word/character in the rectangle, and includes  everything in between even if it's not
         * in the rectangle.
         *
         * Intersection is determined using
         * {@link #setGranularity(int)}. e.g. {@link HandwritingGesture#GRANULARITY_WORD} includes
         * all the words with their width/height center included in the deletion rectangle.
         * @param area {@link RectF} (in screen coordinates) for which text will be deleted.
         * @see HandwritingGesture#GRANULARITY_WORD
         * @see HandwritingGesture#GRANULARITY_CHARACTER
         */
        @SuppressLint("MissingGetterMatchingBuilder")
        @NonNull
        public Builder setDeletionArea(@NonNull RectF area) {
            mArea = area;
            return this;
        }

        /**
         * Set fallback text that will be committed at current cursor position if there is no
         * applicable text beneath the area of gesture.
         * @param fallbackText text to set
         */
        @NonNull
        public Builder setFallbackText(@Nullable String fallbackText) {
            mFallbackText = fallbackText;
            return this;
        }

        /**
         * @return {@link DeleteGesture} using parameters in this {@link DeleteGesture.Builder}.
         * @throws IllegalArgumentException if one or more positional parameters are not specified.
         */
        @NonNull
        public DeleteGesture build() {
            if (mArea == null || mArea.isEmpty()) {
                throw new IllegalArgumentException("Deletion area must be set.");
            }
            if (mGranularity <= GRANULARITY_UNDEFINED) {
                throw new IllegalArgumentException("Deletion granularity must be set.");
            }
            return new DeleteGesture(mGranularity, mArea, mFallbackText);
        }
    }

    /**
     * Used to make this class parcelable.
     */
    public static final @android.annotation.NonNull Creator<DeleteGesture> CREATOR =
            new Creator<DeleteGesture>() {
        @Override
        public DeleteGesture createFromParcel(Parcel source) {
            return new DeleteGesture(source);
        }

        @Override
        public DeleteGesture[] newArray(int size) {
            return new DeleteGesture[size];
        }
    };

    @Override
    public int hashCode() {
        return Objects.hash(mArea, mGranularity, mFallbackText);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DeleteGesture)) return false;

        DeleteGesture that = (DeleteGesture) o;

        if (mGranularity != that.mGranularity) return false;
        if (!Objects.equals(mFallbackText, that.mFallbackText)) return false;
        return Objects.equals(mArea, that.mArea);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * Used to package this object into a {@link Parcel}.
     *
     * @param dest The {@link Parcel} to be written.
     * @param flags The flags used for parceling.
     */
    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString8(mFallbackText);
        dest.writeInt(mGranularity);
        dest.writeTypedObject(mArea, flags);
    }
}
Loading