1package io.flutter.embedding.engine.systemchannels;
3import static io.flutter.Build.API_LEVELS;
8import android.view.inputmethod.EditorInfo;
9import androidx.annotation.NonNull;
10import androidx.annotation.Nullable;
11import androidx.annotation.VisibleForTesting;
13import io.flutter.embedding.engine.dart.DartExecutor;
14import io.flutter.plugin.common.JSONMethodCodec;
15import io.flutter.plugin.common.MethodCall;
16import io.flutter.plugin.common.MethodChannel;
17import io.flutter.plugin.editing.TextEditingDelta;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.HashMap;
24import org.json.JSONArray;
25import org.json.JSONException;
26import org.json.JSONObject;
45 private static final String TAG =
"TextInputChannel";
50 @NonNull @VisibleForTesting
55 if (textInputMethodHandler ==
null) {
61 String method =
call.method;
63 Log.
v(
TAG,
"Received '" + method +
"' message.");
65 case "TextInput.show":
66 textInputMethodHandler.show();
69 case "TextInput.hide":
70 textInputMethodHandler.hide();
73 case "TextInput.setClient":
75 final JSONArray argumentList = (JSONArray)
args;
76 final int textInputClientId = argumentList.getInt(0);
77 final JSONObject jsonConfiguration = argumentList.getJSONObject(1);
78 textInputMethodHandler.setClient(
81 }
catch (JSONException | NoSuchFieldException exception) {
84 result.error(
"error", exception.getMessage(),
null);
87 case "TextInput.requestAutofill":
88 textInputMethodHandler.requestAutofill();
91 case "TextInput.setPlatformViewClient":
93 final JSONObject arguments = (JSONObject)
args;
94 final int platformViewId = arguments.getInt(
"platformViewId");
95 final boolean usesVirtualDisplay =
96 arguments.optBoolean(
"usesVirtualDisplay",
false);
97 textInputMethodHandler.setPlatformViewClient(platformViewId, usesVirtualDisplay);
99 }
catch (JSONException exception) {
100 result.error(
"error", exception.getMessage(),
null);
103 case "TextInput.setEditingState":
105 final JSONObject editingState = (JSONObject)
args;
108 }
catch (JSONException exception) {
109 result.error(
"error", exception.getMessage(),
null);
112 case "TextInput.setEditableSizeAndTransform":
114 final JSONObject arguments = (JSONObject)
args;
115 final double width = arguments.getDouble(
"width");
116 final double height = arguments.getDouble(
"height");
117 final JSONArray jsonMatrix = arguments.getJSONArray(
"transform");
118 final double[]
matrix =
new double[16];
119 for (
int i = 0;
i < 16;
i++) {
120 matrix[
i] = jsonMatrix.getDouble(
i);
125 }
catch (JSONException exception) {
126 result.error(
"error", exception.getMessage(),
null);
129 case "TextInput.clearClient":
130 textInputMethodHandler.clearClient();
133 case "TextInput.sendAppPrivateCommand":
135 final JSONObject arguments = (JSONObject)
args;
136 final String
action = arguments.getString(
"action");
137 final String
data = arguments.getString(
"data");
138 Bundle bundle =
null;
139 if (
data !=
null && !
data.isEmpty()) {
140 bundle =
new Bundle();
141 bundle.putString(
"data",
data);
143 textInputMethodHandler.sendAppPrivateCommand(
action, bundle);
145 }
catch (JSONException exception) {
146 result.error(
"error", exception.getMessage(),
null);
149 case "TextInput.finishAutofillContext":
150 textInputMethodHandler.finishAutofillContext((
boolean)
args);
184 private static HashMap<Object, Object> createEditingStateJSON(
185 String
text,
int selectionStart,
int selectionEnd,
int composingStart,
int composingEnd) {
186 HashMap<Object, Object>
state =
new HashMap<>();
188 state.put(
"selectionBase", selectionStart);
189 state.put(
"selectionExtent", selectionEnd);
190 state.put(
"composingBase", composingStart);
191 state.put(
"composingExtent", composingEnd);
195 private static HashMap<Object, Object> createEditingDeltaJSON(
196 ArrayList<TextEditingDelta> batchDeltas) {
197 HashMap<Object, Object>
state =
new HashMap<>();
199 JSONArray deltas =
new JSONArray();
200 for (TextEditingDelta
delta : batchDeltas) {
201 deltas.put(
delta.toJSON());
203 state.put(
"deltas", deltas);
211 @NonNull String
text,
218 "Sending message to update editing state: \n"
222 +
"Selection start: "
228 +
"Composing start: "
234 final HashMap<Object, Object>
state =
235 createEditingStateJSON(
text, selectionStart, selectionEnd, composingStart, composingEnd);
241 int inputClientId, @NonNull ArrayList<TextEditingDelta> batchDeltas) {
245 "Sending message to update editing state with deltas: \n"
246 +
"Number of deltas: "
247 + batchDeltas.size());
249 final HashMap<Object, Object>
state = createEditingDeltaJSON(batchDeltas);
252 "TextInputClient.updateEditingStateWithDeltas", Arrays.asList(inputClientId,
state));
256 int inputClientId, @NonNull HashMap<String, TextEditState> editStates) {
259 "Sending message to update editing state for "
260 + String.valueOf(editStates.size())
263 final HashMap<String, HashMap<Object, Object>> json =
new HashMap<>();
264 for (Map.Entry<String,
TextEditState> element : editStates.entrySet()) {
268 createEditingStateJSON(
state.text,
state.selectionStart,
state.selectionEnd, -1, -1));
271 "TextInputClient.updateEditingStateWithTag", Arrays.asList(inputClientId, json));
276 Log.
v(
TAG,
"Sending 'newline' message.");
278 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.newline"));
282 public void go(
int inputClientId) {
283 Log.
v(
TAG,
"Sending 'go' message.");
285 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.go"));
290 Log.
v(
TAG,
"Sending 'search' message.");
292 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.search"));
296 public void send(
int inputClientId) {
297 Log.
v(
TAG,
"Sending 'send' message.");
299 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.send"));
303 public void done(
int inputClientId) {
304 Log.
v(
TAG,
"Sending 'done' message.");
306 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.done"));
310 public void next(
int inputClientId) {
311 Log.
v(
TAG,
"Sending 'next' message.");
313 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.next"));
318 Log.
v(
TAG,
"Sending 'previous' message.");
320 "TextInputClient.performAction", Arrays.asList(inputClientId,
"TextInputAction.previous"));
325 Log.
v(
TAG,
"Sending 'unspecified' message.");
327 "TextInputClient.performAction",
328 Arrays.asList(inputClientId,
"TextInputAction.unspecified"));
333 Log.
v(
TAG,
"Sending 'commitContent' message.");
335 "TextInputClient.performAction",
336 Arrays.asList(inputClientId,
"TextInputAction.commitContent",
content));
340 int inputClientId, @NonNull String
action, @NonNull Bundle
data) {
341 HashMap<Object, Object> json =
new HashMap<>();
342 json.put(
"action",
action);
344 HashMap<String, Object> dataMap =
new HashMap<>();
345 Set<String> keySet =
data.keySet();
346 for (String
key : keySet) {
348 if (
value instanceof
byte[]) {
350 }
else if (
value instanceof Byte) {
352 }
else if (
value instanceof
char[]) {
354 }
else if (
value instanceof Character) {
356 }
else if (
value instanceof CharSequence[]) {
357 dataMap.put(
key,
data.getCharSequenceArray(
key));
358 }
else if (
value instanceof CharSequence) {
360 }
else if (
value instanceof
float[]) {
362 }
else if (
value instanceof Float) {
366 json.put(
"data", dataMap);
369 "TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json));
377 this.textInputMethodHandler = textInputMethodHandler;
454 throws JSONException, NoSuchFieldException {
455 final String inputActionName = json.getString(
"inputAction");
456 if (inputActionName ==
null) {
457 throw new JSONException(
"Configuration JSON missing 'inputAction' property.");
460 if (!json.isNull(
"fields")) {
461 final JSONArray jsonFields = json.getJSONArray(
"fields");
463 for (
int i = 0;
i <
fields.length;
i++) {
467 final Integer
inputAction = inputActionFromTextInputAction(inputActionName);
472 json.isNull(
"contentCommitMimeTypes")
474 : json.getJSONArray(
"contentCommitMimeTypes");
482 json.optBoolean(
"obscureText"),
483 json.optBoolean(
"autocorrect",
true),
484 json.optBoolean(
"enableSuggestions"),
485 json.optBoolean(
"enableIMEPersonalizedLearning"),
486 json.optBoolean(
"enableDeltaModel"),
490 json.isNull(
"actionLabel") ?
null : json.getString(
"actionLabel"),
491 json.isNull(
"autofill") ?
null :
Autofill.
fromJson(json.getJSONObject(
"autofill")),
492 contentList.toArray(
new String[contentList.size()]),
497 private static Integer inputActionFromTextInputAction(@NonNull String
inputAction) {
499 case "TextInputAction.newline":
500 return EditorInfo.IME_ACTION_NONE;
501 case "TextInputAction.none":
502 return EditorInfo.IME_ACTION_NONE;
503 case "TextInputAction.unspecified":
504 return EditorInfo.IME_ACTION_UNSPECIFIED;
505 case "TextInputAction.done":
506 return EditorInfo.IME_ACTION_DONE;
507 case "TextInputAction.go":
508 return EditorInfo.IME_ACTION_GO;
509 case "TextInputAction.search":
510 return EditorInfo.IME_ACTION_SEARCH;
511 case "TextInputAction.send":
512 return EditorInfo.IME_ACTION_SEND;
513 case "TextInputAction.next":
514 return EditorInfo.IME_ACTION_NEXT;
515 case "TextInputAction.previous":
516 return EditorInfo.IME_ACTION_PREVIOUS;
519 return EditorInfo.IME_ACTION_UNSPECIFIED;
526 throws JSONException, NoSuchFieldException {
528 final JSONArray
hints = json.getJSONArray(
"hints");
529 final String
hintText = json.isNull(
"hintText") ? null : json.getString(
"hintText");
530 final JSONObject editingState = json.getJSONObject(
"editingValue");
531 final String[] autofillHints =
new String[
hints.length()];
533 for (
int i = 0;
i <
hints.length();
i++) {
534 autofillHints[
i] = translateAutofillHint(
hints.getString(
i));
546 private static String translateAutofillHint(@NonNull String hint) {
552 return "addressLocality";
554 return "addressRegion";
556 return "birthDateFull";
558 return "birthDateDay";
559 case "birthdayMonth":
560 return "birthDateMonth";
562 return "birthDateYear";
564 return "addressCountry";
565 case "creditCardExpirationDate":
566 return View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE;
567 case "creditCardExpirationDay":
568 return View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY;
569 case "creditCardExpirationMonth":
570 return View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH;
571 case "creditCardExpirationYear":
572 return View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR;
573 case "creditCardNumber":
574 return View.AUTOFILL_HINT_CREDIT_CARD_NUMBER;
575 case "creditCardSecurityCode":
576 return View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE;
578 return View.AUTOFILL_HINT_EMAIL_ADDRESS;
580 return "personFamilyName";
581 case "fullStreetAddress":
582 return "streetAddress";
586 return "personGivenName";
587 case "middleInitial":
588 return "personMiddleInitial";
590 return "personMiddleName";
594 return "personNamePrefix";
596 return "personNameSuffix";
598 return "newPassword";
600 return "newUsername";
604 return View.AUTOFILL_HINT_PASSWORD;
605 case "postalAddress":
606 return View.AUTOFILL_HINT_POSTAL_ADDRESS;
607 case "postalAddressExtended":
608 return "extendedAddress";
609 case "postalAddressExtendedPostalCode":
610 return "extendedPostalCode";
612 return View.AUTOFILL_HINT_POSTAL_CODE;
613 case "telephoneNumber":
614 return "phoneNumber";
615 case "telephoneNumberCountryCode":
616 return "phoneCountryCode";
617 case "telephoneNumberDevice":
618 return "phoneNumberDevice";
619 case "telephoneNumberNational":
620 return "phoneNational";
622 return View.AUTOFILL_HINT_USERNAME;
630 @NonNull String[]
hints,
636 this.editState = editingState;
690 throws JSONException, NoSuchFieldException {
693 json.optBoolean(
"signed",
false),
694 json.optBoolean(
"decimal",
false));
724 if (textInputType.encodedName.equals(encodedName)) {
725 return textInputType;
728 throw new NoSuchFieldException(
"No such TextInputType: " + encodedName);
731 @NonNull
private final String encodedName;
734 this.encodedName = encodedName;
743 NONE(
"TextCapitalization.none");
747 if (textCapitalization.encodedName.equals(encodedName)) {
748 return textCapitalization;
751 throw new NoSuchFieldException(
"No such TextCapitalization: " + encodedName);
754 @NonNull
private final String encodedName;
757 this.encodedName = encodedName;
766 textEditState.getString(
"text"),
767 textEditState.getInt(
"selectionBase"),
768 textEditState.getInt(
"selectionExtent"),
769 textEditState.getInt(
"composingBase"),
770 textEditState.getInt(
"composingExtent"));
773 @NonNull
public final String
text;
780 @NonNull String
text,
785 throws IndexOutOfBoundsException {
789 throw new IndexOutOfBoundsException(
790 "invalid selection: ("
798 && (composingStart < 0 || composingStart >
composingEnd)) {
799 throw new IndexOutOfBoundsException(
800 "invalid composing range: ("
808 throw new IndexOutOfBoundsException(
813 throw new IndexOutOfBoundsException(
818 throw new IndexOutOfBoundsException(
819 "invalid selection end: " + String.valueOf(
selectionEnd));
static void v(@NonNull String tag, @NonNull String message)
final String uniqueIdentifier
Autofill( @NonNull String uniqueIdentifier, @NonNull String[] hints, @Nullable String hintText, @NonNull TextEditState editingState)
static Autofill fromJson(@NonNull JSONObject json)
final TextEditState editState
final boolean enableIMEPersonalizedLearning
final InputType inputType
Configuration(boolean obscureText, boolean autocorrect, boolean enableSuggestions, boolean enableIMEPersonalizedLearning, boolean enableDeltaModel, @NonNull TextCapitalization textCapitalization, @NonNull InputType inputType, @Nullable Integer inputAction, @Nullable String actionLabel, @Nullable Autofill autofill, @Nullable String[] contentCommitMimeTypes, @Nullable Configuration[] fields)
final String[] contentCommitMimeTypes
static Configuration fromJson(@NonNull JSONObject json)
final Configuration[] fields
final boolean autocorrect
final boolean enableSuggestions
final Integer inputAction
final boolean enableDeltaModel
final TextCapitalization textCapitalization
final boolean obscureText
InputType(@NonNull TextInputType type, boolean isSigned, boolean isDecimal)
static InputType fromJson(@NonNull JSONObject json)
TextEditState( @NonNull String text, int selectionStart, int selectionEnd, int composingStart, int composingEnd)
static TextEditState fromJson(@NonNull JSONObject textEditState)
void commitContent(int inputClientId, Map< String, Object > content)
final MethodChannel.MethodCallHandler parsingMethodHandler
void next(int inputClientId)
void updateEditingStateWithTag(int inputClientId, @NonNull HashMap< String, TextEditState > editStates)
void unspecifiedAction(int inputClientId)
void newline(int inputClientId)
void go(int inputClientId)
void updateEditingState(int inputClientId, @NonNull String text, int selectionStart, int selectionEnd, int composingStart, int composingEnd)
void send(int inputClientId)
void search(int inputClientId)
TextInputChannel(@NonNull DartExecutor dartExecutor)
void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler)
final MethodChannel channel
void done(int inputClientId)
void performPrivateCommand(int inputClientId, @NonNull String action, @NonNull Bundle data)
void requestExistingInputState()
void updateEditingStateWithDeltas(int inputClientId, @NonNull ArrayList< TextEditingDelta > batchDeltas)
void previous(int inputClientId)
static final JSONMethodCodec INSTANCE
void setMethodCallHandler(final @Nullable MethodCallHandler handler)
void invokeMethod(@NonNull String method, @Nullable Object arguments)
TextCapitalization(@NonNull String encodedName)
static TextCapitalization fromValue(@NonNull String encodedName)
TextInputType(@NonNull String encodedName)
static TextInputType fromValue(@NonNull String encodedName)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
void sendAppPrivateCommand(@NonNull String action, @NonNull Bundle data)
void setEditingState(@NonNull TextEditState editingState)
void setPlatformViewClient(int id, boolean usesVirtualDisplay)
void setClient(int textInputClientId, @NonNull Configuration configuration)
void finishAutofillContext(boolean shouldSave)
void setEditableSizeAndTransform(double width, double height, @NonNull double[] transform)
union flutter::testing::@2836::KeyboardChange::@76 content
unsigned useCenter Optional< SkMatrix > matrix
def Build(configs, env, options)
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
std::shared_ptr< const fml::Mapping > data