Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
38f1ee1
Proof of concept that can call startStylusHandwriting
justinmc May 20, 2024
f5c45e2
Fix the notification 'this app does not support handwriting input'
justinmc May 21, 2024
2c70875
Correctly report success/error
justinmc May 21, 2024
2da6b2e
Show icon on hover
justinmc May 21, 2024
815e6cd
Trying the other gestures, but not getting called...
justinmc May 21, 2024
da21044
Use isStylusHandwritingAvailable, as recommended in the doc
justinmc May 22, 2024
cdf7366
TODO for upgraded androidx
justinmc May 23, 2024
a298ad0
Merge branch 'main' into scribe
justinmc May 28, 2024
39249a5
AndroidX has been upgraded, though the PR seems to be getting reverte…
justinmc May 28, 2024
14ff786
Overriding preview as well, and checking if we ever get a call (no)
justinmc May 28, 2024
8217442
Declare that we support all the handwriting gestures!
justinmc May 28, 2024
d5e0301
Well that's why that wasn't getting called
justinmc May 29, 2024
39a4c8e
Version gate
justinmc May 29, 2024
27ca8db
Send selection gesture to framework
justinmc May 29, 2024
521fb22
TODO for granularity
justinmc May 30, 2024
2a27c55
Merge branch 'main' into scribe
justinmc Sep 30, 2024
5046264
Auto formatting
justinmc Sep 30, 2024
f9856a2
Pointer functionality is being moved to a separate PR
justinmc Sep 30, 2024
989eb2a
WIP Generic handwriting gesture call
justinmc Oct 1, 2024
47bd6b7
WIP previewHandwritingGesture
justinmc Oct 1, 2024
948d387
Scribe gestures will be handled in a separate PR
justinmc Oct 1, 2024
c2f1425
isStylusHandwritingAvailable method
justinmc Oct 1, 2024
5d385c5
Don't need to call isStylusHandwritingAvailable, that's on the caller
justinmc Oct 1, 2024
0a54134
Remove support for gestures, now shows dialog when gesture performed
justinmc Oct 1, 2024
aa95c06
Let's keep it a separate channel for now and check in review
justinmc Oct 1, 2024
0962507
ScribePluginTest
justinmc Oct 1, 2024
885f4bb
Get tests working, and add basic tests for ScribePlugin
justinmc Oct 1, 2024
8ef8f49
License check fix
justinmc Oct 1, 2024
49fa99b
Test for ScribePlugin
justinmc Oct 2, 2024
585ac33
Enforce api levels
justinmc Oct 2, 2024
250adbc
Separate Plugin and Channel tests
justinmc Oct 2, 2024
e96d8bc
Only start scribe when api available
justinmc Oct 2, 2024
8056d17
Test for TextInputPlugin change
justinmc Oct 2, 2024
016223e
Add missing api_level guards
justinmc Oct 3, 2024
cc69690
Move method channel handlers to their own private methods
justinmc Oct 3, 2024
6f1a6d8
Misc Reid's review comments
justinmc Oct 3, 2024
1ae66f1
Test unsupported api levels in scribeplugintest. Allow it to be creat…
justinmc Oct 4, 2024
f019841
Some work on testing api versions in ScribeChannel
justinmc Oct 4, 2024
d802eb2
Works without deprecated method
justinmc Oct 4, 2024
3685c9d
Test ScribeChannel when old api level
justinmc Oct 4, 2024
0ceab93
Make view public so it could be updated if the view changes
justinmc Oct 4, 2024
bc4dafd
TestInputPlugin unsupported test
justinmc Oct 4, 2024
00621e2
Private but settable view
justinmc Oct 7, 2024
53212fc
Use jsonmethodcodec
justinmc Oct 7, 2024
02c8f43
Test reply value now that we're using jsonmethodcodec
justinmc Oct 7, 2024
6d86717
Fix test missing mocked scribechannel
justinmc Oct 7, 2024
44c22d5
Fix platformviewscontrollertest due to missing scribechannel again.
justinmc Oct 7, 2024
06edcdb
Fix mixed up api version in test
justinmc Oct 7, 2024
df68cb8
isFeatureAvailable convenience method
justinmc Oct 18, 2024
37cab6c
Merge branch 'main' into scribe
justinmc Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -44294,6 +44294,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java + ../../../flutter/LICENSE
Expand All @@ -44317,6 +44318,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Flutte
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ScribePlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java + ../../../flutter/LICENSE
Expand Down Expand Up @@ -47176,6 +47178,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java
Expand All @@ -47202,6 +47205,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterT
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ScribePlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ android_java_sources = [
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java",
"io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java",
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
"io/flutter/embedding/engine/systemchannels/ScribeChannel.java",
"io/flutter/embedding/engine/systemchannels/SettingsChannel.java",
"io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java",
"io/flutter/embedding/engine/systemchannels/SystemChannel.java",
Expand All @@ -320,6 +321,7 @@ android_java_sources = [
"io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java",
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
"io/flutter/plugin/editing/ListenableEditingState.java",
"io/flutter/plugin/editing/ScribePlugin.java",
"io/flutter/plugin/editing/SpellCheckPlugin.java",
"io/flutter/plugin/editing/TextEditingDelta.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.editing.ScribePlugin;
import io.flutter.plugin.editing.SpellCheckPlugin;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.localization.LocalizationPlugin;
Expand Down Expand Up @@ -133,6 +134,7 @@ public class FlutterView extends FrameLayout
@Nullable private MouseCursorPlugin mouseCursorPlugin;
@Nullable private TextInputPlugin textInputPlugin;
@Nullable private SpellCheckPlugin spellCheckPlugin;
@Nullable private ScribePlugin scribePlugin;
@Nullable private LocalizationPlugin localizationPlugin;
@Nullable private KeyboardManager keyboardManager;
@Nullable private AndroidTouchProcessor androidTouchProcessor;
Expand Down Expand Up @@ -1120,10 +1122,12 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
mouseCursorPlugin = new MouseCursorPlugin(this, this.flutterEngine.getMouseCursorChannel());
}

textInputPlugin =
new TextInputPlugin(
this,
this.flutterEngine.getTextInputChannel(),
this.flutterEngine.getScribeChannel(),
this.flutterEngine.getPlatformViewsController());

try {
Expand All @@ -1136,6 +1140,10 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
Log.e(TAG, "TextServicesManager not supported by device, spell check disabled.");
}

scribePlugin =
new ScribePlugin(
this, textInputPlugin.getInputMethodManager(), this.flutterEngine.getScribeChannel());

localizationPlugin = this.flutterEngine.getLocalizationPlugin();

keyboardManager = new KeyboardManager(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.ProcessTextChannel;
import io.flutter.embedding.engine.systemchannels.RestorationChannel;
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
Expand Down Expand Up @@ -100,6 +101,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
@NonNull private final RestorationChannel restorationChannel;
@NonNull private final PlatformChannel platformChannel;
@NonNull private final ProcessTextChannel processTextChannel;
@NonNull private final ScribeChannel scribeChannel;
@NonNull private final SettingsChannel settingsChannel;
@NonNull private final SpellCheckChannel spellCheckChannel;
@NonNull private final SystemChannel systemChannel;
Expand Down Expand Up @@ -337,6 +339,7 @@ public FlutterEngine(
platformChannel = new PlatformChannel(dartExecutor);
processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager());
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
scribeChannel = new ScribeChannel(dartExecutor);
settingsChannel = new SettingsChannel(dartExecutor);
spellCheckChannel = new SpellCheckChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
Expand Down Expand Up @@ -610,6 +613,12 @@ public TextInputChannel getTextInputChannel() {
return textInputChannel;
}

/** System channel that sends and receives Scribe requests and results. */
@NonNull
public ScribeChannel getScribeChannel() {
return scribeChannel;
}

/** System channel that sends and receives spell check requests and results. */
@NonNull
public SpellCheckChannel getSpellCheckChannel() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.embedding.engine.systemchannels;

import static io.flutter.Build.API_LEVELS;

import android.annotation.TargetApi;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

/**
* {@link ScribeChannel} is a platform channel that is used by the framework to facilitate the
* Scribe handwriting text input feature.
*/
public class ScribeChannel {
private static final String TAG = "ScribeChannel";

@VisibleForTesting
public static final String METHOD_IS_FEATURE_AVAILABLE = "Scribe.isFeatureAvailable";

@VisibleForTesting
public static final String METHOD_IS_STYLUS_HANDWRITING_AVAILABLE =
"Scribe.isStylusHandwritingAvailable";

@VisibleForTesting
public static final String METHOD_START_STYLUS_HANDWRITING = "Scribe.startStylusHandwriting";

public final MethodChannel channel;
private ScribeMethodHandler scribeMethodHandler;

@NonNull
public final MethodChannel.MethodCallHandler parsingMethodHandler =
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (scribeMethodHandler == null) {
Log.v(TAG, "No ScribeMethodHandler registered. Scribe call not handled.");
return;
}
String method = call.method;
Log.v(TAG, "Received '" + method + "' message.");
switch (method) {
case METHOD_IS_FEATURE_AVAILABLE:
isFeatureAvailable(call, result);
break;
case METHOD_IS_STYLUS_HANDWRITING_AVAILABLE:
isStylusHandwritingAvailable(call, result);
break;
case METHOD_START_STYLUS_HANDWRITING:
startStylusHandwriting(call, result);
break;
default:
result.notImplemented();
break;
}
}
};

private void isFeatureAvailable(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
try {
final boolean isAvailable = scribeMethodHandler.isFeatureAvailable();
result.success(isAvailable);
} catch (IllegalStateException exception) {
result.error("error", exception.getMessage(), null);
}
}

private void isStylusHandwritingAvailable(
@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (Build.VERSION.SDK_INT < API_LEVELS.API_34) {
result.error("error", "Requires API level 34 or higher.", null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that the dart side of the plugin needs to check if it is running on android and check the api level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess in practice this probably means that you want to call this wrapped in a try/catch in Dart. My thought process is that this method channel method is just a proxy for InputMethodManager.isStylusHandwritingAvailable. If I'm not even able to call that, I should error, rather than succeed with false, which the app developer might interpret to mean that InputMethodManager.isStylusHandwritingAvailable returned false.

If that sounds reasonable then I'll at least make sure this is clearly documented in the framework.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update here: #52943 (comment)

return;
}

try {
final boolean isAvailable = scribeMethodHandler.isStylusHandwritingAvailable();
result.success(isAvailable);
} catch (IllegalStateException exception) {
result.error("error", exception.getMessage(), null);
}
}

private void startStylusHandwriting(
@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (Build.VERSION.SDK_INT < API_LEVELS.API_33) {
result.error("error", "Requires API level 33 or higher.", null);
return;
}

try {
scribeMethodHandler.startStylusHandwriting();
result.success(null);
} catch (IllegalStateException exception) {
result.error("error", exception.getMessage(), null);
}
}

public ScribeChannel(@NonNull DartExecutor dartExecutor) {
channel = new MethodChannel(dartExecutor, "flutter/scribe", JSONMethodCodec.INSTANCE);
channel.setMethodCallHandler(parsingMethodHandler);
}

/**
* Sets the {@link ScribeMethodHandler} which receives all requests for scribe sent through this
* channel.
*/
public void setScribeMethodHandler(@Nullable ScribeMethodHandler scribeMethodHandler) {
this.scribeMethodHandler = scribeMethodHandler;
}

public interface ScribeMethodHandler {
/**
* Responds to the {@code result} with success and a boolean indicating whether or not stylus
* handwriting is available.
*/
boolean isFeatureAvailable();

/**
* Responds to the {@code result} with success and a boolean indicating whether or not stylus
* handwriting is available.
*/
@TargetApi(API_LEVELS.API_34)
@RequiresApi(API_LEVELS.API_34)
boolean isStylusHandwritingAvailable();

/**
* Requests to start Scribe stylus handwriting, which will respond to the {@code result} with
* either success if handwriting input has started or error otherwise.
*/
@TargetApi(API_LEVELS.API_33)
@RequiresApi(API_LEVELS.API_33)
void startStylusHandwriting();
}

// TODO(justinmc): Scribe stylus gestures should be supported here.
// https://github.com/flutter/flutter/issues/156018
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import androidx.core.view.inputmethod.InputConnectionCompat;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
Expand All @@ -51,6 +52,7 @@ public interface KeyboardDelegate {

private final View mFlutterView;
private final int mClient;
private final ScribeChannel scribeChannel;
private final TextInputChannel textInputChannel;
private final ListenableEditingState mEditable;
private final EditorInfo mEditorInfo;
Expand All @@ -69,6 +71,7 @@ public InputConnectionAdaptor(
View view,
int client,
TextInputChannel textInputChannel,
ScribeChannel scribeChannel,
KeyboardDelegate keyboardDelegate,
ListenableEditingState editable,
EditorInfo editorInfo,
Expand All @@ -77,6 +80,7 @@ public InputConnectionAdaptor(
mFlutterView = view;
mClient = client;
this.textInputChannel = textInputChannel;
this.scribeChannel = scribeChannel;
mEditable = editable;
mEditable.addEditingStateListener(this);
mEditorInfo = editorInfo;
Expand All @@ -100,10 +104,19 @@ public InputConnectionAdaptor(
View view,
int client,
TextInputChannel textInputChannel,
ScribeChannel scribeChannel,
KeyboardDelegate keyboardDelegate,
ListenableEditingState editable,
EditorInfo editorInfo) {
this(view, client, textInputChannel, keyboardDelegate, editable, editorInfo, new FlutterJNI());
this(
view,
client,
textInputChannel,
scribeChannel,
keyboardDelegate,
editable,
editorInfo,
new FlutterJNI());
}

private ExtractedText getExtractedText(ExtractedTextRequest request) {
Expand Down Expand Up @@ -262,6 +275,10 @@ 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

// Sanitizes the index to ensure the index is within the range of the
// contents of editable.
private static int clampIndexToEditable(int index, Editable editable) {
Expand Down
Loading