From dc93719bd5b9ae0b3cadf2994b348f7c001e418d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 10 Jan 2022 13:45:38 -0800 Subject: [PATCH 01/62] Add session management --- .../plugin/editing/TextInputPlugin.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 7ca0febb19c39..0cc01c9e7e298 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -22,6 +22,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.textservice.SpellCheckerSession; +import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -40,6 +42,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch @NonNull private final View mView; @NonNull private final InputMethodManager mImm; @NonNull private final AutofillManager afm; + @NonNull private final TextServicesManager tsm; @NonNull private final TextInputChannel textInputChannel; @NonNull private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); @Nullable private TextInputChannel.Configuration configuration; @@ -50,6 +53,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch @NonNull private PlatformViewsController platformViewsController; @Nullable private Rect lastClientRect; private ImeSyncDeferringInsetsCallback imeSyncCallback; + private SpellCheckerSession mSpellCheckerSession; // Initialize the "last seen" text editing values to a non-null value. private TextEditState mLastKnownFrameworkTextEditingState; @@ -165,6 +169,10 @@ public void sendAppPrivateCommand(String action, Bundle data) { this.platformViewsController = platformViewsController; this.platformViewsController.attachTextInputPlugin(this); + + // Retrieve manager for text services + tsm = (TextServicesManager) view.getContext(). + getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); } @NonNull @@ -224,6 +232,9 @@ public void destroy() { if (imeSyncCallback != null) { imeSyncCallback.remove(); } + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + } } private static int inputTypeFromTextInputType( @@ -427,6 +438,16 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration unlockPlatformViewInputConnection(); lastClientRect = null; mEditable.addEditingStateListener(this); + + // Open a new spell checker session when a new text input client is set. + // Closes spell checker session if one previously in use. + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + mSpellCheckerSession = textServicesManager.newSpellCheckerSession(...) + } + else { + mSpellCheckerSession = textServicesManager.newSpellCheckerSession(...); + } } private void setPlatformViewTextInputClient(int platformViewId, boolean usesVirtualDisplay) { @@ -563,6 +584,9 @@ void clearTextInputClient() { inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); unlockPlatformViewInputConnection(); lastClientRect = null; + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + } } private static class InputTarget { From cd234add6622f10175513f19fb219db0c5bcabf8 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 10 Jan 2022 15:40:40 -0800 Subject: [PATCH 02/62] Handle receiving results --- .../plugin/editing/TextInputPlugin.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 0cc01c9e7e298..93f741aeeb2b6 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -23,6 +23,8 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textservice.SpellCheckerSession; +import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; +import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -855,4 +857,50 @@ public void autofill(SparseArray values) { textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues); } // -------- End: Autofill ------- + + // -------- Start: Spell Check ------- + // Responsible for calling the Android spell checker API to retrieve spell + // checking results. + public void performSpellCheck() { + // Define TextInfo[] object (textInfos) based on the current input to be + // spell checked. + TextInfo[] textInfos = new String[]{TextInfo(mEditable.toString())}; + + // Make API call. Maximum suggestions requested set to 3 for now. + mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); +} + + // Responsible for decomposing spell checker results into an object that can + // then be sent to the framework. + @Override + public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { + ArrayList> spellCheckerSuggestionSpans = + new ArrayList>(); + + for (int i = 0; i < results[0].getSuggestionsCount(); i++) { + SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + + if (suggestionsCount > 0) { + HashMap spellCheckerSuggestionSpan = new HashMap<>(); + int start = results[0].getOffsetAt(i); + int length = results[0].getLengthAt(i); + + spellCheckerSuggestionSpan.put("start", String.valueOf(start)); + spellCheckerSuggestionSpan.put("end", String.valueOf(start + (length - 1))); + + for (int j = 0; j < suggestionsCount; j++) { + String key = "suggestion_" + String.valueOf(j); + spellCheckerSuggestionSpan.put(key, suggestionsInfo.getSuggestionAt(j)); + } + + spellCheckerSuggestionSpans.put(spellCheckerSuggestionSpan); + } + } + + // Make call to update the spell checker results in the framework. + updateSpellCheckerResults(spellCheckerSuggestionSpans); + } + +// -------- End: Spell Check ------- } From f463846b386a31b44c76704823eab738324b4447 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Tue, 11 Jan 2022 16:14:05 -0800 Subject: [PATCH 03/62] Add framework logic, modify engine --- .../systemchannels/TextInputChannel.java | 18 ++++++++ .../plugin/editing/TextInputPlugin.java | 42 ++++++++++--------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index ecdf86c71820e..2656b3108fa24 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -147,6 +147,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result textInputMethodHandler.finishAutofillContext((boolean) args); result.success(null); break; + case "TextInput.initiateSpellChecking": + final String text = (String) args; + textInputMethodHandler.initiateSpellChecking(text); + result.success(null); + break; default: result.notImplemented(); break; @@ -357,6 +362,13 @@ public void performPrivateCommand(int inputClientId, String action, Bundle data) "TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json)); } + /** Responsible for sending spell checker results across to the framework. */ + public void updateSpellCheckerResults(ArrayList> spellCheckerResults) { + channel.invokeMethod( + "TextInputClient.updateSpellCheckerResults", + Arrays.asList(inputClientId, spellCheckerResults)); + } + /** * Sets the {@link TextInputMethodHandler} which receives all events and requests that are parsed * from the underlying platform channel. @@ -433,6 +445,12 @@ public interface TextInputMethodHandler { * @param data Any data to include with the command. */ void sendAppPrivateCommand(String action, Bundle data); + + /** + * Requests that spell checking is initiated for the inputted text recognized by the framework, + * which will automatically result in spell checking resutls being sent back to the framework. + */ + void initiateSpellChecking(String text); } /** A text editing configuration. */ diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 93f741aeeb2b6..70829cf546ba1 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -23,7 +23,6 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textservice.SpellCheckerSession; -import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; @@ -165,6 +164,11 @@ public void clearClient() { public void sendAppPrivateCommand(String action, Bundle data) { sendTextInputAppPrivateCommand(action, data); } + + @Override + public void initiateSpellChecking(String text) { + performSpellCheck(); + } }); textInputChannel.requestExistingInputState(); @@ -173,8 +177,9 @@ public void sendAppPrivateCommand(String action, Bundle data) { this.platformViewsController.attachTextInputPlugin(this); // Retrieve manager for text services - tsm = (TextServicesManager) view.getContext(). - getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + tsm = + (TextServicesManager) + view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); } @NonNull @@ -236,7 +241,7 @@ public void destroy() { } if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); - } + } } private static int inputTypeFromTextInputType( @@ -440,16 +445,6 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration unlockPlatformViewInputConnection(); lastClientRect = null; mEditable.addEditingStateListener(this); - - // Open a new spell checker session when a new text input client is set. - // Closes spell checker session if one previously in use. - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - mSpellCheckerSession = textServicesManager.newSpellCheckerSession(...) - } - else { - mSpellCheckerSession = textServicesManager.newSpellCheckerSession(...); - } } private void setPlatformViewTextInputClient(int platformViewId, boolean usesVirtualDisplay) { @@ -643,6 +638,16 @@ public void didChangeEditingState( if (textChanged) { // Notify the autofill manager of the value change. notifyValueChanged(mEditable.toString()); + + // Open a new spell checker session when a new text input client is set. + // Closes spell checker session if one previously in use. + // TODO(camillesimon): Get locale from framework to initialize this session. + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + mSpellCheckerSession = tsm.newSpellCheckerSession(null, null, this, true); + } else { + mSpellCheckerSession = tsm.newSpellCheckerSession(null, null, this, true); + } } final int selectionStart = mEditable.getSelectionStart(); @@ -864,18 +869,18 @@ public void autofill(SparseArray values) { public void performSpellCheck() { // Define TextInfo[] object (textInfos) based on the current input to be // spell checked. - TextInfo[] textInfos = new String[]{TextInfo(mEditable.toString())}; + TextInfo[] textInfos = new String[] {TextInfo(mEditable.toString())}; // Make API call. Maximum suggestions requested set to 3 for now. mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); -} + } // Responsible for decomposing spell checker results into an object that can // then be sent to the framework. @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { ArrayList> spellCheckerSuggestionSpans = - new ArrayList>(); + new ArrayList>(); for (int i = 0; i < results[0].getSuggestionsCount(); i++) { SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); @@ -901,6 +906,5 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { // Make call to update the spell checker results in the framework. updateSpellCheckerResults(spellCheckerSuggestionSpans); } - -// -------- End: Spell Check ------- + // -------- End: Spell Check ------- } From 3dfae0a0179fdaae5f8bfecd759974b9d96c3d23 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 12 Jan 2022 13:11:27 -0800 Subject: [PATCH 04/62] Make engine build --- .../engine/systemchannels/TextInputChannel.java | 3 ++- .../flutter/plugin/editing/TextInputPlugin.java | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 2656b3108fa24..4a0f459412f70 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -363,7 +363,8 @@ public void performPrivateCommand(int inputClientId, String action, Bundle data) } /** Responsible for sending spell checker results across to the framework. */ - public void updateSpellCheckerResults(ArrayList> spellCheckerResults) { + public void updateSpellCheckerResults( + int inputClientId, ArrayList> spellCheckerResults) { channel.invokeMethod( "TextInputClient.updateSpellCheckerResults", Arrays.asList(inputClientId, spellCheckerResults)); diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 70829cf546ba1..ab659d27c42f3 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -22,8 +22,10 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -37,7 +39,9 @@ import java.util.HashMap; /** Android implementation of the text input plugin. */ -public class TextInputPlugin implements ListenableEditingState.EditingStateWatcher { +public class TextInputPlugin + implements ListenableEditingState.EditingStateWatcher, + SpellCheckerSession.SpellCheckerSessionListener { private static final String TAG = "TextInputPlugin"; @NonNull private final View mView; @@ -869,7 +873,7 @@ public void autofill(SparseArray values) { public void performSpellCheck() { // Define TextInfo[] object (textInfos) based on the current input to be // spell checked. - TextInfo[] textInfos = new String[] {TextInfo(mEditable.toString())}; + TextInfo[] textInfos = new TextInfo[] {new TextInfo(mEditable.toString())}; // Make API call. Maximum suggestions requested set to 3 for now. mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); @@ -899,12 +903,17 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan.put(key, suggestionsInfo.getSuggestionAt(j)); } - spellCheckerSuggestionSpans.put(spellCheckerSuggestionSpan); + spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan); } } // Make call to update the spell checker results in the framework. - updateSpellCheckerResults(spellCheckerSuggestionSpans); + textInputChannel.updateSpellCheckerResults(inputTarget.id, spellCheckerSuggestionSpans); + } + + @Override + public void onGetSuggestions(SuggestionsInfo[] results) { + // Callback for a deprecated method, so will not use. } // -------- End: Spell Check ------- } From 546692badbad97c63ad8e76c2e5e33080047264b Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 13 Jan 2022 14:18:25 -0800 Subject: [PATCH 05/62] Add retrieving locale --- .../systemchannels/TextInputChannel.java | 14 +++++-- .../plugin/editing/TextInputPlugin.java | 39 ++++++++++++------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 4a0f459412f70..5937084c1d840 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -148,9 +148,15 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result.success(null); break; case "TextInput.initiateSpellChecking": - final String text = (String) args; - textInputMethodHandler.initiateSpellChecking(text); - result.success(null); + try { + final JSONArray argumentList = (JSONArray) args; + String locale = argumentList.getString(0); + String text = argumentList.getString(1); + textInputMethodHandler.initiateSpellChecking(locale, text); + result.success(null); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } break; default: result.notImplemented(); @@ -451,7 +457,7 @@ public interface TextInputMethodHandler { * Requests that spell checking is initiated for the inputted text recognized by the framework, * which will automatically result in spell checking resutls being sent back to the framework. */ - void initiateSpellChecking(String text); + void initiateSpellChecking(String locale, String text); } /** A text editing configuration. */ diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index ab659d27c42f3..ed0003919914d 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -37,6 +37,7 @@ import io.flutter.plugin.platform.PlatformViewsController; import java.util.ArrayList; import java.util.HashMap; +import java.util.Locale; /** Android implementation of the text input plugin. */ public class TextInputPlugin @@ -170,8 +171,8 @@ public void sendAppPrivateCommand(String action, Bundle data) { } @Override - public void initiateSpellChecking(String text) { - performSpellCheck(); + public void initiateSpellChecking(String locale, String text) { + performSpellCheck(locale, text); } }); @@ -642,16 +643,6 @@ public void didChangeEditingState( if (textChanged) { // Notify the autofill manager of the value change. notifyValueChanged(mEditable.toString()); - - // Open a new spell checker session when a new text input client is set. - // Closes spell checker session if one previously in use. - // TODO(camillesimon): Get locale from framework to initialize this session. - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - mSpellCheckerSession = tsm.newSpellCheckerSession(null, null, this, true); - } else { - mSpellCheckerSession = tsm.newSpellCheckerSession(null, null, this, true); - } } final int selectionStart = mEditable.getSelectionStart(); @@ -870,10 +861,30 @@ public void autofill(SparseArray values) { // -------- Start: Spell Check ------- // Responsible for calling the Android spell checker API to retrieve spell // checking results. - public void performSpellCheck() { + public void performSpellCheck(String locale, String text) { + String[] localeCodes = locale.split("-"); + Locale localeToUse; + + if (localeCodes.length == 3) { + localeToUse = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); + } else if (localeCodes.length == 2) { + localeToUse = new Locale(localeCodes[0], localeCodes[1]); + } else { + localeToUse = new Locale(localeCodes[0]); + } + + // Open a new spell checker session when a new text input client is set. + // Closes spell checker session if one previously in use. + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + mSpellCheckerSession = tsm.newSpellCheckerSession(null, localeToUse, this, true); + } else { + mSpellCheckerSession = tsm.newSpellCheckerSession(null, localeToUse, this, true); + } + // Define TextInfo[] object (textInfos) based on the current input to be // spell checked. - TextInfo[] textInfos = new TextInfo[] {new TextInfo(mEditable.toString())}; + TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; // Make API call. Maximum suggestions requested set to 3 for now. mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); From 5f43ff5420be70d7ee490a6762d452e16ff88622 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Tue, 18 Jan 2022 11:39:52 -0800 Subject: [PATCH 06/62] Added comments for experimentation --- .../plugin/editing/TextInputPlugin.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index ed0003919914d..09de844c6d9f3 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -23,6 +23,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textservice.SentenceSuggestionsInfo; +import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; @@ -185,6 +186,7 @@ public void initiateSpellChecking(String locale, String text) { tsm = (TextServicesManager) view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + Log.e("CAMILLE_TSM", tsm.toString()); } @NonNull @@ -862,6 +864,7 @@ public void autofill(SparseArray values) { // Responsible for calling the Android spell checker API to retrieve spell // checking results. public void performSpellCheck(String locale, String text) { + Log.e("CAMILLE_TEXT", text); String[] localeCodes = locale.split("-"); Locale localeToUse; @@ -877,9 +880,19 @@ public void performSpellCheck(String locale, String text) { // Closes spell checker session if one previously in use. if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); - mSpellCheckerSession = tsm.newSpellCheckerSession(null, localeToUse, this, true); + mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); + Log.e("CAMILLE_OLD", mSpellCheckerSession.toString()); } else { - mSpellCheckerSession = tsm.newSpellCheckerSession(null, localeToUse, this, true); + mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); + Log.e("CAMILLE_NEW", mSpellCheckerSession.toString()); + } + SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); + Log.e("CAMILLE_SC", String.valueOf(infoChecker.describeContents())); + Log.e("CAMILLE_LIST", tsm.getEnabledSpellCheckerInfos().toString()); + Log.e("CAMILLE_ONE", tsm.getCurrentSpellCheckerInfo().toString()); + Log.e("CAMILLE_ISC", Boolean.toString(tsm.isSpellCheckerEnabled())); + if (mSpellCheckerSession.isSessionDisconnected()) { + Log.e("CAMILLE_SESSION", "is disconnected"); } // Define TextInfo[] object (textInfos) based on the current input to be @@ -894,12 +907,15 @@ public void performSpellCheck(String locale, String text) { // then be sent to the framework. @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { + Log.e("CAMILLE_RES_LEN", String.valueOf(results.length)); ArrayList> spellCheckerSuggestionSpans = new ArrayList>(); for (int i = 0; i < results[0].getSuggestionsCount(); i++) { SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + Log.e("CAMILLE_INFO", suggestionsInfo.toString()); + Log.e("CAMILLE_SUG_LEN", String.valueOf(suggestionsCount)); if (suggestionsCount > 0) { HashMap spellCheckerSuggestionSpan = new HashMap<>(); @@ -911,6 +927,7 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { for (int j = 0; j < suggestionsCount; j++) { String key = "suggestion_" + String.valueOf(j); + Log.e("CAMILLE_SUGGESTION", suggestionsInfo.getSuggestionAt(j)); spellCheckerSuggestionSpan.put(key, suggestionsInfo.getSuggestionAt(j)); } From ee732198862f266330a4f57da7acf015756bb56c Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 2 Feb 2022 11:22:55 -0800 Subject: [PATCH 07/62] Send string back temporarily, fix results representation --- .../systemchannels/TextInputChannel.java | 4 +-- .../plugin/editing/TextInputPlugin.java | 34 ++++++------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 5937084c1d840..e26e991963ed2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -370,10 +370,10 @@ public void performPrivateCommand(int inputClientId, String action, Bundle data) /** Responsible for sending spell checker results across to the framework. */ public void updateSpellCheckerResults( - int inputClientId, ArrayList> spellCheckerResults) { + int inputClientId, ArrayList spellCheckerResults, String spellCheckedText) { channel.invokeMethod( "TextInputClient.updateSpellCheckerResults", - Arrays.asList(inputClientId, spellCheckerResults)); + Arrays.asList(inputClientId, spellCheckerResults, spellCheckedText)); } /** diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 09de844c6d9f3..3788c0b23360c 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -62,6 +62,9 @@ public class TextInputPlugin private ImeSyncDeferringInsetsCallback imeSyncCallback; private SpellCheckerSession mSpellCheckerSession; + // String of spell checked text for testing + private String currentSpellCheckedText; + // Initialize the "last seen" text editing values to a non-null value. private TextEditState mLastKnownFrameworkTextEditingState; @@ -173,6 +176,7 @@ public void sendAppPrivateCommand(String action, Bundle data) { @Override public void initiateSpellChecking(String locale, String text) { + currentSpellCheckedText = text; performSpellCheck(locale, text); } }); @@ -186,7 +190,6 @@ public void initiateSpellChecking(String locale, String text) { tsm = (TextServicesManager) view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - Log.e("CAMILLE_TSM", tsm.toString()); } @NonNull @@ -864,7 +867,6 @@ public void autofill(SparseArray values) { // Responsible for calling the Android spell checker API to retrieve spell // checking results. public void performSpellCheck(String locale, String text) { - Log.e("CAMILLE_TEXT", text); String[] localeCodes = locale.split("-"); Locale localeToUse; @@ -881,19 +883,10 @@ public void performSpellCheck(String locale, String text) { if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); - Log.e("CAMILLE_OLD", mSpellCheckerSession.toString()); } else { mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); - Log.e("CAMILLE_NEW", mSpellCheckerSession.toString()); } SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); - Log.e("CAMILLE_SC", String.valueOf(infoChecker.describeContents())); - Log.e("CAMILLE_LIST", tsm.getEnabledSpellCheckerInfos().toString()); - Log.e("CAMILLE_ONE", tsm.getCurrentSpellCheckerInfo().toString()); - Log.e("CAMILLE_ISC", Boolean.toString(tsm.isSpellCheckerEnabled())); - if (mSpellCheckerSession.isSessionDisconnected()) { - Log.e("CAMILLE_SESSION", "is disconnected"); - } // Define TextInfo[] object (textInfos) based on the current input to be // spell checked. @@ -907,36 +900,31 @@ public void performSpellCheck(String locale, String text) { // then be sent to the framework. @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - Log.e("CAMILLE_RES_LEN", String.valueOf(results.length)); - ArrayList> spellCheckerSuggestionSpans = - new ArrayList>(); + ArrayList spellCheckerSuggestionSpans = new ArrayList(); for (int i = 0; i < results[0].getSuggestionsCount(); i++) { SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - Log.e("CAMILLE_INFO", suggestionsInfo.toString()); - Log.e("CAMILLE_SUG_LEN", String.valueOf(suggestionsCount)); if (suggestionsCount > 0) { - HashMap spellCheckerSuggestionSpan = new HashMap<>(); + String spellCheckerSuggestionSpan = ""; int start = results[0].getOffsetAt(i); int length = results[0].getLengthAt(i); - spellCheckerSuggestionSpan.put("start", String.valueOf(start)); - spellCheckerSuggestionSpan.put("end", String.valueOf(start + (length - 1))); + spellCheckerSuggestionSpan += (String.valueOf(start) + "."); + spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); for (int j = 0; j < suggestionsCount; j++) { String key = "suggestion_" + String.valueOf(j); - Log.e("CAMILLE_SUGGESTION", suggestionsInfo.getSuggestionAt(j)); - spellCheckerSuggestionSpan.put(key, suggestionsInfo.getSuggestionAt(j)); + spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); } - spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan); + spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } } // Make call to update the spell checker results in the framework. - textInputChannel.updateSpellCheckerResults(inputTarget.id, spellCheckerSuggestionSpans); + textInputChannel.updateSpellCheckerResults(inputTarget.id, spellCheckerSuggestionSpans, currentSpellCheckedText); } @Override From 624f68b80e4b2d4adb48e5e31d5d093c01ada885 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 2 Feb 2022 11:23:49 -0800 Subject: [PATCH 08/62] Formatting --- .../android/io/flutter/plugin/editing/TextInputPlugin.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 3788c0b23360c..bcc07f1209ab1 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -919,12 +919,14 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); } - spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } } // Make call to update the spell checker results in the framework. - textInputChannel.updateSpellCheckerResults(inputTarget.id, spellCheckerSuggestionSpans, currentSpellCheckedText); + textInputChannel.updateSpellCheckerResults( + inputTarget.id, spellCheckerSuggestionSpans, currentSpellCheckedText); } @Override From 64f0ac7e36cb7aaa37251d71b74a2502a3bc8c0a Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 18 Mar 2022 11:56:42 -0700 Subject: [PATCH 09/62] Add new method channel --- shell/platform/android/BUILD.gn | 2 + .../embedding/android/FlutterView.java | 3 + .../embedding/engine/FlutterEngine.java | 9 ++ .../systemchannels/SpellCheckChannel.java | 84 ++++++++++++ .../systemchannels/TextInputChannel.java | 23 +++- .../plugin/editing/SpellCheckPlugin.java | 121 ++++++++++++++++++ .../plugin/platform/PlatformPlugin.java | 5 +- 7 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java create mode 100644 shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 100323a8dc3a0..60e00eec775ae 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -236,6 +236,7 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java", "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", + "io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java", "io/flutter/embedding/engine/systemchannels/SystemChannel.java", "io/flutter/embedding/engine/systemchannels/TextInputChannel.java", "io/flutter/plugin/common/ActivityLifecycleListener.java", @@ -260,6 +261,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/SpellCheckPlugin.java", "io/flutter/plugin/editing/TextEditingDelta.java", "io/flutter/plugin/editing/TextInputPlugin.java", "io/flutter/plugin/localization/LocalizationPlugin.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 19b4986c7efe9..7e540fcb5e304 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -57,6 +57,7 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.plugin.editing.SpellCheckPlugin; import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.mouse.MouseCursorPlugin; @@ -124,6 +125,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC // existing, stateless system channels, e.g., KeyEventChannel, 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; @@ -1120,6 +1122,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); + spellCheckPlugin = new SpellCheckPlugin(this, this.flutterEngine.getSpellCheckChannel()); localizationPlugin = this.flutterEngine.getLocalizationPlugin(); keyboardManager = diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 144cf52086b61..3cab618872ba8 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -33,6 +33,7 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; @@ -95,6 +96,7 @@ public class FlutterEngine { @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; @NonNull private final SettingsChannel settingsChannel; + @NonNull private final SpellCheckChannel spellCheckChannel; @NonNull private final SystemChannel systemChannel; @NonNull private final TextInputChannel textInputChannel; @@ -309,6 +311,7 @@ public FlutterEngine( platformChannel = new PlatformChannel(dartExecutor); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); + spellCheckChannel = new SpellCheckChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); @@ -559,6 +562,12 @@ public TextInputChannel getTextInputChannel() { return textInputChannel; } + /** System channel that sends and receives requests related to spell check. */ + @NonNull + public SpellCheckChannel getSpellCheckChannel() { + return spellCheckChannel; + } + /** * Plugin registry, which registers plugins that want to be applied to this {@code FlutterEngine}. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java new file mode 100644 index 0000000000000..da24dff0612c7 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -0,0 +1,84 @@ +package io.flutter.embedding.engine.systemchannels; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +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; +import java.util.ArrayList; +import java.util.Arrays; +import org.json.JSONArray; +import org.json.JSONException; + +public class SpellCheckChannel { + private static final String TAG = "SpellCheckChannel"; + + @NonNull public final MethodChannel channel; + @Nullable private SpellCheckMethodHandler spellCheckMethodHandler; + + @NonNull @VisibleForTesting + final MethodChannel.MethodCallHandler parsingMethodHandler = + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (spellCheckMethodHandler == null) { + // If no explicit SpellCheckMethodHandler has been registered then we don't + // need to forward this call to an API. Return. + return; + } + String method = call.method; + Object args = call.arguments; + Log.v(TAG, "Received '" + method + "' message."); + switch (method) { + case "SpellCheck.initiateSpellChecking": + System.out.println("-----------FETCH RECEIVED IN ENGINE------------------"); + try { + final JSONArray argumentList = (JSONArray) args; + String locale = argumentList.getString(0); + String text = argumentList.getString(1); + spellCheckMethodHandler.initiateSpellChecking(locale, text); + result.success(null); + } catch (JSONException exception) { + result.error("error", exception.getMessage(), null); + } + break; + default: + System.out.println( + "-----------WRONG METHOD BEING CALLED IN ENGINE------------------"); + result.notImplemented(); + break; + } + } + }; + + public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { + // TODO(camillesimon): Use JSON? + this.channel = new MethodChannel(dartExecutor, "flutter/spellcheck", JSONMethodCodec.INSTANCE); + channel.setMethodCallHandler(parsingMethodHandler); + } + + /** Responsible for sending spell checker results across to the framework. */ + public void updateSpellCheckerResults( + ArrayList spellCheckerResults, String spellCheckedText) { + channel.invokeMethod( + "TextInputClient.updateSpellCheckerResults", + Arrays.asList(spellCheckerResults, spellCheckedText)); + } + + public void setSpellCheckMethodHandler( + @Nullable SpellCheckMethodHandler spellCheckMethodHandler) { + System.out.println("-------------------SPELL CHECK HANDLER SET-------------------"); + this.spellCheckMethodHandler = spellCheckMethodHandler; + } + + public interface SpellCheckMethodHandler { + /** + * Requests that spell checking is initiated for the inputted text recognized by the framework, + * which will automatically result in spell checking resutls being sent back to the framework. + */ + void initiateSpellChecking(String locale, String text); + } +} diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index ec7fb2a066291..29c7e4e554d05 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -13,6 +13,8 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.editing.TextEditingDelta; +//TODO(camillesimon): Remove, using for testing: +import io.flutter.plugin.editing.SpellCheckPlugin; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -44,6 +46,9 @@ public class TextInputChannel { @NonNull public final MethodChannel channel; @Nullable private TextInputMethodHandler textInputMethodHandler; + //TODO(camillesimon): Remove, using for testing: + @Nullable private SpellCheckMethodHandler spellCheckMethodHandler; + @NonNull @VisibleForTesting final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @@ -150,7 +155,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result final JSONArray argumentList = (JSONArray) args; String locale = argumentList.getString(0); String text = argumentList.getString(1); - textInputMethodHandler.initiateSpellChecking(locale, text); + //TODO(camillesimon): Remove, use for testing + spellCheckMethodHandler.initiateSpellChecking(locale, text); result.success(null); } catch (JSONException exception) { result.error("error", exception.getMessage(), null); @@ -372,7 +378,7 @@ public void updateSpellCheckerResults( int inputClientId, ArrayList spellCheckerResults, String spellCheckedText) { channel.invokeMethod( "TextInputClient.updateSpellCheckerResults", - Arrays.asList(inputClientId, spellCheckerResults, spellCheckedText)); + Arrays.asList(0, spellCheckerResults, spellCheckedText)); } /** @@ -383,6 +389,19 @@ public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInput this.textInputMethodHandler = textInputMethodHandler; } + //TODO(camillesimon): Remove, using for testing + public void setSpellCheckMethodHandler(@Nullable SpellCheckMethodHandler spellCheckMethodHandler) { + this.spellCheckMethodHandler = spellCheckMethodHandler; + } + + public interface SpellCheckMethodHandler { + /** + * Requests that spell checking is initiated for the inputted text recognized by the framework, + * which will automatically result in spell checking resutls being sent back to the framework. + */ + void initiateSpellChecking(String locale, String text); + } + public interface TextInputMethodHandler { // TODO(mattcarroll): javadoc void show(); diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java new file mode 100644 index 0000000000000..2716e688840a8 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -0,0 +1,121 @@ +// 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.plugin.editing; + +import android.content.Context; +import android.view.View; +import android.view.textservice.SentenceSuggestionsInfo; +import android.view.textservice.SpellCheckerInfo; +import android.view.textservice.SpellCheckerSession; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; +import android.view.textservice.TextServicesManager; +import androidx.annotation.NonNull; +import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; +import java.util.ArrayList; +import java.util.Locale; + +public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSessionListener { + + @NonNull private final View mView; + @NonNull private final SpellCheckChannel mSpellCheckChannel; + @NonNull private final TextServicesManager tsm; + private SpellCheckerSession mSpellCheckerSession; + + // String of spell checked text for testing + private String currentSpellCheckedText; + + public SpellCheckPlugin(@NonNull View view, @NonNull SpellCheckChannel spellCheckChannel) { + System.out.println("----------------SpellCheckPlugin INITIATED-------------"); + mView = view; + mSpellCheckChannel = spellCheckChannel; + tsm = + (TextServicesManager) + view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + + mSpellCheckChannel.setSpellCheckMethodHandler( + new SpellCheckChannel.SpellCheckMethodHandler() { + @Override + public void initiateSpellChecking(String locale, String text) { + currentSpellCheckedText = text; + performSpellCheck(locale, text); + } + }); + } + + // Responsible for calling the Android spell checker API to retrieve spell + // checking results. + public void performSpellCheck(String locale, String text) { + String[] localeCodes = locale.split("-"); + Locale localeToUse; + + if (localeCodes.length == 3) { + localeToUse = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); + } else if (localeCodes.length == 2) { + localeToUse = new Locale(localeCodes[0], localeCodes[1]); + } else { + localeToUse = new Locale(localeCodes[0]); + } + + // Open a new spell checker session when a new text input client is set. + // Closes spell checker session if one previously in use. + // TODO(camillesimon): Figure out proper session management. + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); + } else { + mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); + } + SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); + + // Define TextInfo[] object (textInfos) based on the current input to be + // spell checked. + TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; + + // Make API call. Maximum suggestions requested set to 3 for now. + System.out.println("----------------SpellCheckPlugin FETCH INITIATED-------------"); + mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); + } + + // Responsible for decomposing spell checker results into an object that can + // then be sent to the framework. + @Override + public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { + ArrayList spellCheckerSuggestionSpans = new ArrayList(); + + for (int i = 0; i < results[0].getSuggestionsCount(); i++) { + SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + + if (suggestionsCount > 0) { + String spellCheckerSuggestionSpan = ""; + int start = results[0].getOffsetAt(i); + int length = results[0].getLengthAt(i); + + spellCheckerSuggestionSpan += (String.valueOf(start) + "."); + spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); + + for (int j = 0; j < suggestionsCount; j++) { + String key = "suggestion_" + String.valueOf(j); + spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); + } + + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + } + } + + // Make call to update the spell checker results in the framework. + // Current text being passed for testing purposes. + // TODO(camillesimon): Don't pass text back to framework. + mSpellCheckChannel.updateSpellCheckerResults( + spellCheckerSuggestionSpans, currentSpellCheckedText); + } + + @Override + public void onGetSuggestions(SuggestionsInfo[] results) { + // Callback for a deprecated method, so will not use. + } +} diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index a1d38ba0720db..ba02e0c7b1ce4 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -370,6 +370,9 @@ private void setSystemChromeSystemUIOverlayStyle( WindowInsetsControllerCompat windowInsetsControllerCompat = new WindowInsetsControllerCompat(window, view); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + // SYSTEM STATUS BAR ------------------------------------------------------------------- // You can't change the color of the system status bar until SDK 21, and you can't change the // color of the status icons until SDK 23. We only allow both starting at 23 to ensure buttons @@ -433,8 +436,6 @@ private void setSystemChromeSystemUIOverlayStyle( } // You can't change the color of the navigation bar divider color until SDK 28. if (systemChromeStyle.systemNavigationBarDividerColor != null && Build.VERSION.SDK_INT >= 28) { - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); window.setNavigationBarDividerColor(systemChromeStyle.systemNavigationBarDividerColor); } From 0cc3776301623a9a4f63180004d5c85abb4a62cc Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 18 Mar 2022 14:00:11 -0700 Subject: [PATCH 10/62] Add platform setting for spell check --- lib/ui/platform_dispatcher.dart | 6 ++++++ lib/ui/window.dart | 2 ++ .../android/io/flutter/embedding/android/FlutterView.java | 2 ++ .../embedding/engine/systemchannels/SettingsChannel.java | 8 ++++++++ 4 files changed, 18 insertions(+) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index b6de42b98fc34..d84f6dcfec991 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -832,6 +832,9 @@ class PlatformDispatcher { _onTextScaleFactorChangedZone = Zone.current; } + bool get defaultSpellCheckEnabled => _defaultSpellCheckEnabled; + bool _defaultSpellCheckEnabled = false; + /// Whether briefly displaying the characters as you type in obscured text /// fields is enabled in system settings. /// @@ -893,6 +896,9 @@ class PlatformDispatcher { final double textScaleFactor = (data['textScaleFactor']! as num).toDouble(); final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat']! as bool; + _defaultSpellCheckEnabled = data['defaultSpellCheckEnabled']! as bool; + print("----------------SETTING defaultSpellCheckEnabled TO:----------------------"); + print(_defaultSpellCheckEnabled); // This field is optional. final bool? brieflyShowPassword = data['brieflyShowPassword'] as bool?; if (brieflyShowPassword != null) { diff --git a/lib/ui/window.dart b/lib/ui/window.dart index f42ec7a3778cb..24681a365a7ba 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -425,6 +425,8 @@ class SingletonFlutterWindow extends FlutterWindow { /// observe when this value changes. double get textScaleFactor => platformDispatcher.textScaleFactor; + bool get defaultSpellCheckEnabled => platformDispatcher.defaultSpellCheckEnabled; + /// Whether briefly displaying the characters as you type in obscured text /// fields is enabled in system settings. /// diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 7e540fcb5e304..a7ba2929781d8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1401,6 +1401,7 @@ public void removeFlutterEngineAttachmentListener( */ @VisibleForTesting /* package */ void sendUserSettingsToFlutter() { + System.out.println("-----------------SENDING USER SETTINGS TO FLUTTER----------------"); // Lookup the current brightness of the Android OS. boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) @@ -1414,6 +1415,7 @@ public void removeFlutterEngineAttachmentListener( .getSettingsChannel() .startMessage() .setTextScaleFactor(getResources().getConfiguration().fontScale) + .setDefaultSpellCheckEnabled(true) .setBrieflyShowPassword( Settings.System.getInt( getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java index 1be798c384cc6..a6482d6b8f284 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java @@ -13,6 +13,7 @@ public class SettingsChannel { public static final String CHANNEL_NAME = "flutter/settings"; private static final String TEXT_SCALE_FACTOR = "textScaleFactor"; + private static final String DEFAULT_SPELL_CHECK_ENABLED = "defaultSpellCheckEnabled"; private static final String BRIEFLY_SHOW_PASSWORD = "brieflyShowPassword"; private static final String ALWAYS_USE_24_HOUR_FORMAT = "alwaysUse24HourFormat"; private static final String PLATFORM_BRIGHTNESS = "platformBrightness"; @@ -42,6 +43,13 @@ public MessageBuilder setTextScaleFactor(float textScaleFactor) { return this; } + @NonNull + public MessageBuilder setDefaultSpellCheckEnabled(@NonNull boolean defaultSpellCheckEnabled) { + System.out.println("--------------DefaultSpellCheckEnabled SET TO TRUE--------------------"); + message.put(DEFAULT_SPELL_CHECK_ENABLED, defaultSpellCheckEnabled); + return this; + } + @NonNull public MessageBuilder setBrieflyShowPassword(@NonNull boolean brieflyShowPassword) { message.put(BRIEFLY_SHOW_PASSWORD, brieflyShowPassword); From 33f2f403412c0720076e65b4a80fd272640b7b49 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 21 Mar 2022 13:57:38 -0700 Subject: [PATCH 11/62] Change method channel name --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index da24dff0612c7..f3ef835f18595 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -64,7 +64,7 @@ public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { public void updateSpellCheckerResults( ArrayList spellCheckerResults, String spellCheckedText) { channel.invokeMethod( - "TextInputClient.updateSpellCheckerResults", + "SpellCheck.updateSpellCheckerResults", Arrays.asList(spellCheckerResults, spellCheckedText)); } From eb3c7311d1fcab03be5bff5df5cb9451ea8494a5 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Tue, 22 Mar 2022 14:33:19 -0700 Subject: [PATCH 12/62] Remove print statements --- lib/ui/platform_dispatcher.dart | 2 -- .../embedding/engine/systemchannels/SettingsChannel.java | 1 - .../embedding/engine/systemchannels/SpellCheckChannel.java | 3 --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 2 -- 4 files changed, 8 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index d84f6dcfec991..f25abe33d4041 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -897,8 +897,6 @@ class PlatformDispatcher { final double textScaleFactor = (data['textScaleFactor']! as num).toDouble(); final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat']! as bool; _defaultSpellCheckEnabled = data['defaultSpellCheckEnabled']! as bool; - print("----------------SETTING defaultSpellCheckEnabled TO:----------------------"); - print(_defaultSpellCheckEnabled); // This field is optional. final bool? brieflyShowPassword = data['brieflyShowPassword'] as bool?; if (brieflyShowPassword != null) { diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java index a6482d6b8f284..562ccea33c320 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java @@ -45,7 +45,6 @@ public MessageBuilder setTextScaleFactor(float textScaleFactor) { @NonNull public MessageBuilder setDefaultSpellCheckEnabled(@NonNull boolean defaultSpellCheckEnabled) { - System.out.println("--------------DefaultSpellCheckEnabled SET TO TRUE--------------------"); message.put(DEFAULT_SPELL_CHECK_ENABLED, defaultSpellCheckEnabled); return this; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index f3ef835f18595..606d75d1792aa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -34,7 +34,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result Log.v(TAG, "Received '" + method + "' message."); switch (method) { case "SpellCheck.initiateSpellChecking": - System.out.println("-----------FETCH RECEIVED IN ENGINE------------------"); try { final JSONArray argumentList = (JSONArray) args; String locale = argumentList.getString(0); @@ -47,7 +46,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result break; default: System.out.println( - "-----------WRONG METHOD BEING CALLED IN ENGINE------------------"); result.notImplemented(); break; } @@ -70,7 +68,6 @@ public void updateSpellCheckerResults( public void setSpellCheckMethodHandler( @Nullable SpellCheckMethodHandler spellCheckMethodHandler) { - System.out.println("-------------------SPELL CHECK HANDLER SET-------------------"); this.spellCheckMethodHandler = spellCheckMethodHandler; } diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 2716e688840a8..9f93551c9f516 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -28,7 +28,6 @@ public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSession private String currentSpellCheckedText; public SpellCheckPlugin(@NonNull View view, @NonNull SpellCheckChannel spellCheckChannel) { - System.out.println("----------------SpellCheckPlugin INITIATED-------------"); mView = view; mSpellCheckChannel = spellCheckChannel; tsm = @@ -75,7 +74,6 @@ public void performSpellCheck(String locale, String text) { TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; // Make API call. Maximum suggestions requested set to 3 for now. - System.out.println("----------------SpellCheckPlugin FETCH INITIATED-------------"); mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); } From 1877242b04dc7afe88a3a0179ae4cbb8ea8c422f Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 23 Mar 2022 13:17:14 -0700 Subject: [PATCH 13/62] Clean up --- .../systemchannels/SpellCheckChannel.java | 1 - .../systemchannels/TextInputChannel.java | 24 +------------------ 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 606d75d1792aa..0e9a109ea7f7c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -45,7 +45,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result } break; default: - System.out.println( result.notImplemented(); break; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 29c7e4e554d05..ea1fa5199650e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -13,8 +13,6 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.editing.TextEditingDelta; -//TODO(camillesimon): Remove, using for testing: -import io.flutter.plugin.editing.SpellCheckPlugin; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -46,9 +44,6 @@ public class TextInputChannel { @NonNull public final MethodChannel channel; @Nullable private TextInputMethodHandler textInputMethodHandler; - //TODO(camillesimon): Remove, using for testing: - @Nullable private SpellCheckMethodHandler spellCheckMethodHandler; - @NonNull @VisibleForTesting final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @@ -150,18 +145,6 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result textInputMethodHandler.finishAutofillContext((boolean) args); result.success(null); break; - case "TextInput.initiateSpellChecking": - try { - final JSONArray argumentList = (JSONArray) args; - String locale = argumentList.getString(0); - String text = argumentList.getString(1); - //TODO(camillesimon): Remove, use for testing - spellCheckMethodHandler.initiateSpellChecking(locale, text); - result.success(null); - } catch (JSONException exception) { - result.error("error", exception.getMessage(), null); - } - break; default: result.notImplemented(); break; @@ -388,12 +371,7 @@ public void updateSpellCheckerResults( public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler) { this.textInputMethodHandler = textInputMethodHandler; } - - //TODO(camillesimon): Remove, using for testing - public void setSpellCheckMethodHandler(@Nullable SpellCheckMethodHandler spellCheckMethodHandler) { - this.spellCheckMethodHandler = spellCheckMethodHandler; - } - + public interface SpellCheckMethodHandler { /** * Requests that spell checking is initiated for the inputted text recognized by the framework, From 5372aaa2b2a96086cc67be0f615e9222d0f0db16 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 24 Mar 2022 13:04:03 -0700 Subject: [PATCH 14/62] Formatting --- .../embedding/engine/systemchannels/TextInputChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index ea1fa5199650e..02da80772c53b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -371,7 +371,7 @@ public void updateSpellCheckerResults( public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler) { this.textInputMethodHandler = textInputMethodHandler; } - + public interface SpellCheckMethodHandler { /** * Requests that spell checking is initiated for the inputted text recognized by the framework, From d9adbdcd180fd28ef6cc74eb7bef6e54f73f3d41 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 28 Mar 2022 16:01:14 -0700 Subject: [PATCH 15/62] Clean PR --- lib/ui/platform_dispatcher.dart | 12 +- lib/ui/window.dart | 10 +- .../embedding/android/FlutterView.java | 6 +- .../embedding/engine/FlutterEngine.java | 2 +- .../systemchannels/SettingsChannel.java | 7 +- .../systemchannels/SpellCheckChannel.java | 27 ++--- .../systemchannels/TextInputChannel.java | 22 ---- .../plugin/editing/SpellCheckPlugin.java | 41 +++---- .../plugin/editing/TextInputPlugin.java | 105 +----------------- .../plugin/platform/PlatformPlugin.java | 4 - 10 files changed, 55 insertions(+), 181 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index f25abe33d4041..2740f745816dc 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -832,8 +832,14 @@ class PlatformDispatcher { _onTextScaleFactorChangedZone = Zone.current; } - bool get defaultSpellCheckEnabled => _defaultSpellCheckEnabled; - bool _defaultSpellCheckEnabled = false; + /// The setting inidicating whether or not Flutter currently supports + /// spell checking via the native spell check service. + /// + /// This option is used by [EditableTextState] to define its + /// [SpellCheckConfiguration] when spell check is enabled, but no spell check + /// service is specified. + bool get nativeSpellCheckServiceDefined => _nativeSpellCheckServiceDefined; + bool _nativeSpellCheckServiceDefined = false; /// Whether briefly displaying the characters as you type in obscured text /// fields is enabled in system settings. @@ -896,7 +902,7 @@ class PlatformDispatcher { final double textScaleFactor = (data['textScaleFactor']! as num).toDouble(); final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat']! as bool; - _defaultSpellCheckEnabled = data['defaultSpellCheckEnabled']! as bool; + _nativeSpellCheckServiceDefined = data['nativeSpellCheckServiceDefined']! as bool; // This field is optional. final bool? brieflyShowPassword = data['brieflyShowPassword'] as bool?; if (brieflyShowPassword != null) { diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 24681a365a7ba..8ee672a491589 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -425,7 +425,15 @@ class SingletonFlutterWindow extends FlutterWindow { /// observe when this value changes. double get textScaleFactor => platformDispatcher.textScaleFactor; - bool get defaultSpellCheckEnabled => platformDispatcher.defaultSpellCheckEnabled; + /// The setting inidicating whether or not Flutter currently supports + /// spell checking via the native spell check service. + /// + /// {@macro dart.ui.window.accessorForwardWarning} + /// + /// This option is used by [EditableTextState] to define its + /// [SpellCheckConfiguration] when spell check is enabled, but no spell check + /// service is specified. + bool get nativeSpellCheckServiceDefined => platformDispatcher.nativeSpellCheckServiceDefined; /// Whether briefly displaying the characters as you type in obscured text /// fields is enabled in system settings. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index a7ba2929781d8..d8ae0dc4397c5 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1122,7 +1122,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); - spellCheckPlugin = new SpellCheckPlugin(this, this.flutterEngine.getSpellCheckChannel()); + spellCheckPlugin = + new SpellCheckPlugin(getContext(), this.flutterEngine.getSpellCheckChannel()); localizationPlugin = this.flutterEngine.getLocalizationPlugin(); keyboardManager = @@ -1401,7 +1402,6 @@ public void removeFlutterEngineAttachmentListener( */ @VisibleForTesting /* package */ void sendUserSettingsToFlutter() { - System.out.println("-----------------SENDING USER SETTINGS TO FLUTTER----------------"); // Lookup the current brightness of the Android OS. boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) @@ -1415,7 +1415,7 @@ public void removeFlutterEngineAttachmentListener( .getSettingsChannel() .startMessage() .setTextScaleFactor(getResources().getConfiguration().fontScale) - .setDefaultSpellCheckEnabled(true) + .setNativeSpellCheckServiceDefined(true) .setBrieflyShowPassword( Settings.System.getInt( getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 3cab618872ba8..48cdfdb24f141 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -562,7 +562,7 @@ public TextInputChannel getTextInputChannel() { return textInputChannel; } - /** System channel that sends and receives requests related to spell check. */ + /** System channel that sends and receives spell check requests and results. */ @NonNull public SpellCheckChannel getSpellCheckChannel() { return spellCheckChannel; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java index 562ccea33c320..f61532c04c33f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java @@ -13,7 +13,7 @@ public class SettingsChannel { public static final String CHANNEL_NAME = "flutter/settings"; private static final String TEXT_SCALE_FACTOR = "textScaleFactor"; - private static final String DEFAULT_SPELL_CHECK_ENABLED = "defaultSpellCheckEnabled"; + private static final String NATIVE_SPELL_CHECK_SERVICE_DEFINED = "nativeSpellCheckServiceDefined"; private static final String BRIEFLY_SHOW_PASSWORD = "brieflyShowPassword"; private static final String ALWAYS_USE_24_HOUR_FORMAT = "alwaysUse24HourFormat"; private static final String PLATFORM_BRIGHTNESS = "platformBrightness"; @@ -44,8 +44,9 @@ public MessageBuilder setTextScaleFactor(float textScaleFactor) { } @NonNull - public MessageBuilder setDefaultSpellCheckEnabled(@NonNull boolean defaultSpellCheckEnabled) { - message.put(DEFAULT_SPELL_CHECK_ENABLED, defaultSpellCheckEnabled); + public MessageBuilder setNativeSpellCheckServiceDefined( + @NonNull boolean nativeSpellCheckServiceDefined) { + message.put(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined); return this; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 0e9a109ea7f7c..302e68d0f1724 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -9,7 +9,6 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import java.util.ArrayList; -import java.util.Arrays; import org.json.JSONArray; import org.json.JSONException; @@ -33,12 +32,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result Object args = call.arguments; Log.v(TAG, "Received '" + method + "' message."); switch (method) { - case "SpellCheck.initiateSpellChecking": + case "SpellCheck.initiateSpellCheck": try { final JSONArray argumentList = (JSONArray) args; String locale = argumentList.getString(0); String text = argumentList.getString(1); - spellCheckMethodHandler.initiateSpellChecking(locale, text); + spellCheckMethodHandler.initiateSpellCheck(locale, text); result.success(null); } catch (JSONException exception) { result.error("error", exception.getMessage(), null); @@ -52,19 +51,19 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result }; public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { - // TODO(camillesimon): Use JSON? this.channel = new MethodChannel(dartExecutor, "flutter/spellcheck", JSONMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); } - /** Responsible for sending spell checker results across to the framework. */ - public void updateSpellCheckerResults( - ArrayList spellCheckerResults, String spellCheckedText) { - channel.invokeMethod( - "SpellCheck.updateSpellCheckerResults", - Arrays.asList(spellCheckerResults, spellCheckedText)); + /** Responsible for sending spell check results through this channel. */ + public void updateSpellCheckResults(ArrayList spellCheckResults) { + channel.invokeMethod("SpellCheck.updateSpellCheckResults", spellCheckResults); } + /** + * 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; @@ -72,9 +71,11 @@ public void setSpellCheckMethodHandler( public interface SpellCheckMethodHandler { /** - * Requests that spell checking is initiated for the inputted text recognized by the framework, - * which will automatically result in spell checking resutls being sent back to the framework. + * Requests that spell check is initiated for the specified text, which will automatically + * result in a call to {@code + * SpellCheckChannel#setSpellCheckMethodHandler(SpellCheckMethodHandler)} once spell check + * results are received from the native spell check service. */ - void initiateSpellChecking(String locale, String text); + void initiateSpellCheck(String locale, String text); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 02da80772c53b..a48e073dc5f60 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -356,14 +356,6 @@ public void performPrivateCommand( "TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json)); } - /** Responsible for sending spell checker results across to the framework. */ - public void updateSpellCheckerResults( - int inputClientId, ArrayList spellCheckerResults, String spellCheckedText) { - channel.invokeMethod( - "TextInputClient.updateSpellCheckerResults", - Arrays.asList(0, spellCheckerResults, spellCheckedText)); - } - /** * Sets the {@link TextInputMethodHandler} which receives all events and requests that are parsed * from the underlying platform channel. @@ -372,14 +364,6 @@ public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInput this.textInputMethodHandler = textInputMethodHandler; } - public interface SpellCheckMethodHandler { - /** - * Requests that spell checking is initiated for the inputted text recognized by the framework, - * which will automatically result in spell checking resutls being sent back to the framework. - */ - void initiateSpellChecking(String locale, String text); - } - public interface TextInputMethodHandler { // TODO(mattcarroll): javadoc void show(); @@ -446,12 +430,6 @@ public interface TextInputMethodHandler { * @param data Any data to include with the command. */ void sendAppPrivateCommand(@NonNull String action, @NonNull Bundle data); - - /** - * Requests that spell checking is initiated for the inputted text recognized by the framework, - * which will automatically result in spell checking resutls being sent back to the framework. - */ - void initiateSpellChecking(String locale, String text); } /** A text editing configuration. */ diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 9f93551c9f516..82653bacf484b 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -5,7 +5,6 @@ package io.flutter.plugin.editing; import android.content.Context; -import android.view.View; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSession; @@ -17,35 +16,29 @@ import java.util.ArrayList; import java.util.Locale; +/** Android implementation of the spell check plugin. */ public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSessionListener { - @NonNull private final View mView; + @NonNull private final Context mContext; @NonNull private final SpellCheckChannel mSpellCheckChannel; @NonNull private final TextServicesManager tsm; private SpellCheckerSession mSpellCheckerSession; - // String of spell checked text for testing - private String currentSpellCheckedText; - - public SpellCheckPlugin(@NonNull View view, @NonNull SpellCheckChannel spellCheckChannel) { - mView = view; + public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { + mContext = context; mSpellCheckChannel = spellCheckChannel; - tsm = - (TextServicesManager) - view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + tsm = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); mSpellCheckChannel.setSpellCheckMethodHandler( new SpellCheckChannel.SpellCheckMethodHandler() { @Override - public void initiateSpellChecking(String locale, String text) { - currentSpellCheckedText = text; + public void initiateSpellCheck(String locale, String text) { performSpellCheck(locale, text); } }); } - // Responsible for calling the Android spell checker API to retrieve spell - // checking results. + /** Calls on the Android spell check API to spell check specified text. */ public void performSpellCheck(String locale, String text) { String[] localeCodes = locale.split("-"); Locale localeToUse; @@ -58,8 +51,6 @@ public void performSpellCheck(String locale, String text) { localeToUse = new Locale(localeCodes[0]); } - // Open a new spell checker session when a new text input client is set. - // Closes spell checker session if one previously in use. // TODO(camillesimon): Figure out proper session management. if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); @@ -69,16 +60,15 @@ public void performSpellCheck(String locale, String text) { } SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); - // Define TextInfo[] object (textInfos) based on the current input to be - // spell checked. TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; - // Make API call. Maximum suggestions requested set to 3 for now. mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); } - // Responsible for decomposing spell checker results into an object that can - // then be sent to the framework. + /** + * Callback for Android spell check API that decomposes results and send results through the + * {@link SpellCheckChannel}. + */ @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { ArrayList spellCheckerSuggestionSpans = new ArrayList(); @@ -105,15 +95,12 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { } } - // Make call to update the spell checker results in the framework. - // Current text being passed for testing purposes. - // TODO(camillesimon): Don't pass text back to framework. - mSpellCheckChannel.updateSpellCheckerResults( - spellCheckerSuggestionSpans, currentSpellCheckedText); + mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); } @Override + @SuppressWarnings("deprecation") public void onGetSuggestions(SuggestionsInfo[] results) { - // Callback for a deprecated method, so will not use. + // Deprecated callback for Android spell check API; will not use. } } diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index e25e38377c0f0..70658ee934493 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -22,12 +22,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.textservice.SentenceSuggestionsInfo; -import android.view.textservice.SpellCheckerInfo; -import android.view.textservice.SpellCheckerSession; -import android.view.textservice.SuggestionsInfo; -import android.view.textservice.TextInfo; -import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -38,18 +32,14 @@ import io.flutter.plugin.platform.PlatformViewsController; import java.util.ArrayList; import java.util.HashMap; -import java.util.Locale; /** Android implementation of the text input plugin. */ -public class TextInputPlugin - implements ListenableEditingState.EditingStateWatcher, - SpellCheckerSession.SpellCheckerSessionListener { +public class TextInputPlugin implements ListenableEditingState.EditingStateWatcher { private static final String TAG = "TextInputPlugin"; @NonNull private final View mView; @NonNull private final InputMethodManager mImm; @NonNull private final AutofillManager afm; - @NonNull private final TextServicesManager tsm; @NonNull private final TextInputChannel textInputChannel; @NonNull private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); @Nullable private TextInputChannel.Configuration configuration; @@ -60,10 +50,6 @@ public class TextInputPlugin @NonNull private PlatformViewsController platformViewsController; @Nullable private Rect lastClientRect; private ImeSyncDeferringInsetsCallback imeSyncCallback; - private SpellCheckerSession mSpellCheckerSession; - - // String of spell checked text for testing - private String currentSpellCheckedText; // Initialize the "last seen" text editing values to a non-null value. private TextEditState mLastKnownFrameworkTextEditingState; @@ -167,23 +153,12 @@ public void clearClient() { public void sendAppPrivateCommand(String action, Bundle data) { sendTextInputAppPrivateCommand(action, data); } - - @Override - public void initiateSpellChecking(String locale, String text) { - currentSpellCheckedText = text; - performSpellCheck(locale, text); - } }); textInputChannel.requestExistingInputState(); this.platformViewsController = platformViewsController; this.platformViewsController.attachTextInputPlugin(this); - - // Retrieve manager for text services - tsm = - (TextServicesManager) - view.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); } @NonNull @@ -215,9 +190,6 @@ public void destroy() { if (imeSyncCallback != null) { imeSyncCallback.remove(); } - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - } } private static int inputTypeFromTextInputType( @@ -508,9 +480,6 @@ void clearTextInputClient() { updateAutofillConfigurationIfNeeded(null); inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); lastClientRect = null; - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - } } private static class InputTarget { @@ -776,76 +745,4 @@ public void autofill(@NonNull SparseArray values) { textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues); } // -------- End: Autofill ------- - - // -------- Start: Spell Check ------- - // Responsible for calling the Android spell checker API to retrieve spell - // checking results. - public void performSpellCheck(String locale, String text) { - String[] localeCodes = locale.split("-"); - Locale localeToUse; - - if (localeCodes.length == 3) { - localeToUse = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); - } else if (localeCodes.length == 2) { - localeToUse = new Locale(localeCodes[0], localeCodes[1]); - } else { - localeToUse = new Locale(localeCodes[0]); - } - - // Open a new spell checker session when a new text input client is set. - // Closes spell checker session if one previously in use. - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); - } else { - mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); - } - SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); - - // Define TextInfo[] object (textInfos) based on the current input to be - // spell checked. - TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; - - // Make API call. Maximum suggestions requested set to 3 for now. - mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); - } - - // Responsible for decomposing spell checker results into an object that can - // then be sent to the framework. - @Override - public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - ArrayList spellCheckerSuggestionSpans = new ArrayList(); - - for (int i = 0; i < results[0].getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); - int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - - if (suggestionsCount > 0) { - String spellCheckerSuggestionSpan = ""; - int start = results[0].getOffsetAt(i); - int length = results[0].getLengthAt(i); - - spellCheckerSuggestionSpan += (String.valueOf(start) + "."); - spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); - - for (int j = 0; j < suggestionsCount; j++) { - String key = "suggestion_" + String.valueOf(j); - spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); - } - - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); - } - } - - // Make call to update the spell checker results in the framework. - textInputChannel.updateSpellCheckerResults( - inputTarget.id, spellCheckerSuggestionSpans, currentSpellCheckedText); - } - - @Override - public void onGetSuggestions(SuggestionsInfo[] results) { - // Callback for a deprecated method, so will not use. - } - // -------- End: Spell Check ------- } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index ba02e0c7b1ce4..70ec9dc385a30 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -15,7 +15,6 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.Window; -import android.view.WindowManager; import androidx.activity.OnBackPressedDispatcherOwner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -370,9 +369,6 @@ private void setSystemChromeSystemUIOverlayStyle( WindowInsetsControllerCompat windowInsetsControllerCompat = new WindowInsetsControllerCompat(window, view); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - // SYSTEM STATUS BAR ------------------------------------------------------------------- // You can't change the color of the system status bar until SDK 21, and you can't change the // color of the status icons until SDK 23. We only allow both starting at 23 to ensure buttons From 34809db32a3b94cfef99fae522657876bb88cb09 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 28 Mar 2022 16:03:16 -0700 Subject: [PATCH 16/62] Undo platform plugin changes --- .../android/io/flutter/plugin/platform/PlatformPlugin.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java index 70ec9dc385a30..a1d38ba0720db 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java @@ -15,6 +15,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.Window; +import android.view.WindowManager; import androidx.activity.OnBackPressedDispatcherOwner; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -432,6 +433,8 @@ private void setSystemChromeSystemUIOverlayStyle( } // You can't change the color of the navigation bar divider color until SDK 28. if (systemChromeStyle.systemNavigationBarDividerColor != null && Build.VERSION.SDK_INT >= 28) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); window.setNavigationBarDividerColor(systemChromeStyle.systemNavigationBarDividerColor); } From 10d154dcebc44a159110a37b7d1daeabd7cf13c0 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 30 Mar 2022 10:39:09 -0700 Subject: [PATCH 17/62] Clean up code --- .../embedding/android/FlutterView.java | 1 + .../plugin/editing/SpellCheckPlugin.java | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index d8ae0dc4397c5..b0ea64ad97e26 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1227,6 +1227,7 @@ public void detachFromFlutterEngine() { textInputPlugin.getInputMethodManager().restartInput(this); textInputPlugin.destroy(); keyboardManager.destroy(); + spellCheckPlugin.destroy(); if (mouseCursorPlugin != null) { mouseCursorPlugin.destroy(); diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 82653bacf484b..fbe9769631ac9 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -38,30 +38,36 @@ public void initiateSpellCheck(String locale, String text) { }); } - /** Calls on the Android spell check API to spell check specified text. */ + + public void destroy() { + mSpellCheckChannel.setSpellCheckMethodHandler(null); + + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); + } + } + + /** + * Calls on the Android spell check API to spell check specified text. + */ public void performSpellCheck(String locale, String text) { String[] localeCodes = locale.split("-"); - Locale localeToUse; + Locale parsedLocale; if (localeCodes.length == 3) { - localeToUse = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); + parsedLocale = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); } else if (localeCodes.length == 2) { - localeToUse = new Locale(localeCodes[0], localeCodes[1]); + parsedLocale = new Locale(localeCodes[0], localeCodes[1]); } else { - localeToUse = new Locale(localeCodes[0]); + parsedLocale = new Locale(localeCodes[0]); } - // TODO(camillesimon): Figure out proper session management. - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); - mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); - } else { - mSpellCheckerSession = tsm.newSpellCheckerSession(null, Locale.ENGLISH, this, true); + if (mSpellCheckerSession == null) { + mSpellCheckerSession = tsm.newSpellCheckerSession(null, parsedLocale, this, true); } - SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); + SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; - mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); } From 00c228e0113c2947c9224618c2edf7cf2fbc2aba Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 30 Mar 2022 12:10:00 -0700 Subject: [PATCH 18/62] Begin adding tests --- .../plugin/editing/SpellCheckPlugin.java | 5 +- ...lutterActivityAndFragmentDelegateTest.java | 2 + .../embedding/android/FlutterViewTest.java | 8 +++ .../plugin/editing/SpellCheckPluginTest.java | 68 +++++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index fbe9769631ac9..8ce48194c9070 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -38,7 +38,6 @@ public void initiateSpellCheck(String locale, String text) { }); } - public void destroy() { mSpellCheckChannel.setSpellCheckMethodHandler(null); @@ -47,9 +46,7 @@ public void destroy() { } } - /** - * Calls on the Android spell check API to spell check specified text. - */ + /** Calls on the Android spell check API to spell check specified text. */ public void performSpellCheck(String locale, String text) { String[] localeCodes = locale.split("-"); Locale parsedLocale; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 4c84e9bfaf8bc..b5b96b5ff0212 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -1067,6 +1067,8 @@ private FlutterEngine mockFlutterEngine() { when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 444efb9f92247..145c34a06183b 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -214,6 +214,8 @@ public void itSendsLightPlatformBrightnessToFlutter() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -265,6 +267,8 @@ public void itSendsDarkPlatformBrightnessToFlutter() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -304,6 +308,8 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -339,6 +345,8 @@ public void itSendsTextHidePasswordToFrameworkOnAttach() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java new file mode 100644 index 0000000000000..2c6a1a07d9cbe --- /dev/null +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -0,0 +1,68 @@ +package io.flutter.plugin.editing; + +import static org.mockito.Mockito.isNull; + +import android.content.Context; +import android.view.textservice.SpellCheckerSession; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; +import io.flutter.plugin.common.BinaryMessenger; +import java.util.Arrays; +import org.mockito.ArgumentCaptor; + +public class SpellCheckPluginTest { + + private static void sendToBinaryMessageHandler( + BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) { + MethodCall methodCall = new MethodCall(method, args); + ByteBuffer encodedMethodCall = JSONMethodCodec.INSTANCE.encodeMethodCall(methodCall); + binaryMessageHandler.onMessage( + (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class)); + } + + @Test + public void respondsToSpellCheckChannelMessage() { + ArgumentCaptor binaryMessageHandlerCaptor = + ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class); + DartExecutor mockBinaryMessenger = mock(DartExecutor.class); + SpellCheckChannel.SpellCheckMethodHandler mockHandler = + mock(SpellCheckChannel.SpellCheckMethodHandler.class); + SpellCheckChannel spellCheckChannel = new SpellCheckChannel(mockBinaryMessenger); + + spellCheckChannel.setSpellCheckMethodHandler(mockHandler); + + verify(mockBinaryMessenger, times(1)) + .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture()); + + BinaryMessenger.BinaryMessageHandler binaryMessageHandler = + binaryMessageHandlerCaptor.getValue(); + + sendToBinaryMessageHandler(binaryMessageHandler, "SpellCheck.initiateSpellCheck", Arrays.asList("en-US", "Hello, world!")); + verify(mockHandler, times(1)).initiateSpellCheck("en-US", "Hello, world!"); + } + + @Test + public void performSpellCheckRequestsUpdateSpellCheckResults() { + // Verify call to performSpellCheckResults(...) leads to call to "SpellCheck.updateSpellCheckResults" + // This would require simulating TextServicesManager because otherwise, there is a lot to mock. + } + + @Test + public void onGetSentenceSuggestionsProperlyFormatsSpellCheckResults() { + // Verify that spell check results are properly formatted by onGetSentenceSuggestions(...) + } + + @Test + public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { + Context fakeContext = mock(Context.class); + SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckerSesssion fakeSpellCheckerSession = mock(SpellCheckerSession.class); + + spellCheckPlugin.destroy(); + + verify(fakeSpellCheckChannel).setSpellCheckMethodHandler(isNull()); + verify(fakeSpellCheckerSession).close(); + } + +} From e08171fff7c0b047b24a02ba869e28c707b05c0a Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 30 Mar 2022 12:11:33 -0700 Subject: [PATCH 19/62] Formatting --- .../plugin/editing/SpellCheckPluginTest.java | 67 ++++++++++--------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index 2c6a1a07d9cbe..0e376b558b5e4 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -12,18 +12,18 @@ public class SpellCheckPluginTest { - private static void sendToBinaryMessageHandler( - BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) { - MethodCall methodCall = new MethodCall(method, args); - ByteBuffer encodedMethodCall = JSONMethodCodec.INSTANCE.encodeMethodCall(methodCall); - binaryMessageHandler.onMessage( - (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class)); - } + private static void sendToBinaryMessageHandler( + BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) { + MethodCall methodCall = new MethodCall(method, args); + ByteBuffer encodedMethodCall = JSONMethodCodec.INSTANCE.encodeMethodCall(methodCall); + binaryMessageHandler.onMessage( + (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class)); + } - @Test - public void respondsToSpellCheckChannelMessage() { + @Test + public void respondsToSpellCheckChannelMessage() { ArgumentCaptor binaryMessageHandlerCaptor = - ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class); + ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class); DartExecutor mockBinaryMessenger = mock(DartExecutor.class); SpellCheckChannel.SpellCheckMethodHandler mockHandler = mock(SpellCheckChannel.SpellCheckMethodHandler.class); @@ -37,32 +37,35 @@ public void respondsToSpellCheckChannelMessage() { BinaryMessenger.BinaryMessageHandler binaryMessageHandler = binaryMessageHandlerCaptor.getValue(); - sendToBinaryMessageHandler(binaryMessageHandler, "SpellCheck.initiateSpellCheck", Arrays.asList("en-US", "Hello, world!")); + sendToBinaryMessageHandler( + binaryMessageHandler, + "SpellCheck.initiateSpellCheck", + Arrays.asList("en-US", "Hello, world!")); verify(mockHandler, times(1)).initiateSpellCheck("en-US", "Hello, world!"); - } + } - @Test - public void performSpellCheckRequestsUpdateSpellCheckResults() { - // Verify call to performSpellCheckResults(...) leads to call to "SpellCheck.updateSpellCheckResults" - // This would require simulating TextServicesManager because otherwise, there is a lot to mock. - } + @Test + public void performSpellCheckRequestsUpdateSpellCheckResults() { + // Verify call to performSpellCheckResults(...) leads to call to + // "SpellCheck.updateSpellCheckResults" + // This would require simulating TextServicesManager because otherwise, there is a lot to mock. + } - @Test - public void onGetSentenceSuggestionsProperlyFormatsSpellCheckResults() { - // Verify that spell check results are properly formatted by onGetSentenceSuggestions(...) - } + @Test + public void onGetSentenceSuggestionsProperlyFormatsSpellCheckResults() { + // Verify that spell check results are properly formatted by onGetSentenceSuggestions(...) + } - @Test - public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { - Context fakeContext = mock(Context.class); - SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); - SpellCheckerSesssion fakeSpellCheckerSession = mock(SpellCheckerSession.class); + @Test + public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { + Context fakeContext = mock(Context.class); + SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckerSesssion fakeSpellCheckerSession = mock(SpellCheckerSession.class); - spellCheckPlugin.destroy(); + spellCheckPlugin.destroy(); - verify(fakeSpellCheckChannel).setSpellCheckMethodHandler(isNull()); - verify(fakeSpellCheckerSession).close(); - } - + verify(fakeSpellCheckChannel).setSpellCheckMethodHandler(isNull()); + verify(fakeSpellCheckerSession).close(); + } } From 5ccc1440c1fcd060fee344567bfe45d8fd41b12d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 31 Mar 2022 10:40:02 -0700 Subject: [PATCH 20/62] Correct sessions, remove unecessary line --- .../io/flutter/plugin/editing/SpellCheckPlugin.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 8ce48194c9070..ab56cec0240ef 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -59,9 +59,11 @@ public void performSpellCheck(String locale, String text) { parsedLocale = new Locale(localeCodes[0]); } - if (mSpellCheckerSession == null) { - mSpellCheckerSession = tsm.newSpellCheckerSession(null, parsedLocale, this, true); + if (mSpellCheckerSession != null) { + mSpellCheckerSession.close(); } + mSpellCheckerSession = tsm.newSpellCheckerSession(null, parsedLocale, this, true); + SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; @@ -89,7 +91,6 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); for (int j = 0; j < suggestionsCount; j++) { - String key = "suggestion_" + String.valueOf(j); spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); } From 87ab60f310848ca9a3052539357fa7c98fb71376 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 31 Mar 2022 15:03:38 -0700 Subject: [PATCH 21/62] Add tests --- .../plugin/editing/SpellCheckPlugin.java | 18 ++--- .../plugin/editing/SpellCheckPluginTest.java | 72 +++++++++++++++---- 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index ab56cec0240ef..562a17869a7df 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -12,6 +12,7 @@ import android.view.textservice.TextInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import java.util.ArrayList; import java.util.Locale; @@ -23,19 +24,20 @@ public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSession @NonNull private final SpellCheckChannel mSpellCheckChannel; @NonNull private final TextServicesManager tsm; private SpellCheckerSession mSpellCheckerSession; + @VisibleForTesting @NonNull final SpellCheckChannel.SpellCheckMethodHandler mSpellCheckMethodHandler; public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { mContext = context; mSpellCheckChannel = spellCheckChannel; tsm = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - mSpellCheckChannel.setSpellCheckMethodHandler( - new SpellCheckChannel.SpellCheckMethodHandler() { - @Override - public void initiateSpellCheck(String locale, String text) { - performSpellCheck(locale, text); - } - }); + mSpellCheckMethodHandler = new SpellCheckChannel.SpellCheckMethodHandler() { + @Override + public void initiateSpellCheck(String locale, String text) { + performSpellCheck(locale, text); + }}; + + mSpellCheckChannel.setSpellCheckMethodHandler(mSpellCheckMethodHandler); } public void destroy() { @@ -64,8 +66,6 @@ public void performSpellCheck(String locale, String text) { } mSpellCheckerSession = tsm.newSpellCheckerSession(null, parsedLocale, this, true); - - SpellCheckerInfo infoChecker = mSpellCheckerSession.getSpellChecker(); TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index 0e376b558b5e4..4e28efcc78278 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -1,15 +1,35 @@ package io.flutter.plugin.editing; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; +import android.view.textservice.SentenceSuggestionsInfo; +import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSession; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; +import android.view.textservice.TextServicesManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Locale; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +@RunWith(AndroidJUnit4.class) public class SpellCheckPluginTest { private static void sendToBinaryMessageHandler( @@ -40,32 +60,56 @@ public void respondsToSpellCheckChannelMessage() { sendToBinaryMessageHandler( binaryMessageHandler, "SpellCheck.initiateSpellCheck", - Arrays.asList("en-US", "Hello, world!")); - verify(mockHandler, times(1)).initiateSpellCheck("en-US", "Hello, world!"); - } + Arrays.asList("en-US", "Hello, wrold!")); - @Test - public void performSpellCheckRequestsUpdateSpellCheckResults() { - // Verify call to performSpellCheckResults(...) leads to call to - // "SpellCheck.updateSpellCheckResults" - // This would require simulating TextServicesManager because otherwise, there is a lot to mock. - } - - @Test - public void onGetSentenceSuggestionsProperlyFormatsSpellCheckResults() { - // Verify that spell check results are properly formatted by onGetSentenceSuggestions(...) + verify(mockHandler).initiateSpellCheck("en-US", "Hello, wrold!"); } @Test public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { Context fakeContext = mock(Context.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)).thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); - SpellCheckerSesssion fakeSpellCheckerSession = mock(SpellCheckerSession.class); + SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + when(fakeTextServicesManager.newSpellCheckerSession(null, new Locale("en", "US"), spellCheckPlugin, true)).thenReturn(fakeSpellCheckerSession); + spellCheckPlugin.performSpellCheck("en-US", "Hello, wrold!"); spellCheckPlugin.destroy(); verify(fakeSpellCheckChannel).setSpellCheckMethodHandler(isNull()); verify(fakeSpellCheckerSession).close(); } + + @Test + public void performSpellCheckSendsRequestToAndroidSpellCheckService() { + Context fakeContext = mock(Context.class); + SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)).thenReturn(fakeTextServicesManager); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + when(fakeTextServicesManager.newSpellCheckerSession(null, new Locale("en", "US"), spellCheckPlugin, true)).thenReturn(fakeSpellCheckerSession); + + ArgumentCaptor textInfosCaptor = ArgumentCaptor.forClass(TextInfo[].class); + ArgumentCaptor maxSuggestionsCaptor = ArgumentCaptor.forClass(Integer.class); + + spellCheckPlugin.performSpellCheck("en-US", "Hello, wrold!"); + + verify(fakeSpellCheckerSession).getSentenceSuggestions(textInfosCaptor.capture(), maxSuggestionsCaptor.capture()); + assertEquals("Hello, wrold!", textInfosCaptor.getValue()[0].getText()); + assertEquals(Integer.valueOf(3), maxSuggestionsCaptor.getValue()); + } + + @Test + public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { + Context fakeContext = mock(Context.class); + SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + + spellCheckPlugin.onGetSentenceSuggestions(new SentenceSuggestionsInfo[]{new SentenceSuggestionsInfo((new SuggestionsInfo[]{new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO, new String[]{"world", "word", "old"})}), new int[]{7}, new int[]{5})}); + + verify(fakeSpellCheckChannel).updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world,word,old"))); + } } From f4083d71bc06e184ac519382ce00ad1348985377 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 31 Mar 2022 15:04:14 -0700 Subject: [PATCH 22/62] Formatting --- .../plugin/editing/SpellCheckPlugin.java | 17 +++++---- .../plugin/editing/SpellCheckPluginTest.java | 37 ++++++++++++++----- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 562a17869a7df..dd951815c9432 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -6,7 +6,6 @@ import android.content.Context; import android.view.textservice.SentenceSuggestionsInfo; -import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; @@ -24,18 +23,22 @@ public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSession @NonNull private final SpellCheckChannel mSpellCheckChannel; @NonNull private final TextServicesManager tsm; private SpellCheckerSession mSpellCheckerSession; - @VisibleForTesting @NonNull final SpellCheckChannel.SpellCheckMethodHandler mSpellCheckMethodHandler; + + @VisibleForTesting @NonNull + final SpellCheckChannel.SpellCheckMethodHandler mSpellCheckMethodHandler; public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { mContext = context; mSpellCheckChannel = spellCheckChannel; tsm = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - mSpellCheckMethodHandler = new SpellCheckChannel.SpellCheckMethodHandler() { - @Override - public void initiateSpellCheck(String locale, String text) { - performSpellCheck(locale, text); - }}; + mSpellCheckMethodHandler = + new SpellCheckChannel.SpellCheckMethodHandler() { + @Override + public void initiateSpellCheck(String locale, String text) { + performSpellCheck(locale, text); + } + }; mSpellCheckChannel.setSpellCheckMethodHandler(mSpellCheckMethodHandler); } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index 4e28efcc78278..1f085fdd3f5f0 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -10,7 +10,6 @@ import android.content.Context; import android.view.textservice.SentenceSuggestionsInfo; -import android.view.textservice.SpellCheckerInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; @@ -70,10 +69,13 @@ public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { Context fakeContext = mock(Context.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)).thenReturn(fakeTextServicesManager); + when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) + .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); - when(fakeTextServicesManager.newSpellCheckerSession(null, new Locale("en", "US"), spellCheckPlugin, true)).thenReturn(fakeSpellCheckerSession); + when(fakeTextServicesManager.newSpellCheckerSession( + null, new Locale("en", "US"), spellCheckPlugin, true)) + .thenReturn(fakeSpellCheckerSession); spellCheckPlugin.performSpellCheck("en-US", "Hello, wrold!"); spellCheckPlugin.destroy(); @@ -87,17 +89,21 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { Context fakeContext = mock(Context.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)).thenReturn(fakeTextServicesManager); + when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) + .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); - when(fakeTextServicesManager.newSpellCheckerSession(null, new Locale("en", "US"), spellCheckPlugin, true)).thenReturn(fakeSpellCheckerSession); + when(fakeTextServicesManager.newSpellCheckerSession( + null, new Locale("en", "US"), spellCheckPlugin, true)) + .thenReturn(fakeSpellCheckerSession); ArgumentCaptor textInfosCaptor = ArgumentCaptor.forClass(TextInfo[].class); ArgumentCaptor maxSuggestionsCaptor = ArgumentCaptor.forClass(Integer.class); - + spellCheckPlugin.performSpellCheck("en-US", "Hello, wrold!"); - verify(fakeSpellCheckerSession).getSentenceSuggestions(textInfosCaptor.capture(), maxSuggestionsCaptor.capture()); + verify(fakeSpellCheckerSession) + .getSentenceSuggestions(textInfosCaptor.capture(), maxSuggestionsCaptor.capture()); assertEquals("Hello, wrold!", textInfosCaptor.getValue()[0].getText()); assertEquals(Integer.valueOf(3), maxSuggestionsCaptor.getValue()); } @@ -107,9 +113,20 @@ public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { Context fakeContext = mock(Context.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); - - spellCheckPlugin.onGetSentenceSuggestions(new SentenceSuggestionsInfo[]{new SentenceSuggestionsInfo((new SuggestionsInfo[]{new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO, new String[]{"world", "word", "old"})}), new int[]{7}, new int[]{5})}); - verify(fakeSpellCheckChannel).updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world,word,old"))); + spellCheckPlugin.onGetSentenceSuggestions( + new SentenceSuggestionsInfo[] { + new SentenceSuggestionsInfo( + (new SuggestionsInfo[] { + new SuggestionsInfo( + SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO, + new String[] {"world", "word", "old"}) + }), + new int[] {7}, + new int[] {5}) + }); + + verify(fakeSpellCheckChannel) + .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world,word,old"))); } } From ce454270224436e298f3a41ceb317158db75a2e7 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 31 Mar 2022 15:15:16 -0700 Subject: [PATCH 23/62] Remove variable --- .../io/flutter/plugin/editing/SpellCheckPlugin.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index dd951815c9432..81b55937be9d0 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -11,7 +11,6 @@ import android.view.textservice.TextInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import java.util.ArrayList; import java.util.Locale; @@ -24,23 +23,18 @@ public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSession @NonNull private final TextServicesManager tsm; private SpellCheckerSession mSpellCheckerSession; - @VisibleForTesting @NonNull - final SpellCheckChannel.SpellCheckMethodHandler mSpellCheckMethodHandler; - public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { mContext = context; mSpellCheckChannel = spellCheckChannel; tsm = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - mSpellCheckMethodHandler = + mSpellCheckChannel.setSpellCheckMethodHandler( new SpellCheckChannel.SpellCheckMethodHandler() { @Override public void initiateSpellCheck(String locale, String text) { performSpellCheck(locale, text); } - }; - - mSpellCheckChannel.setSpellCheckMethodHandler(mSpellCheckMethodHandler); + }); } public void destroy() { From 6972b0b700a7cd5dd963a1008c071b439c5cfddf Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 1 Apr 2022 16:20:08 -0700 Subject: [PATCH 24/62] Begin fixing tests --- .../embedding/android/FlutterViewTest.java | 102 ++++++++++++++++-- 1 file changed, 91 insertions(+), 11 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 145c34a06183b..71f1afc51f4ea 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -35,6 +35,7 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.textservice.TextServicesManager; import android.widget.FrameLayout; import androidx.core.util.Consumer; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -83,11 +84,19 @@ public void setUp() { @Test public void attachToFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + // Attaching FlutterView to engine requires access to TestServicesManager to initialize + // SpellCheckPlugin, so we mock it. + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + flutterView.attachToFlutterEngine(flutterEngine); verify(platformViewsController, times(1)).attachToView(flutterView); @@ -95,11 +104,17 @@ public void attachToFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + flutterView.attachToFlutterEngine(flutterEngine); flutterView.detachFromFlutterEngine(); @@ -108,12 +123,18 @@ public void detachFromFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_turnsOffA11y() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + flutterView.attachToFlutterEngine(flutterEngine); flutterView.detachFromFlutterEngine(); @@ -122,10 +143,16 @@ public void detachFromFlutterEngine_turnsOffA11y() { @Test public void detachFromFlutterEngine_revertImageView() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + flutterView.attachToFlutterEngine(flutterEngine); assertFalse(flutterView.renderSurface instanceof FlutterImageView); @@ -138,10 +165,17 @@ public void detachFromFlutterEngine_revertImageView() { @Test public void detachFromFlutterEngine_removeImageView() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + Context contextSpy = spy(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(contextSpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + when(flutterView.getContext()).thenReturn(contextSpy); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + flutterView.attachToFlutterEngine(flutterEngine); flutterView.convertToImageView(); assertEquals(flutterView.getChildCount(), 2); @@ -165,8 +199,13 @@ public void detachFromFlutterEngine_closesImageView() { FlutterImageView imageViewMock = mock(FlutterImageView.class); when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); + Context contextSpy = spy(RuntimeEnvironment.application); + FlutterView flutterView = spy(new FlutterView(contextSpy)); when(flutterView.createImageView()).thenReturn(imageViewMock); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); flutterView.attachToFlutterEngine(flutterEngine); @@ -182,10 +221,16 @@ public void detachFromFlutterEngine_closesImageView() { @Test public void onConfigurationChanged_fizzlesWhenNullEngine() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + Configuration configuration = RuntimeEnvironment.application.getResources().getConfiguration(); // 1 invocation of channels. flutterView.attachToFlutterEngine(flutterEngine); @@ -207,10 +252,16 @@ public void itSendsLightPlatformBrightnessToFlutter() { new AtomicReference<>(); // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); @@ -264,6 +315,11 @@ public void itSendsDarkPlatformBrightnessToFlutter() { FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(spiedContext) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); @@ -420,12 +476,20 @@ public void setPaddingTopToZeroForFullscreenMode() { FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenModeLegacy() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + // Attaching FlutterView to engine requires access to TestServicesManager to initialize + // SpellCheckPlugin, so we mock it. + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + // When we attach a new FlutterView to the engine without any system insets, the viewport // metrics // default to 0. @@ -527,7 +591,15 @@ public void systemInsetHandlesFullscreenNavbarRight() { assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); + + // Attaching FlutterView to engine requires access to TestServicesManager to initialize + // SpellCheckPlugin, so we mock it. + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -678,13 +750,21 @@ public void systemInsetGetInsetsFullscreenLegacy() { assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + // Attaching FlutterView to engine requires access to TestServicesManager to initialize + // SpellCheckPlugin, so we mock it. + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + // When we attach a new FlutterView to the engine without any system insets, // the viewport metrics default to 0. flutterView.attachToFlutterEngine(flutterEngine); From 3eba4de38a56776904a3819d3198c30a66e6663f Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 7 Apr 2022 15:29:37 -0700 Subject: [PATCH 25/62] Address reviews --- .../systemchannels/SpellCheckChannel.java | 36 +++++- .../plugin/editing/SpellCheckPlugin.java | 110 +++++++++++------- .../plugin/editing/SpellCheckPluginTest.java | 27 ++++- 3 files changed, 126 insertions(+), 47 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 302e68d0f1724..c3a10b56537d9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -2,7 +2,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.plugin.common.JSONMethodCodec; @@ -12,13 +11,39 @@ import org.json.JSONArray; import org.json.JSONException; +/** + * {@link SpellCheckChannel} is a platform channel that is used by Flutter to initiate spell check + * in the Android engine and for the Android engine to send back the results. + * + *

