-
Notifications
You must be signed in to change notification settings - Fork 6k
Add Spell Check Support for Android Engine #30858
Changes from all commits
dc93719
cd234ad
f463846
3dfae0a
546692b
5f43ff5
ee73219
624f68b
846b195
b27704c
64f0ac7
0cc3776
33f2f40
eb3c731
1877242
5372aaa
d9adbdc
34809db
10d154d
00c228e
e08171f
5ccc144
87ab60f
f4083d7
ce45427
6972b0b
3eba4de
963d9ae
6ea2ede
32c7702
e9ebd5b
8298436
993b6c8
4179f42
f999488
ade72bc
c20b62c
49583d4
49939aa
0cf00f3
48a4354
510f83a
d8e333b
61908d3
29b095f
ee28058
4f0a074
6c4d437
d73b915
66c24ed
04a2ce1
15d17f5
e6414b2
2882e3b
dfe4689
cdc6b9c
65b8850
036b15b
188c2ad
8198d4e
a2b6c90
2aac372
894f4e9
2a4ea66
c2a6550
6125e74
74d1477
cc8bf5e
5a175ec
1c02784
3e74d61
975673c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,8 @@ | |
| import android.view.autofill.AutofillValue; | ||
| import android.view.inputmethod.EditorInfo; | ||
| import android.view.inputmethod.InputConnection; | ||
| import android.view.textservice.SpellCheckerInfo; | ||
| import android.view.textservice.TextServicesManager; | ||
| import android.widget.FrameLayout; | ||
| import androidx.annotation.NonNull; | ||
| import androidx.annotation.Nullable; | ||
|
|
@@ -58,6 +60,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.SpellCheckPlugin; | ||
| import io.flutter.plugin.editing.TextInputPlugin; | ||
| import io.flutter.plugin.localization.LocalizationPlugin; | ||
| import io.flutter.plugin.mouse.MouseCursorPlugin; | ||
|
|
@@ -126,10 +129,12 @@ public class FlutterView extends FrameLayout | |
| // existing, stateless system channels, e.g., MouseCursorChannel, TextInputChannel, etc. | ||
| @Nullable private MouseCursorPlugin mouseCursorPlugin; | ||
| @Nullable private TextInputPlugin textInputPlugin; | ||
| @Nullable private SpellCheckPlugin spellCheckPlugin; | ||
| @Nullable private LocalizationPlugin localizationPlugin; | ||
| @Nullable private KeyboardManager keyboardManager; | ||
| @Nullable private AndroidTouchProcessor androidTouchProcessor; | ||
| @Nullable private AccessibilityBridge accessibilityBridge; | ||
| @Nullable private TextServicesManager textServicesManager; | ||
|
|
||
| // Provides access to foldable/hinge information | ||
| @Nullable private WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo; | ||
|
|
@@ -1141,6 +1146,17 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { | |
| this, | ||
| this.flutterEngine.getTextInputChannel(), | ||
| this.flutterEngine.getPlatformViewsController()); | ||
|
|
||
| try { | ||
| textServicesManager = | ||
| (TextServicesManager) | ||
| getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); | ||
| spellCheckPlugin = | ||
| new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); | ||
| } catch (Exception e) { | ||
| Log.e(TAG, "TextServicesManager not supported by device, spell check disabled."); | ||
| } | ||
|
|
||
| localizationPlugin = this.flutterEngine.getLocalizationPlugin(); | ||
|
|
||
| keyboardManager = new KeyboardManager(this); | ||
|
|
@@ -1238,6 +1254,9 @@ public void detachFromFlutterEngine() { | |
| textInputPlugin.getInputMethodManager().restartInput(this); | ||
| textInputPlugin.destroy(); | ||
| keyboardManager.destroy(); | ||
| if (spellCheckPlugin != null) { | ||
| spellCheckPlugin.destroy(); | ||
| } | ||
|
|
||
| if (mouseCursorPlugin != null) { | ||
| mouseCursorPlugin.destroy(); | ||
|
|
@@ -1422,10 +1441,34 @@ public void removeFlutterEngineAttachmentListener( | |
| ? SettingsChannel.PlatformBrightness.dark | ||
| : SettingsChannel.PlatformBrightness.light; | ||
|
|
||
| boolean isNativeSpellCheckServiceDefined = false; | ||
|
|
||
| if (textServicesManager != null) { | ||
| if (Build.VERSION.SDK_INT >= 31) { | ||
| List<SpellCheckerInfo> enabledSpellCheckerInfos = | ||
| textServicesManager.getEnabledSpellCheckerInfos(); | ||
| boolean gboardSpellCheckerEnabled = | ||
| enabledSpellCheckerInfos.stream() | ||
| .anyMatch( | ||
| spellCheckerInfo -> | ||
| spellCheckerInfo | ||
| .getPackageName() | ||
| .equals("com.google.android.inputmethod.latin")); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this detecting if the spell checker is for latin-only languages? what part makes this Gboard specific? What about for Hindi, Japanese, etc...? I think it's ok to start with latin only, but we would probably need to file a bug after confirming the behavior for non-latin languages.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I can tell, this package covers all of the languages that the Gboard spell checker supports, which is also the only spell checker I've actually tested. This check was an accident on my part -- I confused the spell checker(s) that Android supports with this spell checker. I can remove this check. Alternatively, I can leave it and file a bug, as you suggested, to verify the behavior of other spell checkers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bug filed works |
||
|
|
||
| // Checks if enabled spell checker is the one that is suppported by Gboard, which is | ||
| // the one Flutter supports by default. | ||
| isNativeSpellCheckServiceDefined = | ||
| textServicesManager.isSpellCheckerEnabled() && gboardSpellCheckerEnabled; | ||
| } else { | ||
| isNativeSpellCheckServiceDefined = true; | ||
| } | ||
| } | ||
|
|
||
| flutterEngine | ||
| .getSettingsChannel() | ||
| .startMessage() | ||
| .setTextScaleFactor(getResources().getConfiguration().fontScale) | ||
| .setNativeSpellCheckServiceDefined(isNativeSpellCheckServiceDefined) | ||
| .setBrieflyShowPassword( | ||
| Settings.System.getInt( | ||
| getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| // 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 androidx.annotation.NonNull; | ||
| import androidx.annotation.Nullable; | ||
| 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; | ||
| import org.json.JSONArray; | ||
| import org.json.JSONException; | ||
|
|
||
| /** | ||
| * {@link SpellCheckChannel} is a platform channel that is used by the framework to initiate spell | ||
| * check in the embedding and for the embedding to send back the results. | ||
| * | ||
| * <p>When there is new text to be spell checked, the framework will send to the embedding the | ||
| * message {@code SpellCheck.initiateSpellCheck} with the {@code String} locale to spell check with | ||
| * and the {@code String} of text to spell check as arguments. In response, the {@link | ||
| * io.flutter.plugin.editing.SpellCheckPlugin} will make a call to Android's spell check service to | ||
| * fetch spell check results for the specified text. | ||
| * | ||
| * <p>Once the spell check results are received by the {@link | ||
| * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to the framework the message {@code | ||
| * SpellCheck.updateSpellCheckResults} with the {@code ArrayList<String>} of encoded spell check | ||
| * results (see {@link | ||
| * io.flutter.plugin.editing.SpellCheckPlugin#onGetSentenceSuggestions(SentenceSuggestionsInfo[])} | ||
| * for details) with the text that these results correspond to appeneded to the front as an | ||
| * argument. For example, the argument may look like: {@code {"Hello, wrold!", | ||
| * "7.11.world\nword\nold"}}. | ||
| * | ||
| * <p>{@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link SpellCheckMethodHandler} | ||
| * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to spell check | ||
| * requests. | ||
| */ | ||
| public class SpellCheckChannel { | ||
camsim99 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| private static final String TAG = "SpellCheckChannel"; | ||
|
|
||
| public final MethodChannel channel; | ||
| private SpellCheckMethodHandler spellCheckMethodHandler; | ||
|
|
||
| @NonNull | ||
| public final MethodChannel.MethodCallHandler parsingMethodHandler = | ||
| new MethodChannel.MethodCallHandler() { | ||
| @Override | ||
| public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { | ||
| if (spellCheckMethodHandler == null) { | ||
| Log.v( | ||
| TAG, | ||
| "No SpellCheckeMethodHandler registered, call not forwarded to spell check API."); | ||
| return; | ||
| } | ||
| String method = call.method; | ||
| Object args = call.arguments; | ||
| Log.v(TAG, "Received '" + method + "' message."); | ||
| switch (method) { | ||
| case "SpellCheck.initiateSpellCheck": | ||
| try { | ||
| final JSONArray argumentList = (JSONArray) args; | ||
| String locale = argumentList.getString(0); | ||
| String text = argumentList.getString(1); | ||
| spellCheckMethodHandler.initiateSpellCheck(locale, text, result); | ||
| } catch (JSONException exception) { | ||
| result.error("error", exception.getMessage(), null); | ||
| } | ||
| break; | ||
| default: | ||
| result.notImplemented(); | ||
| break; | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { | ||
| channel = new MethodChannel(dartExecutor, "flutter/spellcheck", JSONMethodCodec.INSTANCE); | ||
| channel.setMethodCallHandler(parsingMethodHandler); | ||
| } | ||
|
|
||
| /** | ||
| * Sets the {@link SpellCheckMethodHandler} which receives all requests to spell check the | ||
| * specified text sent through this channel. | ||
| */ | ||
| public void setSpellCheckMethodHandler( | ||
| @Nullable SpellCheckMethodHandler spellCheckMethodHandler) { | ||
| this.spellCheckMethodHandler = spellCheckMethodHandler; | ||
| } | ||
|
|
||
| public interface SpellCheckMethodHandler { | ||
| /** | ||
| * Requests that spell check is initiated for the specified text, which will respond to the | ||
| * {@code result} with either success if spell check results are received or error if the | ||
| * request is skipped. | ||
| */ | ||
| void initiateSpellCheck( | ||
| @NonNull String locale, @NonNull String text, @NonNull MethodChannel.Result result); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.