diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java index 7070000ab17c5..4a76193ab2897 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java @@ -142,6 +142,59 @@ public interface ScribeMethodHandler { void startStylusHandwriting(); } - // TODO(justinmc): Scribe stylus gestures should be supported here. - // https://github.com/flutter/flutter/issues/156018 + public void previewHandwritingGesture( + PreviewableHandwritingGesture gesture, CancellationSignal cancellationSignal) { + System.out.println("justin sending previewHandwritingGesture for gesture: " + gesture); + final HashMap gestureMap = new HashMap<>(); + if (gesture instanceof DeleteGesture) { + gestureMap.put("type", "delete"); + } else if (gesture instanceof DeleteRangeGesture) { + gestureMap.put("type", "deleteRange"); + } else if (gesture instanceof SelectGesture) { + gestureMap.put("type", "select"); + } else if (gesture instanceof SelectRangeGesture) { + gestureMap.put("type", "selectRange"); + } else { + return; + } + + // TODO(justinmc): You'll need to provide some kind of API that allows users + // to cancel a previewed gesture. Maybe keep ahold of cancellationSignal + // here, then provide platform channel methods for cancel, isCanceled, + // setOnCancelListener, and throwIfCanceled. + channel.invokeMethod("ScribeClient.previewHandwritingGesture", gestureMap); + } + + public void performHandwritingGesture(HandwritingGesture gesture, MethodChannel.Result result) { + System.out.println("justin sending performHandwritingGesture for gesture: " + gesture); + + final HashMap gestureMap = new HashMap<>(); + if (gesture instanceof SelectGesture) { + final SelectGesture selectGesture = (SelectGesture) gesture; + final HashMap selectionAreaMap = new HashMap<>(); + final RectF selectionArea = selectGesture.getSelectionArea(); + selectionAreaMap.put("bottom", selectionArea.bottom); + selectionAreaMap.put("top", selectionArea.top); + selectionAreaMap.put("left", selectionArea.left); + selectionAreaMap.put("right", selectionArea.right); + gestureMap.put("type", "select"); + gestureMap.put("granularity", selectGesture.getGranularity()); + gestureMap.put("selectionArea", selectionAreaMap); + } else if (gesture instanceof DeleteGesture) { + final DeleteGesture deleteGesture = (DeleteGesture) gesture; + final HashMap deletionAreaMap = new HashMap<>(); + final RectF deletionArea = deleteGesture.getDeletionArea(); + deletionAreaMap.put("bottom", deletionArea.bottom); + deletionAreaMap.put("top", deletionArea.top); + deletionAreaMap.put("left", deletionArea.left); + deletionAreaMap.put("right", deletionArea.right); + gestureMap.put("type", "delete"); + gestureMap.put("granularity", deleteGesture.getGranularity()); + gestureMap.put("deletionArea", deletionAreaMap); + } + // TODO(justinmc): All other gestures. + // https://developer.android.com/reference/android/view/inputmethod/HandwritingGesture#public-methods + + channel.invokeMethod("ScribeClient.performHandwritingGesture", gestureMap, result); + } } diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index aa96ea02645c9..cd2eae1f47b0f 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -13,6 +13,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.CancellationSignal; import android.text.DynamicLayout; import android.text.Editable; import android.text.InputType; @@ -26,8 +27,10 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputContentInfo; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.PreviewableHandwritingGesture; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.view.inputmethod.InputConnectionCompat; @@ -35,12 +38,15 @@ import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.systemchannels.ScribeChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; +import io.flutter.plugin.common.MethodChannel; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Executor; +import java.util.function.IntConsumer; public class InputConnectionAdaptor extends BaseInputConnection implements ListenableEditingState.EditingStateWatcher { @@ -275,9 +281,44 @@ public boolean setSelection(int start, int end) { return result; } - // TODO(justinmc): Scribe stylus gestures should be supported here via - // performHandwritingGesture. - // https://github.com/flutter/flutter/issues/156018 + @Override + public boolean previewHandwritingGesture( + PreviewableHandwritingGesture gesture, CancellationSignal cancellationSignal) { + System.out.println( + "justin previewHandwritingGesture gesture: " + gesture + ", " + cancellationSignal); + + scribeChannel.previewHandwritingGesture(gesture, cancellationSignal); + return true; + } + + @Override + public void performHandwritingGesture( + HandwritingGesture gesture, Executor executor, IntConsumer consumer) { + System.out.println("justin performHandwritingGesture gesture: " + gesture); + + final MethodChannel.Result result = + new MethodChannel.Result() { + @Override + public void success(Object result) { + executor.execute(() -> consumer.accept(HANDWRITING_GESTURE_RESULT_SUCCESS)); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + executor.execute(() -> consumer.accept(HANDWRITING_GESTURE_RESULT_FAILED)); + } + + @Override + public void notImplemented() { + executor.execute(() -> consumer.accept(HANDWRITING_GESTURE_RESULT_UNSUPPORTED)); + } + // TODO(justinmc): There are a few other HANDWRITING_GESTURE_RESULTs + // that may be useful, see + // https://developer.android.com/reference/android/view/inputmethod/InputConnection + }; + + scribeChannel.performHandwritingGesture((HandwritingGesture) gesture, result); + } // Sanitizes the index to ensure the index is within the range of the // contents of editable. diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 4f3adcec4d8a4..63fc311c26dca 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -20,9 +20,17 @@ import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; +import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.InsertModeGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; +import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -34,7 +42,9 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState; import io.flutter.plugin.platform.PlatformViewsController; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.Set; /** Android implementation of the text input plugin. */ public class TextInputPlugin implements ListenableEditingState.EditingStateWatcher { @@ -350,10 +360,25 @@ public InputConnection createInputConnection( if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) { EditorInfoCompat.setStylusHandwritingEnabled(outAttrs, true); } - // TODO(justinmc): Scribe stylus gestures should be supported here via - // outAttrs.setSupportedHandwritingGestures and - // outAttrs.setSupportedHandwritingGesturePreviews. - // https://github.com/flutter/flutter/issues/156018 + + EditorInfoCompat.setStylusHandwritingEnabled(outAttrs, true); + outAttrs.setSupportedHandwritingGestures( + Arrays.asList( + SelectGesture.class, + SelectRangeGesture.class, + InsertGesture.class, + InsertModeGesture.class, + DeleteGesture.class, + DeleteRangeGesture.class, + SelectRangeGesture.class, + JoinOrSplitGesture.class, + RemoveSpaceGesture.class)); + outAttrs.setSupportedHandwritingGesturePreviews( + Set.of( + SelectGesture.class, + SelectRangeGesture.class, + DeleteGesture.class, + DeleteRangeGesture.class)); InputConnectionAdaptor connection = new InputConnectionAdaptor(