If the {@link io.flutter.plugin.editing.SpellCheckPlugin} is used to handle spell check + * behavior, (such is the case by default) then there is new text to be spell checked, Flutter will + * send a message to the engine. 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. + * + *

Once the spell check results are received by the {@link + * io.flutter.plugin.editing.SpellCheckPlugin}, a message will be sent back to Flutter with the + * results. See diagram below for overview: + * ----------------------------------------------------------------------------------------------------------------------------- + * | From | To | Message | Arguments | + * | ---------------------------------------------------------------------------------------------------------------------------| + * | Flutter | Android Engine | SpellCheck.iniateSpellCheck | {@code String} locale, {@code String} text | + * |----------------------------------------------------------------------------------------------------------------------------| + * | Android Engine | Flutter | SpellCheck.updateSpellCheckResults | {@code ArrayList} of results | + * ----------------------------------------------------------------------------------------------------------------------------- + * + *

By default, {@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link + * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to initiate spell check via the Android + * spell check service. Implement {@link SpellCheckMethodHandler} to respond to Flutter spell check + * messages. + */ public class SpellCheckChannel { private static final String TAG = "SpellCheckChannel"; @NonNull public final MethodChannel channel; @Nullable private SpellCheckMethodHandler spellCheckMethodHandler; - @NonNull @VisibleForTesting + @NonNull final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @Override @@ -26,6 +51,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result if (spellCheckMethodHandler == null) { // If no explicit SpellCheckMethodHandler has been registered then we don't // need to forward this call to an API. Return. + Log.v( + TAG, + "No SpellCheckeMethodHandler registered, call not forwarded to spell check API."); return; } String method = call.method; @@ -51,7 +79,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result }; public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { - this.channel = new MethodChannel(dartExecutor, "flutter/spellcheck", JSONMethodCodec.INSTANCE); + channel = new MethodChannel(dartExecutor, "flutter/spellcheck", JSONMethodCodec.INSTANCE); channel.setMethodCallHandler(parsingMethodHandler); } @@ -76,6 +104,6 @@ public interface SpellCheckMethodHandler { * SpellCheckChannel#setSpellCheckMethodHandler(SpellCheckMethodHandler)} once spell check * results are received from the native spell check service. */ - void initiateSpellCheck(String locale, String text); + void initiateSpellCheck(@NonNull String locale, @NonNull String text); } } diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 81b55937be9d0..0528e1dc95338 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -12,31 +12,50 @@ import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; +import io.flutter.plugin.localization.LocalizationPlugin; import java.util.ArrayList; import java.util.Locale; -/** Android implementation of the spell check plugin. */ -public class SpellCheckPlugin implements SpellCheckerSession.SpellCheckerSessionListener { - - @NonNull private final Context mContext; - @NonNull private final SpellCheckChannel mSpellCheckChannel; - @NonNull private final TextServicesManager tsm; +/** + * {@link SpellCheckPlugin} is the implementation of all functionality needed for spell check for + * text input. + * + *

The plugin handles requests for spell check sent by the {@link + * io.flutter.embedding.engine.systemchannels.SpellCheckChannel} via sending requests to the Android + * spell checker. It also receive the spell check results from the service and send them back to + * Flutter through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. + */ +public class SpellCheckPlugin + implements SpellCheckChannel.SpellCheckMethodHandler, + SpellCheckerSession.SpellCheckerSessionListener { + + private final Context mContext; + private final SpellCheckChannel mSpellCheckChannel; + private final TextServicesManager mTextServicesManager; private SpellCheckerSession mSpellCheckerSession; + // The maximum number of suggestions that the Android spell check service is allowed to provide + // per word. + // Same number that is used by default for Android's {@code TextView}'s. + private static final int MAX_SPELL_CHECK_SUGGESTIONS = 5; + public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { mContext = context; mSpellCheckChannel = spellCheckChannel; - tsm = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + mTextServicesManager = + (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - mSpellCheckChannel.setSpellCheckMethodHandler( - new SpellCheckChannel.SpellCheckMethodHandler() { - @Override - public void initiateSpellCheck(String locale, String text) { - performSpellCheck(locale, text); - } - }); + mSpellCheckChannel.setSpellCheckMethodHandler(this); } + /** + * Unregisters this {@code SpellCheckPlugin} as the {@code + * SpellCheckChannel.SpellCheckMethodHandler}, for the {@link + * io.flutter.embedding.engine.systemchannels.SpellCheckChannel}, and closes the most recently + * opened {@code SpellCheckerSessions}. + * + *

Do not invoke any methods on a {@code SpellCheckPlugin} after invoking this method. + */ public void destroy() { mSpellCheckChannel.setSpellCheckMethodHandler(null); @@ -45,54 +64,66 @@ public void destroy() { } } + @Override + public void initiateSpellCheck(@NonNull String locale, @NonNull String text) { + performSpellCheck(locale, text); + } + /** Calls on the Android spell check API to spell check specified text. */ public void performSpellCheck(String locale, String text) { String[] localeCodes = locale.split("-"); - Locale parsedLocale; - - if (localeCodes.length == 3) { - parsedLocale = new Locale(localeCodes[0], localeCodes[1], localeCodes[2]); - } else if (localeCodes.length == 2) { - parsedLocale = new Locale(localeCodes[0], localeCodes[1]); - } else { - parsedLocale = new Locale(localeCodes[0]); - } + Locale localeFromString = LocalizationPlugin.localeFromString(locale); if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); } - mSpellCheckerSession = tsm.newSpellCheckerSession(null, parsedLocale, this, true); + mSpellCheckerSession = + mTextServicesManager.newSpellCheckerSession( + null, + localeFromString, + this, + /** referToSpellCheckerLanguageSettings= */ + true); TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; - mSpellCheckerSession.getSentenceSuggestions(textInfos, 3); + mSpellCheckerSession.getSentenceSuggestions(textInfos, MAX_SPELL_CHECK_SUGGESTIONS); } /** * Callback for Android spell check API that decomposes results and send results through the * {@link SpellCheckChannel}. + * + *

Spell check results will be encoded as a string representing the span of that result, with + * the format [start_index.end_index.suggestion_1,suggestion_2,suggestion_3] where there may be up + * to 5 suggestions. */ @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { ArrayList spellCheckerSuggestionSpans = new ArrayList(); - for (int i = 0; i < results[0].getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = results[0].getSuggestionsInfoAt(i); - int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + if (results.length > 0) { + SentenceSuggestionsInfo spellCheckResults = results[0]; - if (suggestionsCount > 0) { - String spellCheckerSuggestionSpan = ""; - int start = results[0].getOffsetAt(i); - int length = results[0].getLengthAt(i); + for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { - spellCheckerSuggestionSpan += (String.valueOf(start) + "."); - spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); + SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - for (int j = 0; j < suggestionsCount; j++) { - spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); - } + if (suggestionsCount > 0) { + String spellCheckerSuggestionSpan = ""; + int start = spellCheckResults.getOffsetAt(i); + int length = spellCheckResults.getLengthAt(i); + + spellCheckerSuggestionSpan += (String.valueOf(start) + "."); + spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + for (int j = 0; j < suggestionsCount; j++) { + spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); + } + + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + } } } @@ -100,7 +131,6 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { } @Override - @SuppressWarnings("deprecation") public void onGetSuggestions(SuggestionsInfo[] results) { // Deprecated callback for Android spell check API; will not use. } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index 1f085fdd3f5f0..e93b08042f608 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -93,9 +93,10 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); - when(fakeTextServicesManager.newSpellCheckerSession( - null, new Locale("en", "US"), spellCheckPlugin, true)) + Locale english_US = new Locale("en", "US"); + when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) .thenReturn(fakeSpellCheckerSession); + int maxSuggestions = 5; ArgumentCaptor textInfosCaptor = ArgumentCaptor.forClass(TextInfo[].class); ArgumentCaptor maxSuggestionsCaptor = ArgumentCaptor.forClass(Integer.class); @@ -105,7 +106,27 @@ null, new Locale("en", "US"), spellCheckPlugin, true)) verify(fakeSpellCheckerSession) .getSentenceSuggestions(textInfosCaptor.capture(), maxSuggestionsCaptor.capture()); assertEquals("Hello, wrold!", textInfosCaptor.getValue()[0].getText()); - assertEquals(Integer.valueOf(3), maxSuggestionsCaptor.getValue()); + assertEquals(Integer.valueOf(maxSuggestions), maxSuggestionsCaptor.getValue()); + } + + @Test + public void performSpellCheckCreatesNewSpellCheckerSession() { + Context fakeContext = mock(Context.class); + SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) + .thenReturn(fakeTextServicesManager); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + Locale english_US = new Locale("en", "US"); + when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) + .thenReturn(fakeSpellCheckerSession); + + spellCheckPlugin.performSpellCheck("en-US", "Hello, worl!"); + spellCheckPlugin.performSpellCheck("en-US", "Hello, world!"); + + verify(fakeTextServicesManager, times(2)) + .newSpellCheckerSession(null, english_US, spellCheckPlugin, true); } @Test From 963d9ae9f75f9ea8499acffd84bca1e81c5aa36e Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 7 Apr 2022 17:18:57 -0700 Subject: [PATCH 26/62] Start fixing systesm setting --- .../embedding/android/FlutterView.java | 26 +++++++++++++++++-- .../systemchannels/SpellCheckChannel.java | 11 ++------ .../plugin/editing/SpellCheckPlugin.java | 12 ++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index b0ea64ad97e26..75c5adb15354b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -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; @@ -130,6 +132,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC @Nullable private KeyboardManager keyboardManager; @Nullable private AndroidTouchProcessor androidTouchProcessor; @Nullable private AccessibilityBridge accessibilityBridge; + private TextServicesManager textServicesManager; // Provides access to foldable/hinge information @Nullable private WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo; @@ -1122,8 +1125,10 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); + textServicesManager = + (TextServicesManager) getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); spellCheckPlugin = - new SpellCheckPlugin(getContext(), this.flutterEngine.getSpellCheckChannel()); + new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); localizationPlugin = this.flutterEngine.getLocalizationPlugin(); keyboardManager = @@ -1412,11 +1417,28 @@ public void removeFlutterEngineAttachmentListener( ? SettingsChannel.PlatformBrightness.dark : SettingsChannel.PlatformBrightness.light; + boolean isNativeSpellCheckServiceDefined = true; + + if (Build.VERSION.SDK_INT >= 31) { + SpellCheckerInfo spellCheckerInfo = textServicesManager.getCurrentSpellCheckerInfo(); + + if (spellCheckerInfo != null) { + // Checks if enabled spell checker is that supported by GBoard. + isNativeSpellCheckServiceDefined = + textServicesManager.isSpellCheckerEnabled() + && (spellCheckerInfo + .getPackageName() + .equals("com.google.android.inputmethod.latin")); + } else { + isNativeSpellCheckServiceDefined = false; + } + } + flutterEngine .getSettingsChannel() .startMessage() .setTextScaleFactor(getResources().getConfiguration().fontScale) - .setNativeSpellCheckServiceDefined(true) + .setNativeSpellCheckServiceDefined(isNativeSpellCheckServiceDefined) .setBrieflyShowPassword( Settings.System.getInt( getContext().getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index c3a10b56537d9..9a7e2651907dc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -16,21 +16,14 @@ * in the Android engine and for the Android engine to send back the results. * *

If the {@link io.flutter.plugin.editing.SpellCheckPlugin} is used to handle spell check - * behavior, (such is the case by default) then there is new text to be spell checked, Flutter will + * behavior (such is the case by default), then when there is new text to be spell checked, Flutter will * send a message to the engine. 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. * *

Once the spell check results are received by the {@link * io.flutter.plugin.editing.SpellCheckPlugin}, a message will be sent back to Flutter with the - * results. See diagram below for overview: - * ----------------------------------------------------------------------------------------------------------------------------- - * | From | To | Message | Arguments | - * | ---------------------------------------------------------------------------------------------------------------------------| - * | Flutter | Android Engine | SpellCheck.iniateSpellCheck | {@code String} locale, {@code String} text | - * |----------------------------------------------------------------------------------------------------------------------------| - * | Android Engine | Flutter | SpellCheck.updateSpellCheckResults | {@code ArrayList} of results | - * ----------------------------------------------------------------------------------------------------------------------------- + * results. * *

By default, {@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to initiate spell check via the Android diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 0528e1dc95338..279512e44eaac 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -4,7 +4,6 @@ package io.flutter.plugin.editing; -import android.content.Context; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; @@ -29,21 +28,20 @@ public class SpellCheckPlugin implements SpellCheckChannel.SpellCheckMethodHandler, SpellCheckerSession.SpellCheckerSessionListener { - private final Context mContext; private final SpellCheckChannel mSpellCheckChannel; private final TextServicesManager mTextServicesManager; private SpellCheckerSession mSpellCheckerSession; // The maximum number of suggestions that the Android spell check service is allowed to provide // per word. - // Same number that is used by default for Android's {@code TextView}'s. + // Same number that is used by default for Android's TextViews. private static final int MAX_SPELL_CHECK_SUGGESTIONS = 5; - public SpellCheckPlugin(@NonNull Context context, @NonNull SpellCheckChannel spellCheckChannel) { - mContext = context; + public SpellCheckPlugin( + @NonNull TextServicesManager textServicesManager, + @NonNull SpellCheckChannel spellCheckChannel) { + mTextServicesManager = textServicesManager; mSpellCheckChannel = spellCheckChannel; - mTextServicesManager = - (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); mSpellCheckChannel.setSpellCheckMethodHandler(this); } From 6ea2ede534531a1c358c8d96ba1bb6b15c6e2d7f Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 8 Apr 2022 10:59:49 -0700 Subject: [PATCH 27/62] Continue addressing reviews --- lib/ui/platform_dispatcher.dart | 6 ++--- lib/ui/window.dart | 2 +- .../embedding/android/FlutterView.java | 3 ++- .../systemchannels/SpellCheckChannel.java | 24 ++++++++++--------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 2740f745816dc..82e04011db40f 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -832,12 +832,12 @@ class PlatformDispatcher { _onTextScaleFactorChangedZone = Zone.current; } - /// The setting inidicating whether or not Flutter currently supports + /// The setting indicating whether or not Flutter currently supports /// spell checking via the native spell check service. /// /// This option is used by [EditableTextState] to define its - /// [SpellCheckConfiguration] when spell check is enabled, but no spell check - /// service is specified. + /// [SpellCheckConfiguration] when a default spell check service + /// is requested. bool get nativeSpellCheckServiceDefined => _nativeSpellCheckServiceDefined; bool _nativeSpellCheckServiceDefined = false; diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 8ee672a491589..6ed12aa11d7f3 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -425,7 +425,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// observe when this value changes. double get textScaleFactor => platformDispatcher.textScaleFactor; - /// The setting inidicating whether or not Flutter currently supports + /// The setting indicating whether or not Flutter currently supports /// spell checking via the native spell check service. /// /// {@macro dart.ui.window.accessorForwardWarning} diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 75c5adb15354b..dc943e1b4484d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1423,7 +1423,8 @@ public void removeFlutterEngineAttachmentListener( SpellCheckerInfo spellCheckerInfo = textServicesManager.getCurrentSpellCheckerInfo(); if (spellCheckerInfo != null) { - // Checks if enabled spell checker is that supported by GBoard. + // Checks if enabled spell checker is the one that is suppported by GBoard, which is + // the one Flutter supports by default. isNativeSpellCheckServiceDefined = textServicesManager.isSpellCheckerEnabled() && (spellCheckerInfo diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 9a7e2651907dc..6af3e7b60d49d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -15,20 +15,22 @@ * {@link SpellCheckChannel} is a platform channel that is used by Flutter to initiate spell check * in the Android engine and for the Android engine to send back the results. * - *

If the {@link io.flutter.plugin.editing.SpellCheckPlugin} is used to handle spell check - * behavior (such is the case by default), then when there is new text to be spell checked, Flutter will - * send a message to the engine. 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. + *

When there is new text to be spell checked, Flutter will send to the Android engine 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. * *

Once the spell check results are received by the {@link - * io.flutter.plugin.editing.SpellCheckPlugin}, a message will be sent back to Flutter with the - * results. + * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to Flutter the message {@code + * SpellCheck.updateSpellCheckResults} with the {@code ArrayList} of encoded spell check + * results (see {@link + * io.flutter.plugin.editing.SpellCheckPlugin#onGetSentenceSuggestions(SentenceSuggestionsInfo[])} + * for details) as an argument. * - *

By default, {@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link - * io.flutter.plugin.common.MethodChannel.MethodCallHandler} to initiate spell check via the Android - * spell check service. Implement {@link SpellCheckMethodHandler} to respond to Flutter spell check - * messages. + *

{@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link SpellCheckMethodHandler} + * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to Flutter spell + * check messages. */ public class SpellCheckChannel { private static final String TAG = "SpellCheckChannel"; From 32c77026846a89ae2e6984f70488f7a6b8018d48 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 8 Apr 2022 11:28:19 -0700 Subject: [PATCH 28/62] Continue addressing reviews --- .../android/io/flutter/embedding/android/FlutterView.java | 4 ++++ .../embedding/engine/systemchannels/SpellCheckChannel.java | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index dc943e1b4484d..9e4c26cb2269e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1421,6 +1421,10 @@ public void removeFlutterEngineAttachmentListener( if (Build.VERSION.SDK_INT >= 31) { SpellCheckerInfo spellCheckerInfo = textServicesManager.getCurrentSpellCheckerInfo(); + // List enabledSpellCheckerInfos = textServicesManager.getEnabledSpellCheckerInfos(); + // for (SpellCheckerInfo enabledSpellChecker : enabledSpellCheckerInfos) { + + // } if (spellCheckerInfo != null) { // Checks if enabled spell checker is the one that is suppported by GBoard, which is diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 6af3e7b60d49d..111e0dac8467e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -35,8 +35,8 @@ public class SpellCheckChannel { private static final String TAG = "SpellCheckChannel"; - @NonNull public final MethodChannel channel; - @Nullable private SpellCheckMethodHandler spellCheckMethodHandler; + public final MethodChannel channel; + private SpellCheckMethodHandler spellCheckMethodHandler; @NonNull final MethodChannel.MethodCallHandler parsingMethodHandler = From e9ebd5b91652999ead1a526c038ed3b5ef782ba5 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 8 Apr 2022 14:30:36 -0700 Subject: [PATCH 29/62] Fix spell checkers check --- .../embedding/android/FlutterView.java | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 9e4c26cb2269e..785c323ac2c06 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1420,23 +1420,20 @@ public void removeFlutterEngineAttachmentListener( boolean isNativeSpellCheckServiceDefined = true; if (Build.VERSION.SDK_INT >= 31) { - SpellCheckerInfo spellCheckerInfo = textServicesManager.getCurrentSpellCheckerInfo(); - // List enabledSpellCheckerInfos = textServicesManager.getEnabledSpellCheckerInfos(); - // for (SpellCheckerInfo enabledSpellChecker : enabledSpellCheckerInfos) { - - // } - - if (spellCheckerInfo != null) { - // Checks if enabled spell checker is the one that is suppported by GBoard, which is - // the one Flutter supports by default. - isNativeSpellCheckServiceDefined = - textServicesManager.isSpellCheckerEnabled() - && (spellCheckerInfo - .getPackageName() - .equals("com.google.android.inputmethod.latin")); - } else { - isNativeSpellCheckServiceDefined = false; - } + List enabledSpellCheckerInfos = + textServicesManager.getEnabledSpellCheckerInfos(); + boolean gboardSpellCheckerEnabled = + enabledSpellCheckerInfos.stream() + .anyMatch( + spellCheckerInfo -> + spellCheckerInfo + .getPackageName() + .equals("com.google.android.inputmethod.latin")); + + // 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; } flutterEngine From 8298436b6421141fad42ca0321ea6f0e41f8b40d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 8 Apr 2022 18:24:56 -0700 Subject: [PATCH 30/62] Begin fixing tests --- .../embedding/android/FlutterView.java | 2 + .../embedding/android/FlutterViewTest.java | 73 +++++++++++++++---- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 785c323ac2c06..0c5f1eb30b4f2 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1125,10 +1125,12 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); + textServicesManager = (TextServicesManager) getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); spellCheckPlugin = new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); + localizationPlugin = this.flutterEngine.getLocalizationPlugin(); keyboardManager = diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 71f1afc51f4ea..b55c2296ada12 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -166,17 +166,19 @@ public void detachFromFlutterEngine_revertImageView() { @Test public void detachFromFlutterEngine_removeImageView() { Context contextSpy = spy(RuntimeEnvironment.application); - FlutterView flutterView = new FlutterView(contextSpy); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - when(flutterView.getContext()).thenReturn(contextSpy); + + // when(flutterView.getContext()).thenReturn(contextSpy); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) .when(contextSpy) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - + System.out.println(flutterView.getChildCount()); flutterView.attachToFlutterEngine(flutterEngine); + System.out.println(flutterView.getChildCount()); flutterView.convertToImageView(); assertEquals(flutterView.getChildCount(), 2); View view = flutterView.getChildAt(1); @@ -199,9 +201,11 @@ public void detachFromFlutterEngine_closesImageView() { FlutterImageView imageViewMock = mock(FlutterImageView.class); when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); - Context contextSpy = spy(RuntimeEnvironment.application); - FlutterView flutterView = spy(new FlutterView(contextSpy)); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); when(flutterView.createImageView()).thenReturn(imageViewMock); + when(flutterView.getContext()).thenReturn(contextSpy); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) .when(contextSpy) @@ -355,7 +359,10 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { // Setup test. AtomicReference reportedShowPassword = new AtomicReference<>(); - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); Settings.System.putInt( @@ -381,6 +388,9 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) }); when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); flutterView.attachToFlutterEngine(flutterEngine); @@ -436,11 +446,18 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenMode() { - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); + + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); // When we attach a new FlutterView to the engine without any system insets, the viewport // metrics @@ -516,7 +533,13 @@ public void setPaddingTopToZeroForFullscreenModeLegacy() { @Config(sdk = 30) public void reportSystemInsetWhenNotFullscreen() { // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); + Context contextSpy = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + when(flutterView.getContext()).thenReturn(contextSpy); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -555,7 +578,12 @@ public void reportSystemInsetWhenNotFullscreen() { @Config(sdk = 28) public void reportSystemInsetWhenNotFullscreenLegacy() { // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); + Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activitySpy); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activitySpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -669,11 +697,16 @@ public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarLeft() { FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -713,7 +746,12 @@ public void systemInsetGetInsetsFullscreen() { assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + when(flutterView.getContext()).thenReturn(contextSpy); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -795,7 +833,12 @@ public void systemInsetDisplayCutoutSimple() { FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); - when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); + Context contextSpy = spy(RuntimeEnvironment.systemContext); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + when(flutterView.getContext()).thenReturn(contextSpy); + doReturn(fakeTextServicesManager) + .when(contextSpy) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -865,7 +908,7 @@ public void itRegistersAndUnregistersToWindowManager() { @Test public void itSendsHingeDisplayFeatureToFlutter() { - Context context = Robolectric.setupActivity(Activity.class); + Context context = spy(Robolectric.setupActivity(Activity.class)); FlutterView flutterView = spy(new FlutterView(context)); ShadowDisplay display = Shadows.shadowOf( @@ -873,6 +916,10 @@ public void itSendsHingeDisplayFeatureToFlutter() { RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); when(flutterView.getContext()).thenReturn(context); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); From 993b6c8e892c843cbfcf3123c94ac6eed1f4f776 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 11 Apr 2022 10:06:04 -0700 Subject: [PATCH 31/62] Reformat FlutterViewTest --- .../embedding/android/FlutterViewTest.java | 234 ++++++++---------- 1 file changed, 107 insertions(+), 127 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index b55c2296ada12..2b764d2903ffb 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -84,8 +84,8 @@ public void setUp() { @Test public void attachToFlutterEngine_alertsPlatformViews() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + FlutterView flutterView = new FlutterView(activity); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); @@ -94,7 +94,7 @@ public void attachToFlutterEngine_alertsPlatformViews() { // SpellCheckPlugin, so we mock it. TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); flutterView.attachToFlutterEngine(flutterEngine); @@ -104,16 +104,15 @@ public void attachToFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_alertsPlatformViews() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); flutterView.attachToFlutterEngine(flutterEngine); flutterView.detachFromFlutterEngine(); @@ -123,18 +122,17 @@ public void detachFromFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_turnsOffA11y() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activity) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activitySpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - flutterView.attachToFlutterEngine(flutterEngine); flutterView.detachFromFlutterEngine(); @@ -143,15 +141,14 @@ public void detachFromFlutterEngine_turnsOffA11y() { @Test public void detachFromFlutterEngine_revertImageView() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); flutterView.attachToFlutterEngine(flutterEngine); assertFalse(flutterView.renderSurface instanceof FlutterImageView); @@ -165,20 +162,18 @@ public void detachFromFlutterEngine_revertImageView() { @Test public void detachFromFlutterEngine_removeImageView() { - Context contextSpy = spy(RuntimeEnvironment.application); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - when(flutterView.getContext()).thenReturn(contextSpy); - - // when(flutterView.getContext()).thenReturn(contextSpy); + // TODO(camillesimon): Debug this test. + Context context = spy(RuntimeEnvironment.application); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(contextSpy) + .when(context) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - System.out.println(flutterView.getChildCount()); + FlutterView flutterView = spy(new FlutterView(context)); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + when(flutterView.getContext()).thenReturn(context); + flutterView.attachToFlutterEngine(flutterEngine); - System.out.println(flutterView.getChildCount()); flutterView.convertToImageView(); assertEquals(flutterView.getChildCount(), 2); View view = flutterView.getChildAt(1); @@ -201,15 +196,14 @@ public void detachFromFlutterEngine_closesImageView() { FlutterImageView imageViewMock = mock(FlutterImageView.class); when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); - when(flutterView.createImageView()).thenReturn(imageViewMock); - when(flutterView.getContext()).thenReturn(contextSpy); - + Context context = spy(RuntimeEnvironment.application); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(contextSpy) + .when(context) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); + when(flutterView.createImageView()).thenReturn(imageViewMock); + when(flutterView.getContext()).thenReturn(context); flutterView.attachToFlutterEngine(flutterEngine); @@ -225,15 +219,14 @@ public void detachFromFlutterEngine_closesImageView() { @Test public void onConfigurationChanged_fizzlesWhenNullEngine() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); Configuration configuration = RuntimeEnvironment.application.getResources().getConfiguration(); // 1 invocation of channels. @@ -256,15 +249,14 @@ public void itSendsLightPlatformBrightnessToFlutter() { new AtomicReference<>(); // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); @@ -305,6 +297,10 @@ public void itSendsDarkPlatformBrightnessToFlutter() { new AtomicReference<>(); Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(spiedContext) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); Resources spiedResources = spy(spiedContext.getResources()); when(spiedContext.getResources()).thenReturn(spiedResources); @@ -319,11 +315,6 @@ public void itSendsDarkPlatformBrightnessToFlutter() { FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(spiedContext) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); @@ -359,10 +350,13 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { // Setup test. AtomicReference reportedShowPassword = new AtomicReference<>(); - FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - when(flutterView.getContext()).thenReturn(contextSpy); + Context context = spy(RuntimeEnvironment.systemContext); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); Settings.System.putInt( @@ -388,9 +382,6 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) }); when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); flutterView.attachToFlutterEngine(flutterEngine); @@ -446,18 +437,16 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenMode() { - FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activity) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = spy(new FlutterView(activity)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - when(flutterView.getContext()).thenReturn(contextSpy); - - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); // When we attach a new FlutterView to the engine without any system insets, the viewport // metrics @@ -493,20 +482,17 @@ public void setPaddingTopToZeroForFullscreenMode() { FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenModeLegacy() { - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(activity) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - // Attaching FlutterView to engine requires access to TestServicesManager to initialize - // SpellCheckPlugin, so we mock it. - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activitySpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - // When we attach a new FlutterView to the engine without any system insets, the viewport // metrics // default to 0. @@ -532,14 +518,14 @@ public void setPaddingTopToZeroForFullscreenModeLegacy() { @TargetApi(30) @Config(sdk = 30) public void reportSystemInsetWhenNotFullscreen() { - // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); - Context contextSpy = spy(Robolectric.setupActivity(Activity.class)); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - when(flutterView.getContext()).thenReturn(contextSpy); doReturn(fakeTextServicesManager) - .when(contextSpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + // Without custom shadows, the default system ui visibility flags is 0. + FlutterView flutterView = spy(new FlutterView(activity)); + assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -578,12 +564,12 @@ public void reportSystemInsetWhenNotFullscreen() { @Config(sdk = 28) public void reportSystemInsetWhenNotFullscreenLegacy() { // Without custom shadows, the default system ui visibility flags is 0. - Activity activitySpy = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activitySpy); + Activity activity = spy(Robolectric.setupActivity(Activity.class)); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); doReturn(fakeTextServicesManager) - .when(activitySpy) + .when(activity) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = new FlutterView(activity); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -614,20 +600,17 @@ public void reportSystemInsetWhenNotFullscreenLegacy() { @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarRight() { + Context context = spy(RuntimeEnvironment.systemContext); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_90); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - when(flutterView.getContext()).thenReturn(contextSpy); - - // Attaching FlutterView to engine requires access to TestServicesManager to initialize - // SpellCheckPlugin, so we mock it. - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -696,17 +679,17 @@ public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarLeft() { - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); - Context contextSpy = spy(RuntimeEnvironment.systemContext); + Context context = spy(RuntimeEnvironment.systemContext); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(contextSpy); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -741,17 +724,17 @@ public void systemInsetHandlesFullscreenNavbarLeft() { @TargetApi(30) @Config(sdk = 30, qualifiers = "land") public void systemInsetGetInsetsFullscreen() { + Context context = spy(RuntimeEnvironment.systemContext); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - when(flutterView.getContext()).thenReturn(contextSpy); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -783,26 +766,23 @@ public void systemInsetGetInsetsFullscreen() { @TargetApi(28) @Config(sdk = 28, qualifiers = "land") public void systemInsetGetInsetsFullscreenLegacy() { + Context context = spy(RuntimeEnvironment.systemContext); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - Context contextSpy = spy(RuntimeEnvironment.systemContext); - when(flutterView.getContext()).thenReturn(contextSpy); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); - // Attaching FlutterView to engine requires access to TestServicesManager to initialize - // SpellCheckPlugin, so we mock it. - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(contextSpy) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - // When we attach a new FlutterView to the engine without any system insets, // the viewport metrics default to 0. flutterView.attachToFlutterEngine(flutterEngine); @@ -830,15 +810,15 @@ public void systemInsetGetInsetsFullscreenLegacy() { @TargetApi(30) @Config(sdk = 30, qualifiers = "land") public void systemInsetDisplayCutoutSimple() { - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); - assertEquals(0, flutterView.getSystemUiVisibility()); - when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); - Context contextSpy = spy(RuntimeEnvironment.systemContext); + Context context = spy(RuntimeEnvironment.systemContext); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - when(flutterView.getContext()).thenReturn(contextSpy); doReturn(fakeTextServicesManager) - .when(contextSpy) + .when(context) .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + FlutterView flutterView = spy(new FlutterView(context)); + assertEquals(0, flutterView.getSystemUiVisibility()); + when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); + when(flutterView.getContext()).thenReturn(context); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -909,6 +889,10 @@ public void itRegistersAndUnregistersToWindowManager() { @Test public void itSendsHingeDisplayFeatureToFlutter() { Context context = spy(Robolectric.setupActivity(Activity.class)); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); + doReturn(fakeTextServicesManager) + .when(context) + .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(context)); ShadowDisplay display = Shadows.shadowOf( @@ -916,10 +900,6 @@ public void itSendsHingeDisplayFeatureToFlutter() { RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); when(flutterView.getContext()).thenReturn(context); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = mock(WindowInfoRepositoryCallbackAdapterWrapper.class); doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); From 4179f42fde98be07054359002d18947f5ca3aa5e Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 11 Apr 2022 12:32:08 -0700 Subject: [PATCH 32/62] Fixing tests --- .../embedding/android/FlutterView.java | 58 ++++--- .../plugin/editing/SpellCheckPlugin.java | 1 + .../embedding/android/FlutterViewTest.java | 153 +++--------------- .../plugin/editing/SpellCheckPluginTest.java | 10 +- 4 files changed, 61 insertions(+), 161 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 0c5f1eb30b4f2..bc2090f10dcb7 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -59,6 +59,7 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.plugin.editing.SpellCheckPlugin; import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.localization.LocalizationPlugin; @@ -132,7 +133,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC @Nullable private KeyboardManager keyboardManager; @Nullable private AndroidTouchProcessor androidTouchProcessor; @Nullable private AccessibilityBridge accessibilityBridge; - private TextServicesManager textServicesManager; + @Nullable private TextServicesManager textServicesManager; // Provides access to foldable/hinge information @Nullable private WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo; @@ -1126,10 +1127,17 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); + try{ textServicesManager = - (TextServicesManager) getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - spellCheckPlugin = - new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); + (TextServicesManager) getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + spellCheckPlugin = + new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); + } catch (Exception e) { + spellCheckPlugin = null; + textServicesManager = null; + Log.v(TAG, "TextServicesManager not supported by device, spell check disabled."); + + } localizationPlugin = this.flutterEngine.getLocalizationPlugin(); @@ -1234,7 +1242,9 @@ public void detachFromFlutterEngine() { textInputPlugin.getInputMethodManager().restartInput(this); textInputPlugin.destroy(); keyboardManager.destroy(); - spellCheckPlugin.destroy(); + if (spellCheckPlugin != null) { + spellCheckPlugin.destroy(); + } if (mouseCursorPlugin != null) { mouseCursorPlugin.destroy(); @@ -1419,24 +1429,28 @@ public void removeFlutterEngineAttachmentListener( ? SettingsChannel.PlatformBrightness.dark : SettingsChannel.PlatformBrightness.light; - boolean isNativeSpellCheckServiceDefined = true; - - if (Build.VERSION.SDK_INT >= 31) { - List enabledSpellCheckerInfos = - textServicesManager.getEnabledSpellCheckerInfos(); - boolean gboardSpellCheckerEnabled = - enabledSpellCheckerInfos.stream() - .anyMatch( - spellCheckerInfo -> - spellCheckerInfo - .getPackageName() - .equals("com.google.android.inputmethod.latin")); - - // 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; + boolean isNativeSpellCheckServiceDefined = false; + + if (textServicesManager != null) { + if (Build.VERSION.SDK_INT >= 31) { + List enabledSpellCheckerInfos = + textServicesManager.getEnabledSpellCheckerInfos(); + boolean gboardSpellCheckerEnabled = + enabledSpellCheckerInfos.stream() + .anyMatch( + spellCheckerInfo -> + spellCheckerInfo + .getPackageName() + .equals("com.google.android.inputmethod.latin")); + + // 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() diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 279512e44eaac..30acdbd9d4c98 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -4,6 +4,7 @@ package io.flutter.plugin.editing; +import android.content.Context; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 2b764d2903ffb..444efb9f92247 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -35,7 +35,6 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; -import android.view.textservice.TextServicesManager; import android.widget.FrameLayout; import androidx.core.util.Consumer; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -84,19 +83,11 @@ public void setUp() { @Test public void attachToFlutterEngine_alertsPlatformViews() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - // Attaching FlutterView to engine requires access to TestServicesManager to initialize - // SpellCheckPlugin, so we mock it. - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - flutterView.attachToFlutterEngine(flutterEngine); verify(platformViewsController, times(1)).attachToView(flutterView); @@ -104,12 +95,7 @@ public void attachToFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_alertsPlatformViews() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); @@ -122,12 +108,7 @@ public void detachFromFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_turnsOffA11y() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -141,12 +122,7 @@ public void detachFromFlutterEngine_turnsOffA11y() { @Test public void detachFromFlutterEngine_revertImageView() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -162,16 +138,9 @@ public void detachFromFlutterEngine_revertImageView() { @Test public void detachFromFlutterEngine_removeImageView() { - // TODO(camillesimon): Debug this test. - Context context = spy(RuntimeEnvironment.application); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = spy(new FlutterView(context)); + FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - when(flutterView.getContext()).thenReturn(context); flutterView.attachToFlutterEngine(flutterEngine); flutterView.convertToImageView(); @@ -196,14 +165,8 @@ public void detachFromFlutterEngine_closesImageView() { FlutterImageView imageViewMock = mock(FlutterImageView.class); when(imageViewMock.getAttachedRenderer()).thenReturn(flutterRenderer); - Context context = spy(RuntimeEnvironment.application); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.application)); when(flutterView.createImageView()).thenReturn(imageViewMock); - when(flutterView.getContext()).thenReturn(context); flutterView.attachToFlutterEngine(flutterEngine); @@ -219,12 +182,7 @@ public void detachFromFlutterEngine_closesImageView() { @Test public void onConfigurationChanged_fizzlesWhenNullEngine() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -249,20 +207,13 @@ public void itSendsLightPlatformBrightnessToFlutter() { new AtomicReference<>(); // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -297,10 +248,6 @@ public void itSendsDarkPlatformBrightnessToFlutter() { new AtomicReference<>(); Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(spiedContext) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); Resources spiedResources = spy(spiedContext.getResources()); when(spiedContext.getResources()).thenReturn(spiedResources); @@ -318,8 +265,6 @@ public void itSendsDarkPlatformBrightnessToFlutter() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -350,13 +295,7 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { // Setup test. AtomicReference reportedShowPassword = new AtomicReference<>(); - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); - when(flutterView.getContext()).thenReturn(context); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); Settings.System.putInt( @@ -365,8 +304,6 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -402,8 +339,6 @@ public void itSendsTextHidePasswordToFrameworkOnAttach() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); - when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) - .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -437,12 +372,7 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenMode() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = spy(new FlutterView(activity)); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -482,12 +412,7 @@ public void setPaddingTopToZeroForFullscreenMode() { FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenModeLegacy() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -518,14 +443,8 @@ public void setPaddingTopToZeroForFullscreenModeLegacy() { @TargetApi(30) @Config(sdk = 30) public void reportSystemInsetWhenNotFullscreen() { - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = spy(new FlutterView(activity)); - + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -564,12 +483,7 @@ public void reportSystemInsetWhenNotFullscreen() { @Config(sdk = 28) public void reportSystemInsetWhenNotFullscreenLegacy() { // Without custom shadows, the default system ui visibility flags is 0. - Activity activity = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(activity) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = new FlutterView(activity); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -600,17 +514,12 @@ public void reportSystemInsetWhenNotFullscreenLegacy() { @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarRight() { - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_90); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(context); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -679,17 +588,12 @@ public void systemInsetHandlesFullscreenNavbarRightBelowSDK23() { @Test @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarLeft() { - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(context); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -724,17 +628,12 @@ public void systemInsetHandlesFullscreenNavbarLeft() { @TargetApi(30) @Config(sdk = 30, qualifiers = "land") public void systemInsetGetInsetsFullscreen() { - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(context); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -766,17 +665,12 @@ public void systemInsetGetInsetsFullscreen() { @TargetApi(28) @Config(sdk = 28, qualifiers = "land") public void systemInsetGetInsetsFullscreenLegacy() { - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); setExpectedDisplayRotation(Surface.ROTATION_270); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()) .thenReturn(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - when(flutterView.getContext()).thenReturn(context); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -810,15 +704,10 @@ public void systemInsetGetInsetsFullscreenLegacy() { @TargetApi(30) @Config(sdk = 30, qualifiers = "land") public void systemInsetDisplayCutoutSimple() { - Context context = spy(RuntimeEnvironment.systemContext); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - FlutterView flutterView = spy(new FlutterView(context)); + FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); assertEquals(0, flutterView.getSystemUiVisibility()); when(flutterView.getWindowSystemUiVisibility()).thenReturn(0); - when(flutterView.getContext()).thenReturn(context); + when(flutterView.getContext()).thenReturn(RuntimeEnvironment.systemContext); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -888,11 +777,7 @@ public void itRegistersAndUnregistersToWindowManager() { @Test public void itSendsHingeDisplayFeatureToFlutter() { - Context context = spy(Robolectric.setupActivity(Activity.class)); - TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); - doReturn(fakeTextServicesManager) - .when(context) - .getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + Context context = Robolectric.setupActivity(Activity.class); FlutterView flutterView = spy(new FlutterView(context)); ShadowDisplay display = Shadows.shadowOf( diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index e93b08042f608..b265e18ee9eb1 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -71,7 +71,7 @@ public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); when(fakeTextServicesManager.newSpellCheckerSession( null, new Locale("en", "US"), spellCheckPlugin, true)) @@ -91,7 +91,7 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) @@ -116,7 +116,7 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) @@ -131,9 +131,9 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { @Test public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { - Context fakeContext = mock(Context.class); + TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeContext, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); spellCheckPlugin.onGetSentenceSuggestions( new SentenceSuggestionsInfo[] { From ade72bc87bfe7fdb9c6cb7149898f73d8dac0e1d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 11 Apr 2022 13:05:34 -0700 Subject: [PATCH 33/62] Fix tests minus log errors --- .../embedding/android/FlutterView.java | 45 +++++++++---------- .../plugin/editing/SpellCheckPlugin.java | 1 - .../embedding/android/FlutterViewTest.java | 6 +++ .../plugin/editing/SpellCheckPluginTest.java | 12 +++-- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index bc2090f10dcb7..84eda9960b89a 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -59,7 +59,6 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.plugin.editing.SpellCheckPlugin; import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.localization.LocalizationPlugin; @@ -1127,16 +1126,14 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController()); - try{ - textServicesManager = - (TextServicesManager) getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + try { + textServicesManager = + (TextServicesManager) + getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); spellCheckPlugin = - new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); + new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); } catch (Exception e) { - spellCheckPlugin = null; - textServicesManager = null; Log.v(TAG, "TextServicesManager not supported by device, spell check disabled."); - } localizationPlugin = this.flutterEngine.getLocalizationPlugin(); @@ -1434,23 +1431,23 @@ public void removeFlutterEngineAttachmentListener( if (textServicesManager != null) { if (Build.VERSION.SDK_INT >= 31) { List enabledSpellCheckerInfos = - textServicesManager.getEnabledSpellCheckerInfos(); - boolean gboardSpellCheckerEnabled = - enabledSpellCheckerInfos.stream() - .anyMatch( - spellCheckerInfo -> - spellCheckerInfo - .getPackageName() - .equals("com.google.android.inputmethod.latin")); - - // 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; + textServicesManager.getEnabledSpellCheckerInfos(); + boolean gboardSpellCheckerEnabled = + enabledSpellCheckerInfos.stream() + .anyMatch( + spellCheckerInfo -> + spellCheckerInfo + .getPackageName() + .equals("com.google.android.inputmethod.latin")); + + // 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() diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 30acdbd9d4c98..279512e44eaac 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -4,7 +4,6 @@ package io.flutter.plugin.editing; -import android.content.Context; import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SpellCheckerSession; import android.view.textservice.SuggestionsInfo; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index eb6d4534f7b4a..49e6a5f7fd638 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -213,6 +213,8 @@ public void itSendsLightPlatformBrightnessToFlutter() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -264,6 +266,8 @@ public void itSendsDarkPlatformBrightnessToFlutter() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setBrieflyShowPassword(any(Boolean.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); @@ -303,6 +307,8 @@ public void itSendsTextShowPasswordToFrameworkOnAttach() { SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setNativeSpellCheckServiceDefined(any(Boolean.class))) + .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))) .thenReturn(fakeMessageBuilder); when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index b265e18ee9eb1..ca11f6f48a861 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -71,7 +71,8 @@ public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = + new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); when(fakeTextServicesManager.newSpellCheckerSession( null, new Locale("en", "US"), spellCheckPlugin, true)) @@ -91,7 +92,8 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = + new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) @@ -116,7 +118,8 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = + new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) @@ -133,7 +136,8 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); - SpellCheckPlugin spellCheckPlugin = new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + SpellCheckPlugin spellCheckPlugin = + new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); spellCheckPlugin.onGetSentenceSuggestions( new SentenceSuggestionsInfo[] { From c20b62c9abf014f109b9a1c7212ce8ae096fe5b4 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 13 Apr 2022 15:19:02 -0700 Subject: [PATCH 34/62] Add context shadow to stub tsm request --- .../io/flutter/CustomShadowContextImpl.java | 24 +++++++++++++++++++ .../src/main/resources/robolectric.properties | 1 + 2 files changed, 25 insertions(+) create mode 100644 shell/platform/android/test/io/flutter/CustomShadowContextImpl.java diff --git a/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java b/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java new file mode 100644 index 0000000000000..5b0de72c0267b --- /dev/null +++ b/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java @@ -0,0 +1,24 @@ +package io.flutter; + +import android.content.Context; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowContextImpl; + + +@Implements(className = ShadowContextImpl.CLASS_NAME) +public class CustomShadowContextImpl extends ShadowContextImpl{ + + public static final String CLASS_NAME = "android.app.ContextImpl"; + + @Implementation + @Override + public final Object getSystemService (String name) { + if (name == Context.TEXT_SERVICES_MANAGER_SERVICE) { + return null; + } + return super.getSystemService(name); + } +} diff --git a/shell/platform/android/test_runner/src/main/resources/robolectric.properties b/shell/platform/android/test_runner/src/main/resources/robolectric.properties index ffcbe2dd23944..a15fa53183343 100644 --- a/shell/platform/android/test_runner/src/main/resources/robolectric.properties +++ b/shell/platform/android/test_runner/src/main/resources/robolectric.properties @@ -1 +1,2 @@ sdk=31 +shadows=io.flutter.CustomShadowContextImpl \ No newline at end of file From 49583d45e940e062d94a1f90b68707064260a11d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 13 Apr 2022 15:22:13 -0700 Subject: [PATCH 35/62] Formatting --- .../io/flutter/CustomShadowContextImpl.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java b/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java index 5b0de72c0267b..feab3607987be 100644 --- a/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java +++ b/shell/platform/android/test/io/flutter/CustomShadowContextImpl.java @@ -3,22 +3,18 @@ import android.content.Context; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; -import org.robolectric.annotation.RealObject; -import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowContextImpl; - @Implements(className = ShadowContextImpl.CLASS_NAME) -public class CustomShadowContextImpl extends ShadowContextImpl{ - - public static final String CLASS_NAME = "android.app.ContextImpl"; +public class CustomShadowContextImpl extends ShadowContextImpl { + public static final String CLASS_NAME = "android.app.ContextImpl"; - @Implementation - @Override - public final Object getSystemService (String name) { - if (name == Context.TEXT_SERVICES_MANAGER_SERVICE) { - return null; - } - return super.getSystemService(name); + @Implementation + @Override + public final Object getSystemService(String name) { + if (name == Context.TEXT_SERVICES_MANAGER_SERVICE) { + return null; } + return super.getSystemService(name); + } } From 49939aac920bbe39a9dc083ade32d16b9b065729 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 14 Apr 2022 10:24:18 -0700 Subject: [PATCH 36/62] Fix np error --- lib/ui/platform_dispatcher.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index a9f5714abbdc3..8c7e27a3c3a37 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -912,7 +912,12 @@ class PlatformDispatcher { final double textScaleFactor = (data['textScaleFactor']! as num).toDouble(); final bool alwaysUse24HourFormat = data['alwaysUse24HourFormat']! as bool; - _nativeSpellCheckServiceDefined = data['nativeSpellCheckServiceDefined']! as bool; + final bool? nativeSpellCheckServiceDefined = data['nativeSpellCheckServiceDefined'] as bool?; + if (nativeSpellCheckServiceDefined != null) { + _nativeSpellCheckServiceDefined = nativeSpellCheckServiceDefined; + } else { + _nativeSpellCheckServiceDefined = false; + } // This field is optional. final bool? brieflyShowPassword = data['brieflyShowPassword'] as bool?; if (brieflyShowPassword != null) { From 0cf00f3d5b7c566215aae415205ee36a577d3a0e Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 14 Apr 2022 10:37:17 -0700 Subject: [PATCH 37/62] Add license --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 111e0dac8467e..2c3e17580e3c0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -1,3 +1,7 @@ +// 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; From 48a4354daf35452f3caf40b7a3a00c5c30cac26d Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 14 Apr 2022 13:32:54 -0700 Subject: [PATCH 38/62] Update licenses file --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 0a6f8cd643387..fec5df11f5617 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1295,6 +1295,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/RestorationChannel.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 FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java @@ -1319,6 +1320,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/SpellCheckPlugin.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java From 510f83a907e4d028e99206d8d90530e0e70ac1c1 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 15 Apr 2022 10:11:27 -0700 Subject: [PATCH 39/62] Add nativeSpellCheckServiceDefined to other platform dispatchers --- lib/web_ui/lib/platform_dispatcher.dart | 6 ++++++ lib/web_ui/lib/src/engine/platform_dispatcher.dart | 9 +++++++++ lib/web_ui/lib/window.dart | 2 ++ 3 files changed, 17 insertions(+) diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index 7e72bee0c440f..3cf5848a76f6a 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -82,6 +82,8 @@ abstract class PlatformDispatcher { double get textScaleFactor => configuration.textScaleFactor; + bool get nativeSpellCheckServiceDefined => configuration.nativeSpellCheckServiceDefined; + bool get brieflyShowPassword => true; VoidCallback? get onTextScaleFactorChanged; @@ -123,6 +125,7 @@ class PlatformConfiguration { this.semanticsEnabled = false, this.platformBrightness = Brightness.light, this.textScaleFactor = 1.0, + this.nativeSpellCheckServiceDefined = false, this.locales = const [], this.defaultRouteName = '/', this.systemFontFamily, @@ -134,6 +137,7 @@ class PlatformConfiguration { bool? semanticsEnabled, Brightness? platformBrightness, double? textScaleFactor, + bool? nativeSpellCheckServiceDefined, List? locales, String? defaultRouteName, String? systemFontFamily, @@ -144,6 +148,7 @@ class PlatformConfiguration { semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, platformBrightness: platformBrightness ?? this.platformBrightness, textScaleFactor: textScaleFactor ?? this.textScaleFactor, + nativeSpellCheckServiceDefined: nativeSpellCheckServiceDefined ?? this.nativeSpellCheckServiceDefined, locales: locales ?? this.locales, defaultRouteName: defaultRouteName ?? this.defaultRouteName, systemFontFamily: systemFontFamily ?? this.systemFontFamily, @@ -155,6 +160,7 @@ class PlatformConfiguration { final bool semanticsEnabled; final Brightness platformBrightness; final double textScaleFactor; + final bool nativeSpellCheckServiceDefined; final List locales; final String defaultRouteName; final String? systemFontFamily; diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 5ad7a4dd23a8f..0198c4cfde720 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -777,6 +777,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { @override double get textScaleFactor => configuration.textScaleFactor; + /// The setting indicating whether or not Flutter currently supports + /// spell checking via the native spell check service. + /// + /// This option is used by [EditableTextState] to define its + /// [SpellCheckConfiguration] when a default spell check service + /// is requested. + @override + bool get nativeSpellCheckServiceDefined => configuration.nativeSpellCheckServiceDefined; + /// The setting indicating whether time should always be shown in the 24-hour /// format. /// diff --git a/lib/web_ui/lib/window.dart b/lib/web_ui/lib/window.dart index fa2206778b870..43711551cd9a6 100644 --- a/lib/web_ui/lib/window.dart +++ b/lib/web_ui/lib/window.dart @@ -48,6 +48,8 @@ abstract class SingletonFlutterWindow extends FlutterWindow { double get textScaleFactor => platformDispatcher.textScaleFactor; + bool get nativeSpellCheckServiceDefined => platformDispatcher.nativeSpellCheckServiceDefined; + bool get brieflyShowPassword => platformDispatcher.brieflyShowPassword; bool get alwaysUse24HourFormat => platformDispatcher.alwaysUse24HourFormat; From 61908d32868b1fd69c9c43fdb9119ecf9f11ae74 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 15 Apr 2022 14:58:00 -0700 Subject: [PATCH 40/62] Remove setting --- lib/web_ui/lib/platform_dispatcher.dart | 6 +----- lib/web_ui/lib/src/engine/platform_dispatcher.dart | 9 --------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index 3cf5848a76f6a..5cc6175738e3e 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -82,7 +82,7 @@ abstract class PlatformDispatcher { double get textScaleFactor => configuration.textScaleFactor; - bool get nativeSpellCheckServiceDefined => configuration.nativeSpellCheckServiceDefined; + bool get nativeSpellCheckServiceDefined => false; bool get brieflyShowPassword => true; @@ -125,7 +125,6 @@ class PlatformConfiguration { this.semanticsEnabled = false, this.platformBrightness = Brightness.light, this.textScaleFactor = 1.0, - this.nativeSpellCheckServiceDefined = false, this.locales = const [], this.defaultRouteName = '/', this.systemFontFamily, @@ -137,7 +136,6 @@ class PlatformConfiguration { bool? semanticsEnabled, Brightness? platformBrightness, double? textScaleFactor, - bool? nativeSpellCheckServiceDefined, List? locales, String? defaultRouteName, String? systemFontFamily, @@ -148,7 +146,6 @@ class PlatformConfiguration { semanticsEnabled: semanticsEnabled ?? this.semanticsEnabled, platformBrightness: platformBrightness ?? this.platformBrightness, textScaleFactor: textScaleFactor ?? this.textScaleFactor, - nativeSpellCheckServiceDefined: nativeSpellCheckServiceDefined ?? this.nativeSpellCheckServiceDefined, locales: locales ?? this.locales, defaultRouteName: defaultRouteName ?? this.defaultRouteName, systemFontFamily: systemFontFamily ?? this.systemFontFamily, @@ -160,7 +157,6 @@ class PlatformConfiguration { final bool semanticsEnabled; final Brightness platformBrightness; final double textScaleFactor; - final bool nativeSpellCheckServiceDefined; final List locales; final String defaultRouteName; final String? systemFontFamily; diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 0198c4cfde720..5ad7a4dd23a8f 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -777,15 +777,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { @override double get textScaleFactor => configuration.textScaleFactor; - /// The setting indicating whether or not Flutter currently supports - /// spell checking via the native spell check service. - /// - /// This option is used by [EditableTextState] to define its - /// [SpellCheckConfiguration] when a default spell check service - /// is requested. - @override - bool get nativeSpellCheckServiceDefined => configuration.nativeSpellCheckServiceDefined; - /// The setting indicating whether time should always be shown in the 24-hour /// format. /// From 29b095f1321c848b790d47d553586ae0714f61fe Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 15 Apr 2022 15:59:29 -0700 Subject: [PATCH 41/62] Fix typos --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 279512e44eaac..58fd862cb2e20 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -50,7 +50,7 @@ public SpellCheckPlugin( * Unregisters this {@code SpellCheckPlugin} as the {@code * SpellCheckChannel.SpellCheckMethodHandler}, for the {@link * io.flutter.embedding.engine.systemchannels.SpellCheckChannel}, and closes the most recently - * opened {@code SpellCheckerSessions}. + * opened {@code SpellCheckerSession}. * *

Do not invoke any methods on a {@code SpellCheckPlugin} after invoking this method. */ @@ -92,8 +92,8 @@ public void performSpellCheck(String locale, String text) { * {@link SpellCheckChannel}. * *

Spell check results will be encoded as a string representing the span of that result, with - * the format [start_index.end_index.suggestion_1,suggestion_2,suggestion_3] where there may be up - * to 5 suggestions. + * the format [start_index.end_index.suggestion_1,suggestion_2,suggestion_3], where there may be + * up to 5 suggestions. */ @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { From ee280584ce5db272fb9978132c969e09dcefebf7 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:35:39 -0700 Subject: [PATCH 42/62] Update lib/ui/platform_dispatcher.dart Co-authored-by: Emmanuel Garcia --- lib/ui/platform_dispatcher.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 8c7e27a3c3a37..325b15a586c66 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -842,8 +842,7 @@ class PlatformDispatcher { _onTextScaleFactorChangedZone = Zone.current; } - /// The setting indicating whether or not Flutter currently supports - /// spell checking via the native spell check service. + /// Whether the spell check service is supported on the current platform. /// /// This option is used by [EditableTextState] to define its /// [SpellCheckConfiguration] when a default spell check service From 4f0a074617d9fc8f02aa573090b335846dbd0e24 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:35:49 -0700 Subject: [PATCH 43/62] Update lib/ui/window.dart Co-authored-by: Emmanuel Garcia --- lib/ui/window.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 564ff45da03fc..48716f55c0292 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -425,8 +425,7 @@ class SingletonFlutterWindow extends FlutterWindow { /// observe when this value changes. double get textScaleFactor => platformDispatcher.textScaleFactor; - /// The setting indicating whether or not Flutter currently supports - /// spell checking via the native spell check service. + /// Whether the spell check service is supported on the current platform. /// /// {@macro dart.ui.window.accessorForwardWarning} /// From 6c4d437cd247dc6245862769d6741f9ae3d00c1d Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:36:03 -0700 Subject: [PATCH 44/62] Update shell/platform/android/io/flutter/embedding/android/FlutterView.java Co-authored-by: Emmanuel Garcia --- .../android/io/flutter/embedding/android/FlutterView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 84eda9960b89a..c9e6ed80c9a51 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -1133,7 +1133,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { spellCheckPlugin = new SpellCheckPlugin(textServicesManager, this.flutterEngine.getSpellCheckChannel()); } catch (Exception e) { - Log.v(TAG, "TextServicesManager not supported by device, spell check disabled."); + Log.e(TAG, "TextServicesManager not supported by device, spell check disabled."); } localizationPlugin = this.flutterEngine.getLocalizationPlugin(); From d73b91527f532110c1b951c123403b2e6eea067f Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:37:01 -0700 Subject: [PATCH 45/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SettingsChannel.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java index f61532c04c33f..5873d87f33e4d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java @@ -44,8 +44,7 @@ public MessageBuilder setTextScaleFactor(float textScaleFactor) { } @NonNull - public MessageBuilder setNativeSpellCheckServiceDefined( - @NonNull boolean nativeSpellCheckServiceDefined) { + public MessageBuilder setNativeSpellCheckServiceDefined(boolean nativeSpellCheckServiceDefined) { message.put(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined); return this; } From 66c24ed02b6d4fe6d4aab35227ec0033665bf123 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:37:15 -0700 Subject: [PATCH 46/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 2c3e17580e3c0..377c4e03cefb2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -19,7 +19,7 @@ * {@link SpellCheckChannel} is a platform channel that is used by Flutter to initiate spell check * in the Android engine and for the Android engine to send back the results. * - *

When there is new text to be spell checked, Flutter will send to the Android engine the + *

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 From 04a2ce15608f9588141ec80f51a51c230a710002 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:37:38 -0700 Subject: [PATCH 47/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 377c4e03cefb2..ba2cb052e04a5 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -16,8 +16,8 @@ import org.json.JSONException; /** - * {@link SpellCheckChannel} is a platform channel that is used by Flutter to initiate spell check - * in the Android engine and for the Android engine to send back the results. + * {@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. * *

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 From 15d17f5ab146388a98aa62c7567dbc64e96a24c9 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:37:51 -0700 Subject: [PATCH 48/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index ba2cb052e04a5..5698a7dbfe6ee 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -26,7 +26,7 @@ * fetch spell check results for the specified text. * *

Once the spell check results are received by the {@link - * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to Flutter the message {@code + * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to the framework the message {@code * SpellCheck.updateSpellCheckResults} with the {@code ArrayList} of encoded spell check * results (see {@link * io.flutter.plugin.editing.SpellCheckPlugin#onGetSentenceSuggestions(SentenceSuggestionsInfo[])} From e6414b276f667b929b956d36b675fb660843f6e6 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:38:06 -0700 Subject: [PATCH 49/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 5698a7dbfe6ee..678286791663e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -33,8 +33,8 @@ * for details) as an argument. * *

{@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link SpellCheckMethodHandler} - * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to Flutter spell - * check messages. + * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to spell + * check requests. */ public class SpellCheckChannel { private static final String TAG = "SpellCheckChannel"; From 2882e3b81cf1d3834003628276cd75bf3a5b4a48 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:38:20 -0700 Subject: [PATCH 50/62] Update shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java Co-authored-by: Emmanuel Garcia --- .../embedding/engine/systemchannels/SpellCheckChannel.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 678286791663e..136565c1be549 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -48,8 +48,6 @@ public class SpellCheckChannel { @Override public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { if (spellCheckMethodHandler == null) { - // If no explicit SpellCheckMethodHandler has been registered then we don't - // need to forward this call to an API. Return. Log.v( TAG, "No SpellCheckeMethodHandler registered, call not forwarded to spell check API."); From dfe46897d73f8cf972843cc40d3dde2dc9f8d0df Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:38:29 -0700 Subject: [PATCH 51/62] Update shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java Co-authored-by: Emmanuel Garcia --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 58fd862cb2e20..52a24dffe14e0 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -22,7 +22,7 @@ *

The plugin handles requests for spell check sent by the {@link * io.flutter.embedding.engine.systemchannels.SpellCheckChannel} via sending requests to the Android * spell checker. It also receive the spell check results from the service and send them back to - * Flutter through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. + * the framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. */ public class SpellCheckPlugin implements SpellCheckChannel.SpellCheckMethodHandler, From cdc6b9c417b081309a903e1963c5575af0b72d0d Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:39:00 -0700 Subject: [PATCH 52/62] Update shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java Co-authored-by: Emmanuel Garcia --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 52a24dffe14e0..fbab46eb4f0d5 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -68,7 +68,7 @@ public void initiateSpellCheck(@NonNull String locale, @NonNull String text) { } /** Calls on the Android spell check API to spell check specified text. */ - public void performSpellCheck(String locale, String text) { + public void performSpellCheck(@NonNull String locale, @NonNull String text) { String[] localeCodes = locale.split("-"); Locale localeFromString = LocalizationPlugin.localeFromString(locale); From 65b88503a1e7b270f94601fd7ee81e5b86bdeee8 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 18 Apr 2022 12:39:27 -0700 Subject: [PATCH 53/62] Update shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java Co-authored-by: Emmanuel Garcia --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index fbab46eb4f0d5..8b1f5f737dbc3 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -103,7 +103,6 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { SentenceSuggestionsInfo spellCheckResults = results[0]; for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); int suggestionsCount = suggestionsInfo.getSuggestionsCount(); From 036b15b4152deeb047ea4921c83a1da786bc4f28 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 18 Apr 2022 13:39:43 -0700 Subject: [PATCH 54/62] Make review fixes --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 5 ++--- .../test_runner/src/main/resources/robolectric.properties | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 58fd862cb2e20..668a1f5bffe11 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -116,11 +116,10 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); for (int j = 0; j < suggestionsCount; j++) { - spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + ","); + spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + "/n"); } - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan); } } } diff --git a/shell/platform/android/test_runner/src/main/resources/robolectric.properties b/shell/platform/android/test_runner/src/main/resources/robolectric.properties index a15fa53183343..ba72a7e57eb78 100644 --- a/shell/platform/android/test_runner/src/main/resources/robolectric.properties +++ b/shell/platform/android/test_runner/src/main/resources/robolectric.properties @@ -1,2 +1,2 @@ sdk=31 -shadows=io.flutter.CustomShadowContextImpl \ No newline at end of file +shadows=io.flutter.CustomShadowContextImpl From 8198d4ef5a156792ac8c70d878a83c1474377d00 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 18 Apr 2022 13:41:03 -0700 Subject: [PATCH 55/62] Formatting --- .../embedding/engine/systemchannels/SettingsChannel.java | 3 ++- .../engine/systemchannels/SpellCheckChannel.java | 8 ++++---- .../io/flutter/plugin/editing/SpellCheckPlugin.java | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java index 5873d87f33e4d..46bf51190c147 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java @@ -44,7 +44,8 @@ public MessageBuilder setTextScaleFactor(float textScaleFactor) { } @NonNull - public MessageBuilder setNativeSpellCheckServiceDefined(boolean nativeSpellCheckServiceDefined) { + public MessageBuilder setNativeSpellCheckServiceDefined( + boolean nativeSpellCheckServiceDefined) { message.put(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined); return this; } diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 136565c1be549..5dc3e6ae8c1e2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -16,8 +16,8 @@ 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. + * {@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. * *

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 @@ -33,8 +33,8 @@ * for details) as an argument. * *

{@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link SpellCheckMethodHandler} - * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to spell - * check requests. + * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to spell check + * requests. */ public class SpellCheckChannel { private static final String TAG = "SpellCheckChannel"; diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 88f63698761f9..510d28e338c87 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -21,8 +21,8 @@ * *

The plugin handles requests for spell check sent by the {@link * io.flutter.embedding.engine.systemchannels.SpellCheckChannel} via sending requests to the Android - * spell checker. It also receive the spell check results from the service and send them back to - * the framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. + * spell checker. It also receive the spell check results from the service and send them back to the + * framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. */ public class SpellCheckPlugin implements SpellCheckChannel.SpellCheckMethodHandler, From a2b6c90fda45c8d166fe4fd7a9c5bb371d0714f4 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Mon, 18 Apr 2022 14:05:07 -0700 Subject: [PATCH 56/62] Fix spell check test --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 3 ++- .../test/io/flutter/plugin/editing/SpellCheckPluginTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 510d28e338c87..dfca397f78747 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -118,7 +118,8 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + "/n"); } - spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan); + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 2)); } } } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index ca11f6f48a861..b8aca5d4fce31 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -152,6 +152,6 @@ public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { }); verify(fakeSpellCheckChannel) - .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world,word,old"))); + .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world/nword/nold"))); } } From 2aac372eb25bc42a6ee8d43c42d411532fd804d0 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 20 Apr 2022 17:02:32 -0700 Subject: [PATCH 57/62] Address nits --- .../plugin/editing/SpellCheckPlugin.java | 39 +++++++++++-------- .../plugin/editing/SpellCheckPluginTest.java | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index dfca397f78747..71beab8b0550a 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -99,29 +99,34 @@ public void performSpellCheck(@NonNull String locale, @NonNull String text) { public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { ArrayList spellCheckerSuggestionSpans = new ArrayList(); - if (results.length > 0) { - SentenceSuggestionsInfo spellCheckResults = results[0]; + if (results.length == 0) { + mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); + return; + } + + SentenceSuggestionsInfo spellCheckResults = results[0]; - for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); - int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { + SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - if (suggestionsCount > 0) { - String spellCheckerSuggestionSpan = ""; - int start = spellCheckResults.getOffsetAt(i); - int length = spellCheckResults.getLengthAt(i); + if (suggestionsCount == 0) { + continue; + } - spellCheckerSuggestionSpan += (String.valueOf(start) + "."); - spellCheckerSuggestionSpan += (String.valueOf(start + (length - 1)) + "."); + String spellCheckerSuggestionSpan = ""; + int start = spellCheckResults.getOffsetAt(i); + int end = start + spellCheckResults.getLengthAt(i) - 1; - for (int j = 0; j < suggestionsCount; j++) { - spellCheckerSuggestionSpan += (suggestionsInfo.getSuggestionAt(j) + "/n"); - } + spellCheckerSuggestionSpan += String.valueOf(start) + "."; + spellCheckerSuggestionSpan += String.valueOf(end) + "."; - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 2)); - } + for (int j = 0; j < suggestionsCount; j++) { + spellCheckerSuggestionSpan += suggestionsInfo.getSuggestionAt(j) + "\n"; } + + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index b8aca5d4fce31..03f2c6ce78bd1 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -152,6 +152,6 @@ public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { }); verify(fakeSpellCheckChannel) - .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world/nword/nold"))); + .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world\nword\nold"))); } } From 894f4e9fa0fd0ff8f7e8954a953c23f14b68cfb9 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 28 Apr 2022 13:29:44 -0700 Subject: [PATCH 58/62] Send text to framework --- .../systemchannels/SpellCheckChannel.java | 11 +- .../plugin/editing/SpellCheckPlugin.java | 106 +++++++++++------- .../plugin/editing/SpellCheckPluginTest.java | 49 ++++++-- 3 files changed, 110 insertions(+), 56 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 5dc3e6ae8c1e2..48f32cb43b2cd 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -29,8 +29,10 @@ * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to the framework the message {@code * SpellCheck.updateSpellCheckResults} with the {@code ArrayList} of encoded spell check * results (see {@link - * io.flutter.plugin.editing.SpellCheckPlugin#onGetSentenceSuggestions(SentenceSuggestionsInfo[])} - * for details) as an argument. + * io.flutter.plugin.editing.SpellCheckPlugin.SpellCheckPluginSessionListener#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"}}. * *

{@link io.flutter.plugin.editing.SpellCheckPlugin} implements {@link SpellCheckMethodHandler} * to initiate spell check. Implement {@link SpellCheckMethodHandler} to respond to spell check @@ -80,8 +82,9 @@ public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { channel.setMethodCallHandler(parsingMethodHandler); } - /** Responsible for sending spell check results through this channel. */ - public void updateSpellCheckResults(ArrayList spellCheckResults) { + /** Responsible for sending spell check results and corresponding text through this channel. */ + public void updateSpellCheckResults(ArrayList spellCheckResults, String text) { + spellCheckResults.add(0, text); channel.invokeMethod("SpellCheck.updateSpellCheckResults", spellCheckResults); } diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 71beab8b0550a..35a42603eeb6a 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -10,6 +10,7 @@ import android.view.textservice.TextInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.plugin.localization.LocalizationPlugin; import java.util.ArrayList; @@ -21,12 +22,10 @@ * *

The plugin handles requests for spell check sent by the {@link * io.flutter.embedding.engine.systemchannels.SpellCheckChannel} via sending requests to the Android - * spell checker. It also receive the spell check results from the service and send them back to the - * framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. + * spell checker. It also receives the spell check results from the service and sends them back to + * the framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. */ -public class SpellCheckPlugin - implements SpellCheckChannel.SpellCheckMethodHandler, - SpellCheckerSession.SpellCheckerSessionListener { +public class SpellCheckPlugin implements SpellCheckChannel.SpellCheckMethodHandler { private final SpellCheckChannel mSpellCheckChannel; private final TextServicesManager mTextServicesManager; @@ -62,6 +61,11 @@ public void destroy() { } } + @VisibleForTesting + public SpellCheckPluginSessionListener createSpellCheckerSessionListener(String text) { + return new SpellCheckPluginSessionListener(text); + } + @Override public void initiateSpellCheck(@NonNull String locale, @NonNull String text) { performSpellCheck(locale, text); @@ -75,11 +79,12 @@ public void performSpellCheck(@NonNull String locale, @NonNull String text) { if (mSpellCheckerSession != null) { mSpellCheckerSession.close(); } + mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession( null, localeFromString, - this, + createSpellCheckerSessionListener(text), /** referToSpellCheckerLanguageSettings= */ true); @@ -87,53 +92,72 @@ public void performSpellCheck(@NonNull String locale, @NonNull String text) { mSpellCheckerSession.getSentenceSuggestions(textInfos, MAX_SPELL_CHECK_SUGGESTIONS); } - /** - * Callback for Android spell check API that decomposes results and send results through the - * {@link SpellCheckChannel}. - * - *

Spell check results will be encoded as a string representing the span of that result, with - * the format [start_index.end_index.suggestion_1,suggestion_2,suggestion_3], where there may be - * up to 5 suggestions. - */ - @Override - public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - ArrayList spellCheckerSuggestionSpans = new ArrayList(); + class SpellCheckPluginSessionListener implements SpellCheckerSession.SpellCheckerSessionListener { + private final String text; - if (results.length == 0) { - mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); - return; + public SpellCheckPluginSessionListener(String text) { + this.text = text; } - SentenceSuggestionsInfo spellCheckResults = results[0]; + @VisibleForTesting + public SpellCheckChannel getSpellCheckChannel() { + return mSpellCheckChannel; + } - for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); - int suggestionsCount = suggestionsInfo.getSuggestionsCount(); + @VisibleForTesting + public String getText() { + return text; + } - if (suggestionsCount == 0) { - continue; + /** + * Callback for Android spell check API that decomposes results and send results through the + * {@link SpellCheckChannel}. + * + *

Spell check results will be encoded as a string representing the span of that result, with + * the format "start_index.end_index.suggestion_1/nsuggestion_2/nsuggestion_3", where there may + * be up to 5 suggestions. + */ + @Override + public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { + ArrayList spellCheckerSuggestionSpans = new ArrayList(); + SpellCheckChannel spellCheckChannel = getSpellCheckChannel(); + + if (results.length == 0) { + spellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans, getText()); + return; } - String spellCheckerSuggestionSpan = ""; - int start = spellCheckResults.getOffsetAt(i); - int end = start + spellCheckResults.getLengthAt(i) - 1; + SentenceSuggestionsInfo spellCheckResults = results[0]; - spellCheckerSuggestionSpan += String.valueOf(start) + "."; - spellCheckerSuggestionSpan += String.valueOf(end) + "."; + for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { + SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - for (int j = 0; j < suggestionsCount; j++) { - spellCheckerSuggestionSpan += suggestionsInfo.getSuggestionAt(j) + "\n"; + if (suggestionsCount == 0) { + continue; + } + + String spellCheckerSuggestionSpan = ""; + int start = spellCheckResults.getOffsetAt(i); + int end = start + spellCheckResults.getLengthAt(i) - 1; + + spellCheckerSuggestionSpan += String.valueOf(start) + "."; + spellCheckerSuggestionSpan += String.valueOf(end) + "."; + + for (int j = 0; j < suggestionsCount; j++) { + spellCheckerSuggestionSpan += suggestionsInfo.getSuggestionAt(j) + "\n"; + } + + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + spellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans, getText()); } - mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); - } - - @Override - public void onGetSuggestions(SuggestionsInfo[] results) { - // Deprecated callback for Android spell check API; will not use. + @Override + public void onGetSuggestions(SuggestionsInfo[] results) { + // Deprecated callback for Android spell check API; will not use. + } } } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index 03f2c6ce78bd1..cf853ea6fc40e 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -4,6 +4,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -72,10 +73,15 @@ public void destroyClosesSpellCheckerSessionAndClearsSpellCheckMethodHandler() { when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = - new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + SpellCheckPlugin.SpellCheckPluginSessionListener fakeSpellCheckPluginSessionListener = + mock(SpellCheckPlugin.SpellCheckPluginSessionListener.class); + + when(spellCheckPlugin.createSpellCheckerSessionListener("Hello, wrold!")) + .thenReturn(fakeSpellCheckPluginSessionListener); when(fakeTextServicesManager.newSpellCheckerSession( - null, new Locale("en", "US"), spellCheckPlugin, true)) + null, new Locale("en", "US"), fakeSpellCheckPluginSessionListener, true)) .thenReturn(fakeSpellCheckerSession); spellCheckPlugin.performSpellCheck("en-US", "Hello, wrold!"); @@ -93,13 +99,19 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = - new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + SpellCheckPlugin.SpellCheckPluginSessionListener fakeSpellCheckPluginSessionListener = + mock(SpellCheckPlugin.SpellCheckPluginSessionListener.class); Locale english_US = new Locale("en", "US"); - when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) + + when(spellCheckPlugin.createSpellCheckerSessionListener("Hello, wrold!")) + .thenReturn(fakeSpellCheckPluginSessionListener); + when(fakeTextServicesManager.newSpellCheckerSession( + null, english_US, fakeSpellCheckPluginSessionListener, true)) .thenReturn(fakeSpellCheckerSession); - int maxSuggestions = 5; + int maxSuggestions = 5; ArgumentCaptor textInfosCaptor = ArgumentCaptor.forClass(TextInfo[].class); ArgumentCaptor maxSuggestionsCaptor = ArgumentCaptor.forClass(Integer.class); @@ -119,17 +131,25 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { when(fakeContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(fakeTextServicesManager); SpellCheckPlugin spellCheckPlugin = - new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); + SpellCheckPlugin.SpellCheckPluginSessionListener fakeSpellCheckPluginSessionListener = + mock(SpellCheckPlugin.SpellCheckPluginSessionListener.class); Locale english_US = new Locale("en", "US"); - when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) + + when(spellCheckPlugin.createSpellCheckerSessionListener("Hello, worl!")) + .thenReturn(fakeSpellCheckPluginSessionListener); + when(spellCheckPlugin.createSpellCheckerSessionListener("Hello, world!")) + .thenReturn(fakeSpellCheckPluginSessionListener); + when(fakeTextServicesManager.newSpellCheckerSession( + null, english_US, fakeSpellCheckPluginSessionListener, true)) .thenReturn(fakeSpellCheckerSession); spellCheckPlugin.performSpellCheck("en-US", "Hello, worl!"); spellCheckPlugin.performSpellCheck("en-US", "Hello, world!"); verify(fakeTextServicesManager, times(2)) - .newSpellCheckerSession(null, english_US, spellCheckPlugin, true); + .newSpellCheckerSession(null, english_US, fakeSpellCheckPluginSessionListener, true); } @Test @@ -137,9 +157,15 @@ public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); SpellCheckPlugin spellCheckPlugin = - new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); + SpellCheckPlugin.SpellCheckPluginSessionListener fakeSpellCheckPluginSessionListener = + spy(spellCheckPlugin.createSpellCheckerSessionListener("Hello, wrold!")); + + when(fakeSpellCheckPluginSessionListener.getSpellCheckChannel()) + .thenReturn(fakeSpellCheckChannel); + when(fakeSpellCheckPluginSessionListener.getText()).thenReturn("Hello, wrold!"); - spellCheckPlugin.onGetSentenceSuggestions( + fakeSpellCheckPluginSessionListener.onGetSentenceSuggestions( new SentenceSuggestionsInfo[] { new SentenceSuggestionsInfo( (new SuggestionsInfo[] { @@ -152,6 +178,7 @@ public void onGetSentenceSuggestionsProperlyRequestsUpdateSpellCheckResults() { }); verify(fakeSpellCheckChannel) - .updateSpellCheckResults(new ArrayList(Arrays.asList("7.11.world\nword\nold"))); + .updateSpellCheckResults( + new ArrayList(Arrays.asList("7.11.world\nword\nold")), "Hello, wrold!"); } } From 2a4ea667cc2ea1d4d570bf491e29ad2a6f6f6876 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 4 May 2022 10:50:44 -0700 Subject: [PATCH 59/62] Push for visibility --- .../systemchannels/SpellCheckChannel.java | 21 +++- .../plugin/editing/SpellCheckPlugin.java | 117 +++++++----------- .../plugin/editing/SpellCheckPluginTest.java | 1 + 3 files changed, 63 insertions(+), 76 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 48f32cb43b2cd..6ff8f9504208e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -29,7 +29,7 @@ * io.flutter.plugin.editing.SpellCheckPlugin}, it will send to the framework the message {@code * SpellCheck.updateSpellCheckResults} with the {@code ArrayList} of encoded spell check * results (see {@link - * io.flutter.plugin.editing.SpellCheckPlugin.SpellCheckPluginSessionListener#onGetSentenceSuggestions(SentenceSuggestionsInfo[])} + * 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"}}. @@ -44,6 +44,9 @@ public class SpellCheckChannel { public final MethodChannel channel; private SpellCheckMethodHandler spellCheckMethodHandler; + private String textAwaitingResponse; + private MethodChannel.Result resultAwaitingResponse; + @NonNull final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @@ -64,8 +67,10 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result final JSONArray argumentList = (JSONArray) args; String locale = argumentList.getString(0); String text = argumentList.getString(1); + textAwaitingResponse = text; + resultAwaitingResponse = result; + spellCheckMethodHandler.initiateSpellCheck(locale, text); - result.success(null); } catch (JSONException exception) { result.error("error", exception.getMessage(), null); } @@ -83,9 +88,15 @@ public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { } /** Responsible for sending spell check results and corresponding text through this channel. */ - public void updateSpellCheckResults(ArrayList spellCheckResults, String text) { - spellCheckResults.add(0, text); - channel.invokeMethod("SpellCheck.updateSpellCheckResults", spellCheckResults); + public void updateSpellCheckResults(ArrayList spellCheckResults) { + spellCheckResults.add(0, textAwaitingResponse); + + if (resultAwaitingResponse != null && textAwaitingResponse != null) { + resultAwaitingResponse.success(spellCheckResults); + } + + resultAwaitingResponse = null; + textAwaitingResponse = null; } /** diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 35a42603eeb6a..5c6edd97067e3 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -10,7 +10,6 @@ import android.view.textservice.TextInfo; import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.plugin.localization.LocalizationPlugin; import java.util.ArrayList; @@ -25,7 +24,9 @@ * spell checker. It also receives the spell check results from the service and sends them back to * the framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}. */ -public class SpellCheckPlugin implements SpellCheckChannel.SpellCheckMethodHandler { +public class SpellCheckPlugin + implements SpellCheckChannel.SpellCheckMethodHandler, + SpellCheckerSession.SpellCheckerSessionListener { private final SpellCheckChannel mSpellCheckChannel; private final TextServicesManager mTextServicesManager; @@ -61,11 +62,6 @@ public void destroy() { } } - @VisibleForTesting - public SpellCheckPluginSessionListener createSpellCheckerSessionListener(String text) { - return new SpellCheckPluginSessionListener(text); - } - @Override public void initiateSpellCheck(@NonNull String locale, @NonNull String text) { performSpellCheck(locale, text); @@ -76,88 +72,67 @@ public void performSpellCheck(@NonNull String locale, @NonNull String text) { String[] localeCodes = locale.split("-"); Locale localeFromString = LocalizationPlugin.localeFromString(locale); - if (mSpellCheckerSession != null) { - mSpellCheckerSession.close(); + if (mSpellCheckerSession == null) { + mSpellCheckerSession = + mTextServicesManager.newSpellCheckerSession( + null, + localeFromString, + this, + /** referToSpellCheckerLanguageSettings= */ + true); } - mSpellCheckerSession = - mTextServicesManager.newSpellCheckerSession( - null, - localeFromString, - createSpellCheckerSessionListener(text), - /** referToSpellCheckerLanguageSettings= */ - true); - TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)}; mSpellCheckerSession.getSentenceSuggestions(textInfos, MAX_SPELL_CHECK_SUGGESTIONS); } - class SpellCheckPluginSessionListener implements SpellCheckerSession.SpellCheckerSessionListener { - private final String text; + /** + * Callback for Android spell check API that decomposes results and send results through the + * {@link SpellCheckChannel}. + * + *

Spell check results will be encoded as a string representing the span of that result, with + * the format "start_index.end_index.suggestion_1/nsuggestion_2/nsuggestion_3", where there may be + * up to 5 suggestions. + */ + @Override + public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { + ArrayList spellCheckerSuggestionSpans = new ArrayList(); - public SpellCheckPluginSessionListener(String text) { - this.text = text; + if (results.length == 0) { + mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); + return; } - @VisibleForTesting - public SpellCheckChannel getSpellCheckChannel() { - return mSpellCheckChannel; - } + SentenceSuggestionsInfo spellCheckResults = results[0]; - @VisibleForTesting - public String getText() { - return text; - } + for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { + SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); + int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - /** - * Callback for Android spell check API that decomposes results and send results through the - * {@link SpellCheckChannel}. - * - *

Spell check results will be encoded as a string representing the span of that result, with - * the format "start_index.end_index.suggestion_1/nsuggestion_2/nsuggestion_3", where there may - * be up to 5 suggestions. - */ - @Override - public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - ArrayList spellCheckerSuggestionSpans = new ArrayList(); - SpellCheckChannel spellCheckChannel = getSpellCheckChannel(); - - if (results.length == 0) { - spellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans, getText()); - return; + if (suggestionsCount == 0) { + continue; } - SentenceSuggestionsInfo spellCheckResults = results[0]; - - for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { - SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); - int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - - if (suggestionsCount == 0) { - continue; - } - - String spellCheckerSuggestionSpan = ""; - int start = spellCheckResults.getOffsetAt(i); - int end = start + spellCheckResults.getLengthAt(i) - 1; - - spellCheckerSuggestionSpan += String.valueOf(start) + "."; - spellCheckerSuggestionSpan += String.valueOf(end) + "."; + String spellCheckerSuggestionSpan = ""; + int start = spellCheckResults.getOffsetAt(i); + int end = start + spellCheckResults.getLengthAt(i) - 1; - for (int j = 0; j < suggestionsCount; j++) { - spellCheckerSuggestionSpan += suggestionsInfo.getSuggestionAt(j) + "\n"; - } + spellCheckerSuggestionSpan += String.valueOf(start) + "."; + spellCheckerSuggestionSpan += String.valueOf(end) + "."; - spellCheckerSuggestionSpans.add( - spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); + for (int j = 0; j < suggestionsCount; j++) { + spellCheckerSuggestionSpan += suggestionsInfo.getSuggestionAt(j) + "\n"; } - spellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans, getText()); + spellCheckerSuggestionSpans.add( + spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } - @Override - public void onGetSuggestions(SuggestionsInfo[] results) { - // Deprecated callback for Android spell check API; will not use. - } + mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); + } + + @Override + public void onGetSuggestions(SuggestionsInfo[] results) { + // Deprecated callback for Android spell check API; will not use. } } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index cf853ea6fc40e..184097b992a10 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -29,6 +29,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +// TODO(camillesimon): Fix theses tests @RunWith(AndroidJUnit4.class) public class SpellCheckPluginTest { From c2a6550c7780f7d3240d50c6e6b2894292c828b4 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 6 May 2022 09:02:59 -0700 Subject: [PATCH 60/62] Modify result response protocol --- .../systemchannels/SpellCheckChannel.java | 30 ++++------------- .../plugin/editing/SpellCheckPlugin.java | 33 +++++++++++++++---- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java index 6ff8f9504208e..43a51d2cc1b7e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -11,7 +11,6 @@ import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import java.util.ArrayList; import org.json.JSONArray; import org.json.JSONException; @@ -44,9 +43,6 @@ public class SpellCheckChannel { public final MethodChannel channel; private SpellCheckMethodHandler spellCheckMethodHandler; - private String textAwaitingResponse; - private MethodChannel.Result resultAwaitingResponse; - @NonNull final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() { @@ -67,10 +63,8 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result final JSONArray argumentList = (JSONArray) args; String locale = argumentList.getString(0); String text = argumentList.getString(1); - textAwaitingResponse = text; - resultAwaitingResponse = result; - spellCheckMethodHandler.initiateSpellCheck(locale, text); + spellCheckMethodHandler.initiateSpellCheck(locale, text, result); } catch (JSONException exception) { result.error("error", exception.getMessage(), null); } @@ -87,18 +81,6 @@ public SpellCheckChannel(@NonNull DartExecutor dartExecutor) { channel.setMethodCallHandler(parsingMethodHandler); } - /** Responsible for sending spell check results and corresponding text through this channel. */ - public void updateSpellCheckResults(ArrayList spellCheckResults) { - spellCheckResults.add(0, textAwaitingResponse); - - if (resultAwaitingResponse != null && textAwaitingResponse != null) { - resultAwaitingResponse.success(spellCheckResults); - } - - resultAwaitingResponse = null; - textAwaitingResponse = null; - } - /** * Sets the {@link SpellCheckMethodHandler} which receives all requests to spell check the * specified text sent through this channel. @@ -110,11 +92,11 @@ public void setSpellCheckMethodHandler( public interface SpellCheckMethodHandler { /** - * Requests that spell check is initiated for the specified text, which will automatically - * result in a call to {@code - * SpellCheckChannel#setSpellCheckMethodHandler(SpellCheckMethodHandler)} once spell check - * results are received from the native spell check service. + * 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); + void initiateSpellCheck( + @NonNull String locale, @NonNull String text, @NonNull MethodChannel.Result result); } } diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index 5c6edd97067e3..755e6a577f3c0 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -11,8 +11,10 @@ import android.view.textservice.TextServicesManager; import androidx.annotation.NonNull; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; +import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.localization.LocalizationPlugin; import java.util.ArrayList; +import java.util.Arrays; import java.util.Locale; /** @@ -32,9 +34,11 @@ public class SpellCheckPlugin private final TextServicesManager mTextServicesManager; private SpellCheckerSession mSpellCheckerSession; + private MethodChannel.Result pendingResult; + private String pendingResultText; + // The maximum number of suggestions that the Android spell check service is allowed to provide - // per word. - // Same number that is used by default for Android's TextViews. + // per word. Same number that is used by default for Android's TextViews. private static final int MAX_SPELL_CHECK_SUGGESTIONS = 5; public SpellCheckPlugin( @@ -62,8 +66,21 @@ public void destroy() { } } + /** + * Initiates call to native spell checker to spell check specified text if there is no result + * awaiting a response. + */ @Override - public void initiateSpellCheck(@NonNull String locale, @NonNull String text) { + public void initiateSpellCheck( + @NonNull String locale, @NonNull String text, @NonNull MethodChannel.Result result) { + if (pendingResult != null) { + result.error("error", "Previous spell check request still pending.", null); + return; + } + + pendingResult = result; + pendingResultText = text; + performSpellCheck(locale, text); } @@ -96,13 +113,13 @@ public void performSpellCheck(@NonNull String locale, @NonNull String text) { */ @Override public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { - ArrayList spellCheckerSuggestionSpans = new ArrayList(); - if (results.length == 0) { - mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); + pendingResult.success(new ArrayList<>(Arrays.asList(pendingResultText, ""))); + pendingResult = null; return; } + ArrayList spellCheckerSuggestionSpans = new ArrayList(); SentenceSuggestionsInfo spellCheckResults = results[0]; for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) { @@ -128,7 +145,9 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { spellCheckerSuggestionSpan.substring(0, spellCheckerSuggestionSpan.length() - 1)); } - mSpellCheckChannel.updateSpellCheckResults(spellCheckerSuggestionSpans); + spellCheckerSuggestionSpans.add(0, pendingResultText); + pendingResult.success(spellCheckerSuggestionSpans); + pendingResult = null; } @Override From 5a175ec2039769cd9275eec1e534092a7336ca04 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Tue, 10 May 2022 13:32:50 -0700 Subject: [PATCH 61/62] Formatting and pull --- .../flutter/embedding/android/FlutterView.java | 2 +- .../plugin/editing/SpellCheckPluginTest.java | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 61b0c2a911df7..538249703216b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -59,8 +59,8 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; -import io.flutter.plugin.editing.SpellCheckPlugin; 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; diff --git a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java index cff828bd658b8..42a8f97d1874a 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/SpellCheckPluginTest.java @@ -24,7 +24,6 @@ import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; - import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -66,8 +65,8 @@ public void respondsToSpellCheckChannelMessage() { "SpellCheck.initiateSpellCheck", Arrays.asList("en-US", "Hello, wrold!")); - - verify(mockHandler).initiateSpellCheck(eq("en-US"), eq("Hello, wrold!"), any(MethodChannel.Result.class)); + verify(mockHandler) + .initiateSpellCheck(eq("en-US"), eq("Hello, wrold!"), any(MethodChannel.Result.class)); } @Test @@ -75,7 +74,7 @@ public void initiateSpellCheckPerformsSpellCheckWhenNoResultPending() { SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); SpellCheckPlugin spellCheckPlugin = - spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); @@ -93,7 +92,7 @@ public void initiateSpellCheckThrowsErrorWhenResultPending() { SpellCheckChannel fakeSpellCheckChannel = mock(SpellCheckChannel.class); TextServicesManager fakeTextServicesManager = mock(TextServicesManager.class); SpellCheckPlugin spellCheckPlugin = - spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); + spy(new SpellCheckPlugin(fakeTextServicesManager, fakeSpellCheckChannel)); MethodChannel.Result mockPendingResult = mock(MethodChannel.Result.class); MethodChannel.Result mockResult = mock(MethodChannel.Result.class); spellCheckPlugin.pendingResult = mockPendingResult; @@ -138,8 +137,7 @@ public void performSpellCheckSendsRequestToAndroidSpellCheckService() { SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); - when(fakeTextServicesManager.newSpellCheckerSession( - null, english_US, spellCheckPlugin, true)) + when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) .thenReturn(fakeSpellCheckerSession); int maxSuggestions = 5; @@ -166,8 +164,7 @@ public void performSpellCheckCreatesNewSpellCheckerSession() { SpellCheckerSession fakeSpellCheckerSession = mock(SpellCheckerSession.class); Locale english_US = new Locale("en", "US"); - when(fakeTextServicesManager.newSpellCheckerSession( - null, english_US, spellCheckPlugin, true)) + when(fakeTextServicesManager.newSpellCheckerSession(null, english_US, spellCheckPlugin, true)) .thenReturn(fakeSpellCheckerSession); spellCheckPlugin.performSpellCheck("en-US", "Hello, worl!"); @@ -189,8 +186,7 @@ public void onGetSentenceSuggestionsResultsWithSuccessAndNoResultsProperly() { spellCheckPlugin.onGetSentenceSuggestions(new SentenceSuggestionsInfo[] {}); - verify(mockResult) - .success(new ArrayList(Arrays.asList("Hello, world!", ""))); + verify(mockResult).success(new ArrayList(Arrays.asList("Hello, world!", ""))); } @Test From 3e74d619ba2522f93b475e31380ee2b44876a21f Mon Sep 17 00:00:00 2001 From: camsim99 Date: Fri, 13 May 2022 13:32:11 -0700 Subject: [PATCH 62/62] Emulator fix --- .../android/io/flutter/plugin/editing/SpellCheckPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java index e5919d8cdae9d..af024801748c6 100644 --- a/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java @@ -127,7 +127,7 @@ public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) { SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i); int suggestionsCount = suggestionsInfo.getSuggestionsCount(); - if (suggestionsCount == 0) { + if (suggestionsCount <= 0) { continue; }