This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
Scribe (Android stylus handwriting text input) #52943
Merged
Merged
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 f5c45e2
Fix the notification 'this app does not support handwriting input'
justinmc 2c70875
Correctly report success/error
justinmc 2da6b2e
Show icon on hover
justinmc 815e6cd
Trying the other gestures, but not getting called...
justinmc da21044
Use isStylusHandwritingAvailable, as recommended in the doc
justinmc cdf7366
TODO for upgraded androidx
justinmc a298ad0
Merge branch 'main' into scribe
justinmc 39249a5
AndroidX has been upgraded, though the PR seems to be getting reverte…
justinmc 14ff786
Overriding preview as well, and checking if we ever get a call (no)
justinmc 8217442
Declare that we support all the handwriting gestures!
justinmc d5e0301
Well that's why that wasn't getting called
justinmc 39a4c8e
Version gate
justinmc 27ca8db
Send selection gesture to framework
justinmc 521fb22
TODO for granularity
justinmc 2a27c55
Merge branch 'main' into scribe
justinmc 5046264
Auto formatting
justinmc f9856a2
Pointer functionality is being moved to a separate PR
justinmc 989eb2a
WIP Generic handwriting gesture call
justinmc 47bd6b7
WIP previewHandwritingGesture
justinmc 948d387
Scribe gestures will be handled in a separate PR
justinmc c2f1425
isStylusHandwritingAvailable method
justinmc 5d385c5
Don't need to call isStylusHandwritingAvailable, that's on the caller
justinmc 0a54134
Remove support for gestures, now shows dialog when gesture performed
justinmc aa95c06
Let's keep it a separate channel for now and check in review
justinmc 0962507
ScribePluginTest
justinmc 885f4bb
Get tests working, and add basic tests for ScribePlugin
justinmc 8ef8f49
License check fix
justinmc 49fa99b
Test for ScribePlugin
justinmc 585ac33
Enforce api levels
justinmc 250adbc
Separate Plugin and Channel tests
justinmc e96d8bc
Only start scribe when api available
justinmc 8056d17
Test for TextInputPlugin change
justinmc 016223e
Add missing api_level guards
justinmc cc69690
Move method channel handlers to their own private methods
justinmc 6f1a6d8
Misc Reid's review comments
justinmc 1ae66f1
Test unsupported api levels in scribeplugintest. Allow it to be creat…
justinmc f019841
Some work on testing api versions in ScribeChannel
justinmc d802eb2
Works without deprecated method
justinmc 3685c9d
Test ScribeChannel when old api level
justinmc 0ceab93
Make view public so it could be updated if the view changes
justinmc bc4dafd
TestInputPlugin unsupported test
justinmc 00621e2
Private but settable view
justinmc 53212fc
Use jsonmethodcodec
justinmc 02c8f43
Test reply value now that we're using jsonmethodcodec
justinmc 6d86717
Fix test missing mocked scribechannel
justinmc 44c22d5
Fix platformviewscontrollertest due to missing scribechannel again.
justinmc 06edcdb
Fix mixed up api version in test
justinmc df68cb8
isFeatureAvailable convenience method
justinmc 37cab6c
Merge branch 'main' into scribe
justinmc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| 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 | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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/catchin 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 withfalse, 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update here: #52943 (comment)