Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
TextInputChannel.java
Go to the documentation of this file.
1package io.flutter.embedding.engine.systemchannels;
2
3import static io.flutter.Build.API_LEVELS;
4
5import android.os.Build;
6import android.os.Bundle;
7import android.view.View;
8import android.view.inputmethod.EditorInfo;
9import androidx.annotation.NonNull;
10import androidx.annotation.Nullable;
11import androidx.annotation.VisibleForTesting;
12import io.flutter.Log;
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;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24import org.json.JSONArray;
25import org.json.JSONException;
26import org.json.JSONObject;
27
28/**
29 * {@link TextInputChannel} is a platform channel between Android and Flutter that is used to
30 * communicate information about the user's text input.
31 *
32 * <p>When the user presses an action button like "done" or "next", that action is sent from Android
33 * to Flutter through this {@link TextInputChannel}.
34 *
35 * <p>When an input system in the Flutter app wants to show the keyboard, or hide it, or configure
36 * editing state, etc. a message is sent from Flutter to Android through this {@link
37 * TextInputChannel}.
38 *
39 * <p>{@link TextInputChannel} comes with a default {@link
40 * io.flutter.plugin.common.MethodChannel.MethodCallHandler} that parses incoming messages from
41 * Flutter. Register a {@link TextInputMethodHandler} to respond to standard Flutter text input
42 * messages.
43 */
44public class TextInputChannel {
45 private static final String TAG = "TextInputChannel";
46
47 @NonNull public final MethodChannel channel;
48 @Nullable private TextInputMethodHandler textInputMethodHandler;
49
50 @NonNull @VisibleForTesting
51 final MethodChannel.MethodCallHandler parsingMethodHandler =
52 new MethodChannel.MethodCallHandler() {
53 @Override
54 public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
55 if (textInputMethodHandler == null) {
56 // If no explicit TextInputMethodHandler has been registered then we don't
57 // need to forward this call to an API. Return.
58 return;
59 }
60
61 String method = call.method;
62 Object args = call.arguments;
63 Log.v(TAG, "Received '" + method + "' message.");
64 switch (method) {
65 case "TextInput.show":
66 textInputMethodHandler.show();
67 result.success(null);
68 break;
69 case "TextInput.hide":
70 textInputMethodHandler.hide();
71 result.success(null);
72 break;
73 case "TextInput.setClient":
74 try {
75 final JSONArray argumentList = (JSONArray) args;
76 final int textInputClientId = argumentList.getInt(0);
77 final JSONObject jsonConfiguration = argumentList.getJSONObject(1);
78 textInputMethodHandler.setClient(
79 textInputClientId, Configuration.fromJson(jsonConfiguration));
80 result.success(null);
81 } catch (JSONException | NoSuchFieldException exception) {
82 // JSONException: missing keys or bad value types.
83 // NoSuchFieldException: one or more values were invalid.
84 result.error("error", exception.getMessage(), null);
85 }
86 break;
87 case "TextInput.requestAutofill":
88 textInputMethodHandler.requestAutofill();
89 result.success(null);
90 break;
91 case "TextInput.setPlatformViewClient":
92 try {
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);
98 result.success(null);
99 } catch (JSONException exception) {
100 result.error("error", exception.getMessage(), null);
101 }
102 break;
103 case "TextInput.setEditingState":
104 try {
105 final JSONObject editingState = (JSONObject) args;
106 textInputMethodHandler.setEditingState(TextEditState.fromJson(editingState));
107 result.success(null);
108 } catch (JSONException exception) {
109 result.error("error", exception.getMessage(), null);
110 }
111 break;
112 case "TextInput.setEditableSizeAndTransform":
113 try {
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);
121 }
122
123 textInputMethodHandler.setEditableSizeAndTransform(width, height, matrix);
124 result.success(null);
125 } catch (JSONException exception) {
126 result.error("error", exception.getMessage(), null);
127 }
128 break;
129 case "TextInput.clearClient":
130 textInputMethodHandler.clearClient();
131 result.success(null);
132 break;
133 case "TextInput.sendAppPrivateCommand":
134 try {
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);
142 }
143 textInputMethodHandler.sendAppPrivateCommand(action, bundle);
144 result.success(null);
145 } catch (JSONException exception) {
146 result.error("error", exception.getMessage(), null);
147 }
148 break;
149 case "TextInput.finishAutofillContext":
150 textInputMethodHandler.finishAutofillContext((boolean) args);
151 result.success(null);
152 break;
153 default:
154 result.notImplemented();
155 break;
156 }
157 }
158 };
159
160 /**
161 * Constructs a {@code TextInputChannel} that connects Android to the Dart code running in {@code
162 * dartExecutor}.
163 *
164 * <p>The given {@code dartExecutor} is permitted to be idle or executing code.
165 *
166 * <p>See {@link DartExecutor}.
167 */
168 public TextInputChannel(@NonNull DartExecutor dartExecutor) {
169 this.channel = new MethodChannel(dartExecutor, "flutter/textinput", JSONMethodCodec.INSTANCE);
170 channel.setMethodCallHandler(parsingMethodHandler);
171 }
172
173 /**
174 * Instructs Flutter to reattach the last active text input client, if any.
175 *
176 * <p>This is necessary when the view hierarchy has been detached and reattached to a {@link
177 * io.flutter.embedding.engine.FlutterEngine}, as the engine may have kept alive a text editing
178 * client on the Dart side.
179 */
181 channel.invokeMethod("TextInputClient.requestExistingInputState", null);
182 }
183
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<>();
187 state.put("text", text);
188 state.put("selectionBase", selectionStart);
189 state.put("selectionExtent", selectionEnd);
190 state.put("composingBase", composingStart);
191 state.put("composingExtent", composingEnd);
192 return state;
193 }
194
195 private static HashMap<Object, Object> createEditingDeltaJSON(
196 ArrayList<TextEditingDelta> batchDeltas) {
197 HashMap<Object, Object> state = new HashMap<>();
198
199 JSONArray deltas = new JSONArray();
200 for (TextEditingDelta delta : batchDeltas) {
201 deltas.put(delta.toJSON());
202 }
203 state.put("deltas", deltas);
204 return state;
205 }
206 /**
207 * Instructs Flutter to update its text input editing state to reflect the given configuration.
208 */
210 int inputClientId,
211 @NonNull String text,
212 int selectionStart,
213 int selectionEnd,
214 int composingStart,
215 int composingEnd) {
216 Log.v(
217 TAG,
218 "Sending message to update editing state: \n"
219 + "Text: "
220 + text
221 + "\n"
222 + "Selection start: "
223 + selectionStart
224 + "\n"
225 + "Selection end: "
226 + selectionEnd
227 + "\n"
228 + "Composing start: "
229 + composingStart
230 + "\n"
231 + "Composing end: "
232 + composingEnd);
233
234 final HashMap<Object, Object> state =
235 createEditingStateJSON(text, selectionStart, selectionEnd, composingStart, composingEnd);
236
237 channel.invokeMethod("TextInputClient.updateEditingState", Arrays.asList(inputClientId, state));
238 }
239
241 int inputClientId, @NonNull ArrayList<TextEditingDelta> batchDeltas) {
242
243 Log.v(
244 TAG,
245 "Sending message to update editing state with deltas: \n"
246 + "Number of deltas: "
247 + batchDeltas.size());
248
249 final HashMap<Object, Object> state = createEditingDeltaJSON(batchDeltas);
250
251 channel.invokeMethod(
252 "TextInputClient.updateEditingStateWithDeltas", Arrays.asList(inputClientId, state));
253 }
254
256 int inputClientId, @NonNull HashMap<String, TextEditState> editStates) {
257 Log.v(
258 TAG,
259 "Sending message to update editing state for "
260 + String.valueOf(editStates.size())
261 + " field(s).");
262
263 final HashMap<String, HashMap<Object, Object>> json = new HashMap<>();
264 for (Map.Entry<String, TextEditState> element : editStates.entrySet()) {
265 final TextEditState state = element.getValue();
266 json.put(
267 element.getKey(),
268 createEditingStateJSON(state.text, state.selectionStart, state.selectionEnd, -1, -1));
269 }
270 channel.invokeMethod(
271 "TextInputClient.updateEditingStateWithTag", Arrays.asList(inputClientId, json));
272 }
273
274 /** Instructs Flutter to execute a "newline" action. */
275 public void newline(int inputClientId) {
276 Log.v(TAG, "Sending 'newline' message.");
277 channel.invokeMethod(
278 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.newline"));
279 }
280
281 /** Instructs Flutter to execute a "go" action. */
282 public void go(int inputClientId) {
283 Log.v(TAG, "Sending 'go' message.");
284 channel.invokeMethod(
285 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.go"));
286 }
287
288 /** Instructs Flutter to execute a "search" action. */
289 public void search(int inputClientId) {
290 Log.v(TAG, "Sending 'search' message.");
291 channel.invokeMethod(
292 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.search"));
293 }
294
295 /** Instructs Flutter to execute a "send" action. */
296 public void send(int inputClientId) {
297 Log.v(TAG, "Sending 'send' message.");
298 channel.invokeMethod(
299 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.send"));
300 }
301
302 /** Instructs Flutter to execute a "done" action. */
303 public void done(int inputClientId) {
304 Log.v(TAG, "Sending 'done' message.");
305 channel.invokeMethod(
306 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.done"));
307 }
308
309 /** Instructs Flutter to execute a "next" action. */
310 public void next(int inputClientId) {
311 Log.v(TAG, "Sending 'next' message.");
312 channel.invokeMethod(
313 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.next"));
314 }
315
316 /** Instructs Flutter to execute a "previous" action. */
317 public void previous(int inputClientId) {
318 Log.v(TAG, "Sending 'previous' message.");
319 channel.invokeMethod(
320 "TextInputClient.performAction", Arrays.asList(inputClientId, "TextInputAction.previous"));
321 }
322
323 /** Instructs Flutter to execute an "unspecified" action. */
324 public void unspecifiedAction(int inputClientId) {
325 Log.v(TAG, "Sending 'unspecified' message.");
326 channel.invokeMethod(
327 "TextInputClient.performAction",
328 Arrays.asList(inputClientId, "TextInputAction.unspecified"));
329 }
330
331 /** Instructs Flutter to commit inserted content back to the text channel. */
332 public void commitContent(int inputClientId, Map<String, Object> content) {
333 Log.v(TAG, "Sending 'commitContent' message.");
334 channel.invokeMethod(
335 "TextInputClient.performAction",
336 Arrays.asList(inputClientId, "TextInputAction.commitContent", content));
337 }
338
340 int inputClientId, @NonNull String action, @NonNull Bundle data) {
341 HashMap<Object, Object> json = new HashMap<>();
342 json.put("action", action);
343 if (data != null) {
344 HashMap<String, Object> dataMap = new HashMap<>();
345 Set<String> keySet = data.keySet();
346 for (String key : keySet) {
347 Object value = data.get(key);
348 if (value instanceof byte[]) {
349 dataMap.put(key, data.getByteArray(key));
350 } else if (value instanceof Byte) {
351 dataMap.put(key, data.getByte(key));
352 } else if (value instanceof char[]) {
353 dataMap.put(key, data.getCharArray(key));
354 } else if (value instanceof Character) {
355 dataMap.put(key, data.getChar(key));
356 } else if (value instanceof CharSequence[]) {
357 dataMap.put(key, data.getCharSequenceArray(key));
358 } else if (value instanceof CharSequence) {
359 dataMap.put(key, data.getCharSequence(key));
360 } else if (value instanceof float[]) {
361 dataMap.put(key, data.getFloatArray(key));
362 } else if (value instanceof Float) {
363 dataMap.put(key, data.getFloat(key));
364 }
365 }
366 json.put("data", dataMap);
367 }
368 channel.invokeMethod(
369 "TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json));
370 }
371
372 /**
373 * Sets the {@link TextInputMethodHandler} which receives all events and requests that are parsed
374 * from the underlying platform channel.
375 */
376 public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler) {
377 this.textInputMethodHandler = textInputMethodHandler;
378 }
379
380 public interface TextInputMethodHandler {
381 // TODO(mattcarroll): javadoc
382 void show();
383
384 // TODO(mattcarroll): javadoc
385 void hide();
386
387 /**
388 * Requests that the autofill dropdown menu appear for the current client.
389 *
390 * <p>Has no effect if the current client does not support autofill.
391 */
393
394 /**
395 * Requests that the {@link android.view.autofill.AutofillManager} cancel or commit the current
396 * autofill context.
397 *
398 * <p>The method calls {@link android.view.autofill.AutofillManager#commit()} when {@code
399 * shouldSave} is true, and calls {@link android.view.autofill.AutofillManager#cancel()}
400 * otherwise.
401 *
402 * @param shouldSave whether the active autofill service should save the current user input for
403 * future use.
404 */
405 void finishAutofillContext(boolean shouldSave);
406
407 // TODO(mattcarroll): javadoc
408 void setClient(int textInputClientId, @NonNull Configuration configuration);
409
410 /**
411 * Sets a platform view as the text input client.
412 *
413 * <p>Subsequent calls to createInputConnection will be delegated to the platform view until a
414 * different client is set.
415 *
416 * @param id the ID of the platform view to be set as a text input client.
417 * @param usesVirtualDisplay True if the platform view uses a virtual display, false if it uses
418 * hybrid composition.
419 */
420 void setPlatformViewClient(int id, boolean usesVirtualDisplay);
421
422 /**
423 * Sets the size and the transform matrix of the current text input client.
424 *
425 * @param width the width of text input client. Must be finite.
426 * @param height the height of text input client. Must be finite.
427 * @param transform a 4x4 matrix that maps the local paint coordinate system to coordinate
428 * system of the FlutterView that owns the current client.
429 */
430 void setEditableSizeAndTransform(double width, double height, @NonNull double[] transform);
431
432 // TODO(mattcarroll): javadoc
433 void setEditingState(@NonNull TextEditState editingState);
434
435 // TODO(mattcarroll): javadoc
437
438 /**
439 * Sends client app private command to the current text input client(input method). The app
440 * private command result will be informed through {@code performPrivateCommand}.
441 *
442 * @param action Name of the command to be performed. This must be a scoped name. i.e. prefixed
443 * with a package name you own, so that different developers will not create conflicting
444 * commands.
445 * @param data Any data to include with the command.
446 */
447 void sendAppPrivateCommand(@NonNull String action, @NonNull Bundle data);
448 }
449
450 /** A text editing configuration. */
451 public static class Configuration {
452 @NonNull
453 public static Configuration fromJson(@NonNull JSONObject json)
454 throws JSONException, NoSuchFieldException {
455 final String inputActionName = json.getString("inputAction");
456 if (inputActionName == null) {
457 throw new JSONException("Configuration JSON missing 'inputAction' property.");
458 }
459 Configuration[] fields = null;
460 if (!json.isNull("fields")) {
461 final JSONArray jsonFields = json.getJSONArray("fields");
462 fields = new Configuration[jsonFields.length()];
463 for (int i = 0; i < fields.length; i++) {
464 fields[i] = Configuration.fromJson(jsonFields.getJSONObject(i));
465 }
466 }
467 final Integer inputAction = inputActionFromTextInputAction(inputActionName);
468
469 // Build list of content commit mime types from the data in the JSON list.
470 List<String> contentList = new ArrayList<String>();
471 JSONArray contentCommitMimeTypes =
472 json.isNull("contentCommitMimeTypes")
473 ? null
474 : json.getJSONArray("contentCommitMimeTypes");
475 if (contentCommitMimeTypes != null) {
476 for (int i = 0; i < contentCommitMimeTypes.length(); i++) {
477 contentList.add(contentCommitMimeTypes.optString(i));
478 }
479 }
480
481 return new Configuration(
482 json.optBoolean("obscureText"),
483 json.optBoolean("autocorrect", true),
484 json.optBoolean("enableSuggestions"),
485 json.optBoolean("enableIMEPersonalizedLearning"),
486 json.optBoolean("enableDeltaModel"),
487 TextCapitalization.fromValue(json.getString("textCapitalization")),
488 InputType.fromJson(json.getJSONObject("inputType")),
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()]),
493 fields);
494 }
495
496 @NonNull
497 private static Integer inputActionFromTextInputAction(@NonNull String inputAction) {
498 switch (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;
517 default:
518 // Present default key if bad input type is given.
519 return EditorInfo.IME_ACTION_UNSPECIFIED;
520 }
521 }
522
523 public static class Autofill {
524 @NonNull
525 public static Autofill fromJson(@NonNull JSONObject json)
526 throws JSONException, NoSuchFieldException {
527 final String uniqueIdentifier = json.getString("uniqueIdentifier");
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()];
532
533 for (int i = 0; i < hints.length(); i++) {
534 autofillHints[i] = translateAutofillHint(hints.getString(i));
535 }
536 return new Autofill(
537 uniqueIdentifier, autofillHints, hintText, TextEditState.fromJson(editingState));
538 }
539
540 public final String uniqueIdentifier;
541 public final String[] hints;
543 public final String hintText;
544
545 @NonNull
546 private static String translateAutofillHint(@NonNull String hint) {
547 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
548 return hint;
549 }
550 switch (hint) {
551 case "addressCity":
552 return "addressLocality";
553 case "addressState":
554 return "addressRegion";
555 case "birthday":
556 return "birthDateFull";
557 case "birthdayDay":
558 return "birthDateDay";
559 case "birthdayMonth":
560 return "birthDateMonth";
561 case "birthdayYear":
562 return "birthDateYear";
563 case "countryName":
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;
577 case "email":
578 return View.AUTOFILL_HINT_EMAIL_ADDRESS;
579 case "familyName":
580 return "personFamilyName";
581 case "fullStreetAddress":
582 return "streetAddress";
583 case "gender":
584 return "gender";
585 case "givenName":
586 return "personGivenName";
587 case "middleInitial":
588 return "personMiddleInitial";
589 case "middleName":
590 return "personMiddleName";
591 case "name":
592 return "personName";
593 case "namePrefix":
594 return "personNamePrefix";
595 case "nameSuffix":
596 return "personNameSuffix";
597 case "newPassword":
598 return "newPassword";
599 case "newUsername":
600 return "newUsername";
601 case "oneTimeCode":
602 return "smsOTPCode";
603 case "password":
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";
611 case "postalCode":
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";
621 case "username":
622 return View.AUTOFILL_HINT_USERNAME;
623 default:
624 return hint;
625 }
626 }
627
628 public Autofill(
629 @NonNull String uniqueIdentifier,
630 @NonNull String[] hints,
631 @Nullable String hintText,
632 @NonNull TextEditState editingState) {
633 this.uniqueIdentifier = uniqueIdentifier;
634 this.hints = hints;
635 this.hintText = hintText;
636 this.editState = editingState;
637 }
638 }
639
640 public final boolean obscureText;
641 public final boolean autocorrect;
642 public final boolean enableSuggestions;
643 public final boolean enableIMEPersonalizedLearning;
644 public final boolean enableDeltaModel;
646 @NonNull public final InputType inputType;
647 @Nullable public final Integer inputAction;
648 @Nullable public final String actionLabel;
649 @Nullable public final Autofill autofill;
650 @Nullable public final String[] contentCommitMimeTypes;
651 @Nullable public final Configuration[] fields;
652
654 boolean obscureText,
655 boolean autocorrect,
656 boolean enableSuggestions,
658 boolean enableDeltaModel,
660 @NonNull InputType inputType,
661 @Nullable Integer inputAction,
662 @Nullable String actionLabel,
663 @Nullable Autofill autofill,
664 @Nullable String[] contentCommitMimeTypes,
665 @Nullable Configuration[] fields) {
666 this.obscureText = obscureText;
667 this.autocorrect = autocorrect;
668 this.enableSuggestions = enableSuggestions;
669 this.enableIMEPersonalizedLearning = enableIMEPersonalizedLearning;
670 this.enableDeltaModel = enableDeltaModel;
671 this.textCapitalization = textCapitalization;
672 this.inputType = inputType;
673 this.inputAction = inputAction;
674 this.actionLabel = actionLabel;
675 this.autofill = autofill;
676 this.contentCommitMimeTypes = contentCommitMimeTypes;
677 this.fields = fields;
678 }
679 }
680
681 /**
682 * A text input type.
683 *
684 * <p>If the {@link #type} is {@link TextInputType#NUMBER}, this {@code InputType} also reports
685 * whether that number {@link #isSigned} and {@link #isDecimal}.
686 */
687 public static class InputType {
688 @NonNull
689 public static InputType fromJson(@NonNull JSONObject json)
690 throws JSONException, NoSuchFieldException {
691 return new InputType(
692 TextInputType.fromValue(json.getString("name")),
693 json.optBoolean("signed", false),
694 json.optBoolean("decimal", false));
695 }
696
697 @NonNull public final TextInputType type;
698 public final boolean isSigned;
699 public final boolean isDecimal;
700
701 public InputType(@NonNull TextInputType type, boolean isSigned, boolean isDecimal) {
702 this.type = type;
703 this.isSigned = isSigned;
704 this.isDecimal = isDecimal;
705 }
706 }
707
708 /** Types of text input. */
709 public enum TextInputType {
710 TEXT("TextInputType.text"),
711 DATETIME("TextInputType.datetime"),
712 NAME("TextInputType.name"),
713 POSTAL_ADDRESS("TextInputType.address"),
714 NUMBER("TextInputType.number"),
715 PHONE("TextInputType.phone"),
716 MULTILINE("TextInputType.multiline"),
717 EMAIL_ADDRESS("TextInputType.emailAddress"),
718 URL("TextInputType.url"),
719 VISIBLE_PASSWORD("TextInputType.visiblePassword"),
720 NONE("TextInputType.none");
721
722 static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldException {
723 for (TextInputType textInputType : TextInputType.values()) {
724 if (textInputType.encodedName.equals(encodedName)) {
725 return textInputType;
726 }
727 }
728 throw new NoSuchFieldException("No such TextInputType: " + encodedName);
729 }
730
731 @NonNull private final String encodedName;
732
733 TextInputType(@NonNull String encodedName) {
734 this.encodedName = encodedName;
735 }
736 }
737
738 /** Text capitalization schemes. */
739 public enum TextCapitalization {
740 CHARACTERS("TextCapitalization.characters"),
741 WORDS("TextCapitalization.words"),
742 SENTENCES("TextCapitalization.sentences"),
743 NONE("TextCapitalization.none");
744
745 static TextCapitalization fromValue(@NonNull String encodedName) throws NoSuchFieldException {
746 for (TextCapitalization textCapitalization : TextCapitalization.values()) {
747 if (textCapitalization.encodedName.equals(encodedName)) {
748 return textCapitalization;
749 }
750 }
751 throw new NoSuchFieldException("No such TextCapitalization: " + encodedName);
752 }
753
754 @NonNull private final String encodedName;
755
756 TextCapitalization(@NonNull String encodedName) {
757 this.encodedName = encodedName;
758 }
759 }
760
761 /** State of an on-going text editing session. */
762 public static class TextEditState {
763 @NonNull
764 public static TextEditState fromJson(@NonNull JSONObject textEditState) throws JSONException {
765 return new TextEditState(
766 textEditState.getString("text"),
767 textEditState.getInt("selectionBase"),
768 textEditState.getInt("selectionExtent"),
769 textEditState.getInt("composingBase"),
770 textEditState.getInt("composingExtent"));
771 }
772
773 @NonNull public final String text;
774 public final int selectionStart;
775 public final int selectionEnd;
776 public final int composingStart;
777 public final int composingEnd;
778
780 @NonNull String text,
781 int selectionStart,
782 int selectionEnd,
783 int composingStart,
784 int composingEnd)
785 throws IndexOutOfBoundsException {
786
787 if ((selectionStart != -1 || selectionEnd != -1)
788 && (selectionStart < 0 || selectionEnd < 0)) {
789 throw new IndexOutOfBoundsException(
790 "invalid selection: ("
791 + String.valueOf(selectionStart)
792 + ", "
793 + String.valueOf(selectionEnd)
794 + ")");
795 }
796
797 if ((composingStart != -1 || composingEnd != -1)
798 && (composingStart < 0 || composingStart > composingEnd)) {
799 throw new IndexOutOfBoundsException(
800 "invalid composing range: ("
801 + String.valueOf(composingStart)
802 + ", "
803 + String.valueOf(composingEnd)
804 + ")");
805 }
806
807 if (composingEnd > text.length()) {
808 throw new IndexOutOfBoundsException(
809 "invalid composing start: " + String.valueOf(composingStart));
810 }
811
812 if (selectionStart > text.length()) {
813 throw new IndexOutOfBoundsException(
814 "invalid selection start: " + String.valueOf(selectionStart));
815 }
816
817 if (selectionEnd > text.length()) {
818 throw new IndexOutOfBoundsException(
819 "invalid selection end: " + String.valueOf(selectionEnd));
820 }
821
822 this.text = text;
823 this.selectionStart = selectionStart;
824 this.selectionEnd = selectionEnd;
825 this.composingStart = composingStart;
826 this.composingEnd = composingEnd;
827 }
828
829 public boolean hasSelection() {
830 // When selectionStart == -1, it's guaranteed that selectionEnd will also
831 // be -1.
832 return selectionStart >= 0;
833 }
834
835 public boolean hasComposing() {
837 }
838 }
839}
void add(sk_sp< SkIDChangeListener > listener) SK_EXCLUDES(fMutex)
static void v(@NonNull String tag, @NonNull String message)
Definition Log.java:40
Autofill( @NonNull String uniqueIdentifier, @NonNull String[] hints, @Nullable String hintText, @NonNull TextEditState editingState)
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)
InputType(@NonNull TextInputType type, boolean isSigned, boolean isDecimal)
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)
void updateEditingStateWithTag(int inputClientId, @NonNull HashMap< String, TextEditState > editStates)
void updateEditingState(int inputClientId, @NonNull String text, int selectionStart, int selectionEnd, int composingStart, int composingEnd)
void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler)
void performPrivateCommand(int inputClientId, @NonNull String action, @NonNull Bundle data)
void updateEditingStateWithDeltas(int inputClientId, @NonNull ArrayList< TextEditingDelta > batchDeltas)
AtkStateType state
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
static ::testing::Matcher< GBytes * > MethodCall(const std::string &name, ::testing::Matcher< FlValue * > args)
uint8_t value
GAsyncResult * result
void sendAppPrivateCommand(@NonNull String action, @NonNull Bundle data)
void setClient(int textInputClientId, @NonNull Configuration configuration)
void setEditableSizeAndTransform(double width, double height, @NonNull double[] transform)
std::u16string text
union flutter::testing::@2838::KeyboardChange::@76 content
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
#define TAG()
int32_t height
int32_t width