Flutter Engine
The Flutter Engine
SpellCheckPlugin.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.plugin.editing;
6
7import android.view.textservice.SentenceSuggestionsInfo;
8import android.view.textservice.SpellCheckerSession;
9import android.view.textservice.SuggestionsInfo;
10import android.view.textservice.TextInfo;
11import android.view.textservice.TextServicesManager;
12import androidx.annotation.NonNull;
13import androidx.annotation.VisibleForTesting;
14import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
15import io.flutter.plugin.common.MethodChannel;
16import io.flutter.plugin.localization.LocalizationPlugin;
17import java.util.ArrayList;
18import java.util.HashMap;
19import java.util.Locale;
20
21/**
22 * {@link SpellCheckPlugin} is the implementation of all functionality needed for spell check for
23 * text input.
24 *
25 * <p>The plugin handles requests for spell check sent by the {@link
26 * io.flutter.embedding.engine.systemchannels.SpellCheckChannel} via sending requests to the Android
27 * spell checker. It also receives the spell check results from the service and sends them back to
28 * the framework through the {@link io.flutter.embedding.engine.systemchannels.SpellCheckChannel}.
29 */
30public class SpellCheckPlugin
31 implements SpellCheckChannel.SpellCheckMethodHandler,
32 SpellCheckerSession.SpellCheckerSessionListener {
33
34 private final SpellCheckChannel mSpellCheckChannel;
35 private final TextServicesManager mTextServicesManager;
36 private SpellCheckerSession mSpellCheckerSession;
37
38 public static final String START_INDEX_KEY = "startIndex";
39 public static final String END_INDEX_KEY = "endIndex";
40 public static final String SUGGESTIONS_KEY = "suggestions";
41
42 @VisibleForTesting MethodChannel.Result pendingResult;
43
44 // The maximum number of suggestions that the Android spell check service is allowed to provide
45 // per word. Same number that is used by default for Android's TextViews.
46 private static final int MAX_SPELL_CHECK_SUGGESTIONS = 5;
47
49 @NonNull TextServicesManager textServicesManager,
50 @NonNull SpellCheckChannel spellCheckChannel) {
51 mTextServicesManager = textServicesManager;
52 mSpellCheckChannel = spellCheckChannel;
53
54 mSpellCheckChannel.setSpellCheckMethodHandler(this);
55 }
56
57 /**
58 * Unregisters this {@code SpellCheckPlugin} as the {@code
59 * SpellCheckChannel.SpellCheckMethodHandler}, for the {@link
60 * io.flutter.embedding.engine.systemchannels.SpellCheckChannel}, and closes the most recently
61 * opened {@code SpellCheckerSession}.
62 *
63 * <p>Do not invoke any methods on a {@code SpellCheckPlugin} after invoking this method.
64 */
65 public void destroy() {
66 mSpellCheckChannel.setSpellCheckMethodHandler(null);
67
68 if (mSpellCheckerSession != null) {
69 mSpellCheckerSession.close();
70 }
71 }
72
73 /**
74 * Initiates call to native spell checker to spell check specified text if there is no result
75 * awaiting a response.
76 */
77 @Override
78 public void initiateSpellCheck(
79 @NonNull String locale, @NonNull String text, @NonNull MethodChannel.Result result) {
80 if (pendingResult != null) {
81 result.error("error", "Previous spell check request still pending.", null);
82 return;
83 }
84
86
87 performSpellCheck(locale, text);
88 }
89
90 /** Calls on the Android spell check API to spell check specified text. */
91 public void performSpellCheck(@NonNull String locale, @NonNull String text) {
92 Locale localeFromString = LocalizationPlugin.localeFromString(locale);
93
94 if (mSpellCheckerSession == null) {
95 mSpellCheckerSession =
96 mTextServicesManager.newSpellCheckerSession(
97 null,
98 localeFromString,
99 this,
100 /** referToSpellCheckerLanguageSettings= */
101 true);
102 }
103
104 TextInfo[] textInfos = new TextInfo[] {new TextInfo(text)};
105 mSpellCheckerSession.getSentenceSuggestions(textInfos, MAX_SPELL_CHECK_SUGGESTIONS);
106 }
107
108 /**
109 * Callback for Android spell check API that decomposes results and send results through the
110 * {@link SpellCheckChannel}.
111 *
112 * <p>Spell check results are encoded as dictionaries with a format that looks like
113 *
114 * <pre>{@code
115 * {
116 * startIndex: 0,
117 * endIndex: 5,
118 * suggestions: [hello, ...]
119 * }
120 * }</pre>
121 *
122 * where there may be up to 5 suggestions.
123 */
124 @Override
125 public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
126 if (results.length == 0) {
127 pendingResult.success(new ArrayList<HashMap<String, Object>>());
128 pendingResult = null;
129 return;
130 }
131
132 ArrayList<HashMap<String, Object>> spellCheckerSuggestionSpans =
133 new ArrayList<HashMap<String, Object>>();
134 SentenceSuggestionsInfo spellCheckResults = results[0];
135 if (spellCheckResults == null) {
136 pendingResult.success(new ArrayList<HashMap<String, Object>>());
137 pendingResult = null;
138 return;
139 }
140
141 for (int i = 0; i < spellCheckResults.getSuggestionsCount(); i++) {
142 SuggestionsInfo suggestionsInfo = spellCheckResults.getSuggestionsInfoAt(i);
143 int suggestionsCount = suggestionsInfo.getSuggestionsCount();
144
145 if (suggestionsCount <= 0) {
146 continue;
147 }
148
149 HashMap<String, Object> spellCheckerSuggestionSpan = new HashMap<String, Object>();
150 int start = spellCheckResults.getOffsetAt(i);
151 int end = start + spellCheckResults.getLengthAt(i);
152
153 spellCheckerSuggestionSpan.put(START_INDEX_KEY, start);
154 spellCheckerSuggestionSpan.put(END_INDEX_KEY, end);
155
156 ArrayList<String> suggestions = new ArrayList<String>();
157 boolean validSuggestionsFound = false;
158 for (int j = 0; j < suggestionsCount; j++) {
159 String suggestion = suggestionsInfo.getSuggestionAt(j);
160 // TODO(camsim99): Support spell check on Samsung by retrieving accurate spell check
161 // results, then remove this check: https://github.com/flutter/flutter/issues/120608.
162 if (!suggestion.equals("")) {
163 validSuggestionsFound = true;
164 suggestions.add(suggestion);
165 }
166 }
167
168 if (!validSuggestionsFound) {
169 continue;
170 }
171 spellCheckerSuggestionSpan.put(SUGGESTIONS_KEY, suggestions);
172 spellCheckerSuggestionSpans.add(spellCheckerSuggestionSpan);
173 }
174
175 pendingResult.success(spellCheckerSuggestionSpans);
176 pendingResult = null;
177 }
178
179 @Override
180 public void onGetSuggestions(SuggestionsInfo[] results) {
181 // Deprecated callback for Android spell check API; will not use.
182 }
183}
void setSpellCheckMethodHandler( @Nullable SpellCheckMethodHandler spellCheckMethodHandler)
void onGetSuggestions(SuggestionsInfo[] results)
void performSpellCheck(@NonNull String locale, @NonNull String text)
SpellCheckPlugin( @NonNull TextServicesManager textServicesManager, @NonNull SpellCheckChannel spellCheckChannel)
void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results)
void initiateSpellCheck( @NonNull String locale, @NonNull String text, @NonNull MethodChannel.Result result)
static Locale localeFromString(@NonNull String localeString)
glong glong end
GAsyncResult * result
std::u16string text