diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter
index 39c7d86010..cea4f342c6 100644
--- a/engine/src/flutter/ci/licenses_golden/licenses_flutter
+++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter
@@ -446,7 +446,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/D
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java
-FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
@@ -454,7 +453,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.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
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java
diff --git a/engine/src/flutter/shell/platform/android/BUILD.gn b/engine/src/flutter/shell/platform/android/BUILD.gn
index ea6ee24397..b6560fca38 100644
--- a/engine/src/flutter/shell/platform/android/BUILD.gn
+++ b/engine/src/flutter/shell/platform/android/BUILD.gn
@@ -114,7 +114,6 @@ java_library("flutter_shell_java") {
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
"io/flutter/embedding/engine/renderer/FlutterRenderer.java",
"io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java",
- "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java",
"io/flutter/embedding/engine/systemchannels/KeyEventChannel.java",
"io/flutter/embedding/engine/systemchannels/LifecycleChannel.java",
"io/flutter/embedding/engine/systemchannels/LocalizationChannel.java",
@@ -122,7 +121,6 @@ java_library("flutter_shell_java") {
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
"io/flutter/embedding/engine/systemchannels/SettingsChannel.java",
"io/flutter/embedding/engine/systemchannels/SystemChannel.java",
- "io/flutter/embedding/engine/systemchannels/TextInputChannel.java",
"io/flutter/plugin/common/ActivityLifecycleListener.java",
"io/flutter/plugin/common/BasicMessageChannel.java",
"io/flutter/plugin/common/BinaryCodec.java",
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
deleted file mode 100644
index 4bfcd37973..0000000000
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package io.flutter.embedding.engine.systemchannels;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.HashMap;
-
-import io.flutter.embedding.engine.dart.DartExecutor;
-import io.flutter.plugin.common.BasicMessageChannel;
-import io.flutter.plugin.common.StandardMessageCodec;
-
-/**
- * System channel that sends accessibility requests and events from Flutter to Android.
- *
- * See {@link AccessibilityMessageHandler}, which lists all accessibility requests and
- * events that might be sent from Flutter to the Android platform.
- */
-public class AccessibilityChannel {
- @NonNull
- public BasicMessageChannel channel;
- @Nullable
- private AccessibilityMessageHandler handler;
-
- private final BasicMessageChannel.MessageHandler parsingMessageHandler = new BasicMessageChannel.MessageHandler() {
- @Override
- public void onMessage(Object message, BasicMessageChannel.Reply reply) {
- // If there is no handler to respond to this message then we don't need to
- // parse it. Return.
- if (handler == null) {
- return;
- }
-
- @SuppressWarnings("unchecked")
- final HashMap annotatedEvent = (HashMap) message;
- final String type = (String) annotatedEvent.get("type");
- @SuppressWarnings("unchecked")
- final HashMap data = (HashMap) annotatedEvent.get("data");
-
- switch (type) {
- case "announce":
- String announceMessage = (String) data.get("message");
- if (announceMessage != null) {
- handler.announce(announceMessage);
- }
- break;
- case "tap": {
- Integer nodeId = (Integer) annotatedEvent.get("nodeId");
- if (nodeId != null) {
- handler.onTap(nodeId);
- }
- break;
- }
- case "longPress": {
- Integer nodeId = (Integer) annotatedEvent.get("nodeId");
- if (nodeId != null) {
- handler.onLongPress(nodeId);
- }
- break;
- }
- case "tooltip": {
- String tooltipMessage = (String) data.get("message");
- if (tooltipMessage != null) {
- handler.onTooltip(tooltipMessage);
- }
- break;
- }
- }
- }
- };
-
- /**
- * Constructs an {@code AccessibilityChannel} that connects Android to the Dart code
- * running in {@code dartExecutor}.
- *
- * The given {@code dartExecutor} is permitted to be idle or executing code.
- *
- * See {@link DartExecutor}.
- */
- public AccessibilityChannel(@NonNull DartExecutor dartExecutor) {
- channel = new BasicMessageChannel<>(dartExecutor, "flutter/accessibility", StandardMessageCodec.INSTANCE);
- channel.setMessageHandler(parsingMessageHandler);
- }
-
- /**
- * Sets the {@link AccessibilityMessageHandler} which receives all events and requests
- * that are parsed from the underlying accessibility channel.
- */
- public void setAccessibilityMessageHandler(@Nullable AccessibilityMessageHandler handler) {
- this.handler = handler;
- }
-
- /**
- * Handler that receives accessibility messages sent from Flutter to Android
- * through a given {@link AccessibilityChannel}.
- *
- * To register an {@code AccessibilityMessageHandler} with a {@link AccessibilityChannel},
- * see {@link AccessibilityChannel#setAccessibilityMessageHandler(AccessibilityMessageHandler)}.
- */
- public interface AccessibilityMessageHandler {
- /**
- * The Dart application would like the given {@code message} to be announced.
- */
- void announce(@NonNull String message);
-
- /**
- * The user has tapped on the artifact with the given {@code nodeId}.
- */
- void onTap(int nodeId);
-
- /**
- * The user has long pressed on the artifact with the given {@code nodeId}.
- */
- void onLongPress(int nodeId);
-
- /**
- * The user has opened a popup window, menu, dialog, etc.
- */
- void onTooltip(@NonNull String message);
- }
-}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
index 52e4fbb461..ffb8f1f84a 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
@@ -6,17 +6,14 @@ package io.flutter.embedding.engine.systemchannels;
import android.support.annotation.NonNull;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.JSONMethodCodec;
import io.flutter.plugin.common.MethodChannel;
/**
- * Sends the platform's locales to Dart.
+ * TODO(mattcarroll): fill in javadoc for LocalizationChannel.
*/
public class LocalizationChannel {
@@ -27,18 +24,12 @@ public class LocalizationChannel {
this.channel = new MethodChannel(dartExecutor, "flutter/localization", JSONMethodCodec.INSTANCE);
}
- /**
- * Send the given {@code locales} to Dart.
- */
- public void sendLocales(List locales) {
- List data = new ArrayList<>();
- for (Locale locale : locales) {
- data.add(locale.getLanguage());
- data.add(locale.getCountry());
- data.add(locale.getScript());
- data.add(locale.getVariant());
- }
- channel.invokeMethod("setLocale", data);
+ public void setLocale(String language, String country) {
+ channel.invokeMethod("setLocale", Arrays.asList(language, country));
+ }
+
+ public void setMethodCallHandler(MethodChannel.MethodCallHandler handler) {
+ channel.setMethodCallHandler(handler);
}
}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
index 44269313bb..b1ceb82b7a 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
@@ -4,613 +4,26 @@
package io.flutter.embedding.engine.systemchannels;
-import android.content.pm.ActivityInfo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.List;
-
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;
/**
- * System channel that receives requests for host platform behavior, e.g., haptic and sound
- * effects, system chrome configurations, and clipboard interaction.
+ * TODO(mattcarroll): fill in javadoc for PlatformChannel.
*/
public class PlatformChannel {
- @NonNull
+
public final MethodChannel channel;
- @Nullable
- private PlatformMessageHandler platformMessageHandler;
- private final MethodChannel.MethodCallHandler parsingMethodCallHandler = new MethodChannel.MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, MethodChannel.Result result) {
- if (platformMessageHandler == null) {
- // If no explicit PlatformMessageHandler has been registered then we don't
- // need to forward this call to an API. Return.
- return;
- }
-
- String method = call.method;
- Object arguments = call.arguments;
- try {
- switch (method) {
- case "SystemSound.play":
- try {
- SoundType soundType = SoundType.fromValue((String) arguments);
- platformMessageHandler.playSystemSound(soundType);
- result.success(null);
- } catch (NoSuchFieldException exception) {
- // The desired sound type does not exist.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "HapticFeedback.vibrate":
- try {
- HapticFeedbackType feedbackType = HapticFeedbackType.fromValue((String) arguments);
- platformMessageHandler.vibrateHapticFeedback(feedbackType);
- result.success(null);
- } catch (NoSuchFieldException exception) {
- // The desired feedback type does not exist.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "SystemChrome.setPreferredOrientations":
- try {
- int androidOrientation = decodeOrientations((JSONArray) arguments);
- platformMessageHandler.setPreferredOrientations(androidOrientation);
- result.success(null);
- } catch (JSONException | NoSuchFieldException exception) {
- // JSONException: One or more expected fields were either omitted or referenced an invalid type.
- // NoSuchFieldException: One or more expected fields were either omitted or referenced an invalid type.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "SystemChrome.setApplicationSwitcherDescription":
- try {
- AppSwitcherDescription description = decodeAppSwitcherDescription((JSONObject) arguments);
- platformMessageHandler.setApplicationSwitcherDescription(description);
- result.success(null);
- } catch (JSONException exception) {
- // One or more expected fields were either omitted or referenced an invalid type.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "SystemChrome.setEnabledSystemUIOverlays":
- try {
- List overlays = decodeSystemUiOverlays((JSONArray) arguments);
- platformMessageHandler.showSystemOverlays(overlays);
- result.success(null);
- } catch (JSONException | NoSuchFieldException exception) {
- // JSONException: One or more expected fields were either omitted or referenced an invalid type.
- // NoSuchFieldException: One or more of the overlay names are invalid.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "SystemChrome.restoreSystemUIOverlays":
- platformMessageHandler.restoreSystemUiOverlays();
- result.success(null);
- break;
- case "SystemChrome.setSystemUIOverlayStyle":
- try {
- SystemChromeStyle systemChromeStyle = decodeSystemChromeStyle((JSONObject) arguments);
- platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle);
- result.success(null);
- } catch (JSONException | NoSuchFieldException exception) {
- // JSONException: One or more expected fields were either omitted or referenced an invalid type.
- // NoSuchFieldException: One or more of the brightness names are invalid.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "SystemNavigator.pop":
- platformMessageHandler.popSystemNavigator();
- result.success(null);
- break;
- case "Clipboard.getData": {
- String contentFormatName = (String) arguments;
- ClipboardContentFormat clipboardFormat = null;
- if (contentFormatName != null) {
- try {
- clipboardFormat = ClipboardContentFormat.fromValue(contentFormatName);
- } catch (NoSuchFieldException exception) {
- // An unsupported content format was requested. Return failure.
- result.error("error", "No such clipboard content format: " + contentFormatName, null);
- }
- }
-
- CharSequence clipboardContent = platformMessageHandler.getClipboardData(clipboardFormat);
- if (clipboardContent != null) {
- JSONObject response = new JSONObject();
- response.put("text", clipboardContent);
- result.success(response);
- } else {
- result.success(null);
- }
- break;
- }
- case "Clipboard.setData": {
- String clipboardContent = ((JSONObject) arguments).getString("text");
- platformMessageHandler.setClipboardData(clipboardContent);
- result.success(null);
- break;
- }
- default:
- result.notImplemented();
- break;
- }
- } catch (JSONException e) {
- result.error("error", "JSON error: " + e.getMessage(), null);
- }
- }
- };
-
- /**
- * Constructs a {@code PlatformChannel} that connects Android to the Dart code
- * running in {@code dartExecutor}.
- *
- * The given {@code dartExecutor} is permitted to be idle or executing code.
- *
- * See {@link DartExecutor}.
- */
public PlatformChannel(@NonNull DartExecutor dartExecutor) {
- channel = new MethodChannel(dartExecutor, "flutter/platform", JSONMethodCodec.INSTANCE);
- channel.setMethodCallHandler(parsingMethodCallHandler);
+ this.channel = new MethodChannel(dartExecutor, "flutter/platform", JSONMethodCodec.INSTANCE);
}
- /**
- * Sets the {@link PlatformMessageHandler} which receives all events and requests
- * that are parsed from the underlying platform channel.
- */
- public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
- this.platformMessageHandler = platformMessageHandler;
+ public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
+ channel.setMethodCallHandler(handler);
}
- // TODO(mattcarroll): add support for IntDef annotations, then add @ScreenOrientation
-
- /**
- * Decodes a series of orientations to an aggregate desired orientation.
- *
- * @throws JSONException if {@code encodedOrientations} does not contain expected keys and value types.
- * @throws NoSuchFieldException if any given encoded orientation is not a valid orientation name.
- */
- private int decodeOrientations(@NonNull JSONArray encodedOrientations) throws JSONException, NoSuchFieldException {
- int requestedOrientation = 0x00;
- int firstRequestedOrientation = 0x00;
- for (int index = 0; index < encodedOrientations.length(); index += 1) {
- String encodedOrientation = encodedOrientations.getString(index);
- DeviceOrientation orientation = DeviceOrientation.fromValue(encodedOrientation);
-
- switch (orientation) {
- case PORTRAIT_UP:
- requestedOrientation |= 0x01;
- break;
- case PORTRAIT_DOWN:
- requestedOrientation |= 0x04;
- break;
- case LANDSCAPE_LEFT:
- requestedOrientation |= 0x02;
- break;
- case LANDSCAPE_RIGHT:
- requestedOrientation |= 0x08;
- break;
- }
-
- if (firstRequestedOrientation == 0x00) {
- firstRequestedOrientation = requestedOrientation;
- }
- }
-
- switch (requestedOrientation) {
- case 0x00:
- return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- case 0x01:
- return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- case 0x02:
- return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
- case 0x04:
- return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
- case 0x05:
- return ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
- case 0x08:
- return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
- case 0x0a:
- return ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
- case 0x0b:
- return ActivityInfo.SCREEN_ORIENTATION_USER;
- case 0x0f:
- return ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
- case 0x03: // portraitUp and landscapeLeft
- case 0x06: // portraitDown and landscapeLeft
- case 0x07: // portraitUp, portraitDown, and landscapeLeft
- case 0x09: // portraitUp and landscapeRight
- case 0x0c: // portraitDown and landscapeRight
- case 0x0d: // portraitUp, portraitDown, and landscapeRight
- case 0x0e: // portraitDown, landscapeLeft, and landscapeRight
- // Android can't describe these cases, so just default to whatever the first
- // specified value was.
- switch (firstRequestedOrientation) {
- case 0x01:
- return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- case 0x02:
- return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
- case 0x04:
- return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
- case 0x08:
- return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
- }
- }
-
- // Execution should never get this far, but if it does then we default
- // to a portrait orientation.
- return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
- }
-
- private AppSwitcherDescription decodeAppSwitcherDescription(@NonNull JSONObject encodedDescription) throws JSONException {
- int color = encodedDescription.getInt("primaryColor");
- if (color != 0) { // 0 means color isn't set, use system default
- color = color | 0xFF000000; // color must be opaque if set
- }
- String label = encodedDescription.getString("label");
- return new AppSwitcherDescription(color, label);
- }
-
- /**
- * Decodes a list of JSON-encoded overlays to a list of {@link SystemUiOverlay}.
- *
- * @throws JSONException if {@code encodedSystemUiOverlay} does not contain expected keys and value types.
- * @throws NoSuchFieldException if any of the given encoded overlay names are invalid.
- */
- private List decodeSystemUiOverlays(@NonNull JSONArray encodedSystemUiOverlay) throws JSONException, NoSuchFieldException {
- List overlays = new ArrayList<>();
- for (int i = 0; i < encodedSystemUiOverlay.length(); ++i) {
- String encodedOverlay = encodedSystemUiOverlay.getString(i);
- SystemUiOverlay overlay = SystemUiOverlay.fromValue(encodedOverlay);
- switch(overlay) {
- case TOP_OVERLAYS:
- overlays.add(SystemUiOverlay.TOP_OVERLAYS);
- break;
- case BOTTOM_OVERLAYS:
- overlays.add(SystemUiOverlay.BOTTOM_OVERLAYS);
- break;
- }
- }
- return overlays;
- }
-
- /**
- * Decodes a JSON-encoded {@code encodedStyle} to a {@link SystemChromeStyle}.
- *
- * @throws JSONException if {@code encodedStyle} does not contain expected keys and value types.
- * @throws NoSuchFieldException if any provided brightness name is invalid.
- */
- private SystemChromeStyle decodeSystemChromeStyle(@NonNull JSONObject encodedStyle) throws JSONException, NoSuchFieldException {
- Brightness systemNavigationBarIconBrightness = null;
- // TODO(mattcarroll): add color annotation
- Integer systemNavigationBarColor = null;
- // TODO(mattcarroll): add color annotation
- Integer systemNavigationBarDividerColor = null;
- Brightness statusBarIconBrightness = null;
- // TODO(mattcarroll): add color annotation
- Integer statusBarColor = null;
-
- if (!encodedStyle.isNull("systemNavigationBarIconBrightness")) {
- systemNavigationBarIconBrightness = Brightness.fromValue(encodedStyle.getString("systemNavigationBarIconBrightness"));
- }
-
- if (!encodedStyle.isNull("systemNavigationBarColor")) {
- systemNavigationBarColor = encodedStyle.getInt("systemNavigationBarColor");
- }
-
- if (!encodedStyle.isNull("statusBarIconBrightness")) {
- statusBarIconBrightness = Brightness.fromValue(encodedStyle.getString("statusBarIconBrightness"));
- }
-
- if (!encodedStyle.isNull("statusBarColor")) {
- statusBarColor = encodedStyle.getInt("statusBarColor");
- }
-
- if (!encodedStyle.isNull("systemNavigationBarDividerColor")) {
- systemNavigationBarDividerColor = encodedStyle.getInt("systemNavigationBarDividerColor");
- }
-
- return new SystemChromeStyle(
- statusBarColor,
- statusBarIconBrightness,
- systemNavigationBarColor,
- systemNavigationBarIconBrightness,
- systemNavigationBarDividerColor
- );
- }
-
- /**
- * Handler that receives platform messages sent from Flutter to Android
- * through a given {@link PlatformChannel}.
- *
- * To register a {@code PlatformMessageHandler} with a {@link PlatformChannel},
- * see {@link PlatformChannel#setPlatformMessageHandler(PlatformMessageHandler)}.
- */
- public interface PlatformMessageHandler {
- /**
- * The Flutter application would like to play the given {@code soundType}.
- */
- void playSystemSound(@NonNull SoundType soundType);
-
- /**
- * The Flutter application would like to play the given haptic {@code feedbackType}.
- */
- void vibrateHapticFeedback(@NonNull HapticFeedbackType feedbackType);
-
- /**
- * The Flutter application would like to display in the given {@code androidOrientation}.
- */
- // TODO(mattcarroll): add @ScreenOrientation annotation
- void setPreferredOrientations(int androidOrientation);
-
- /**
- * The Flutter application would like to be displayed in Android's app switcher with
- * the visual representation described in the given {@code description}.
- *
- * See the related Android documentation:
- * https://developer.android.com/guide/components/activities/recents
- */
- void setApplicationSwitcherDescription(@NonNull AppSwitcherDescription description);
-
- /**
- * The Flutter application would like the Android system to display the given
- * {@code overlays}.
- *
- * {@link SystemUiOverlay#TOP_OVERLAYS} refers to system overlays such as the
- * status bar, while {@link SystemUiOverlay#BOTTOM_OVERLAYS} refers to system
- * overlays such as the back/home/recents navigation on the bottom of the screen.
- *
- * An empty list of {@code overlays} should hide all system overlays.
- */
- void showSystemOverlays(@NonNull List overlays);
-
- /**
- * The Flutter application would like to restore the visibility of system
- * overlays to the last set of overlays sent via {@link #showSystemOverlays(List)}.
- *
- * If {@link #showSystemOverlays(List)} has yet to be called, then a default
- * system overlay appearance is desired:
- *
- * {@code
- * View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- * }
- */
- void restoreSystemUiOverlays();
-
- /**
- * The Flutter application would like the system chrome to present itself with
- * the given {@code systemUiOverlayStyle}, i.e., the given status bar and
- * navigation bar colors and brightness.
- */
- void setSystemUiOverlayStyle(@NonNull SystemChromeStyle systemUiOverlayStyle);
-
- /**
- * The Flutter application would like to pop the top item off of the Android
- * app's navigation back stack.
- */
- void popSystemNavigator();
-
- /**
- * The Flutter application would like to receive the current data in the
- * clipboard and have it returned in the given {@code format}.
- */
- @Nullable
- CharSequence getClipboardData(@Nullable ClipboardContentFormat format);
-
- /**
- * The Flutter application would like to set the current data in the
- * clipboard to the given {@code text}.
- */
- void setClipboardData(@NonNull String text);
- }
-
- /**
- * Types of sounds the Android OS can play on behalf of an application.
- */
- public enum SoundType {
- CLICK("SoundType.click");
-
- static SoundType fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (SoundType soundType : SoundType.values()) {
- if (soundType.encodedName.equals(encodedName)) {
- return soundType;
- }
- }
- throw new NoSuchFieldException("No such SoundType: " + encodedName);
- }
-
- @NonNull
- private final String encodedName;
-
- SoundType(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * The types of haptic feedback that the Android OS can generate on behalf
- * of an application.
- */
- public enum HapticFeedbackType {
- STANDARD(null),
- LIGHT_IMPACT("HapticFeedbackType.lightImpact"),
- MEDIUM_IMPACT("HapticFeedbackType.mediumImpact"),
- HEAVY_IMPACT("HapticFeedbackType.heavyImpact"),
- SELECTION_CLICK("HapticFeedbackType.selectionClick");
-
- static HapticFeedbackType fromValue(@Nullable String encodedName) throws NoSuchFieldException {
- for (HapticFeedbackType feedbackType : HapticFeedbackType.values()) {
- if ((feedbackType.encodedName == null && encodedName == null)
- || (feedbackType.encodedName != null && feedbackType.encodedName.equals(encodedName))) {
- return feedbackType;
- }
- }
- throw new NoSuchFieldException("No such HapticFeedbackType: " + encodedName);
- }
-
- @Nullable
- private final String encodedName;
-
- HapticFeedbackType(@Nullable String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * The possible desired orientations of a Flutter application.
- */
- public enum DeviceOrientation {
- PORTRAIT_UP("DeviceOrientation.portraitUp"),
- PORTRAIT_DOWN("DeviceOrientation.portraitDown"),
- LANDSCAPE_LEFT("DeviceOrientation.landscapeLeft"),
- LANDSCAPE_RIGHT("DeviceOrientation.landscapeRight");
-
- static DeviceOrientation fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (DeviceOrientation orientation : DeviceOrientation.values()) {
- if (orientation.encodedName.equals(encodedName)) {
- return orientation;
- }
- }
- throw new NoSuchFieldException("No such DeviceOrientation: " + encodedName);
- }
-
- @NonNull
- private String encodedName;
-
- DeviceOrientation(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * The set of Android system UI overlays as perceived by the Flutter application.
- *
- * Android includes many more overlay options and flags than what is provided by
- * {@code SystemUiOverlay}. Flutter only requires control over a subset of the
- * overlays and those overlays are represented by {@code SystemUiOverlay} values.
- */
- public enum SystemUiOverlay {
- TOP_OVERLAYS("SystemUiOverlay.top"),
- BOTTOM_OVERLAYS("SystemUiOverlay.bottom");
-
- static SystemUiOverlay fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (SystemUiOverlay overlay : SystemUiOverlay.values()) {
- if (overlay.encodedName.equals(encodedName)) {
- return overlay;
- }
- }
- throw new NoSuchFieldException("No such SystemUiOverlay: " + encodedName);
- }
-
- @NonNull
- private String encodedName;
-
- SystemUiOverlay(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * The color and label of an application that appears in Android's app switcher, AKA
- * recents screen.
- */
- public static class AppSwitcherDescription {
- // TODO(mattcarroll): add color annotation
- public final int color;
- @NonNull
- public final String label;
-
- public AppSwitcherDescription(int color, @NonNull String label) {
- this.color = color;
- this.label = label;
- }
- }
-
- /**
- * The color and brightness of system chrome, e.g., status bar and system navigation bar.
- */
- public static class SystemChromeStyle {
- // TODO(mattcarroll): add color annotation
- @Nullable
- public final Integer statusBarColor;
- @Nullable
- public final Brightness statusBarIconBrightness;
- // TODO(mattcarroll): add color annotation
- @Nullable
- public final Integer systemNavigationBarColor;
- @Nullable
- public final Brightness systemNavigationBarIconBrightness;
- // TODO(mattcarroll): add color annotation
- @Nullable
- public final Integer systemNavigationBarDividerColor;
-
- public SystemChromeStyle(
- @Nullable Integer statusBarColor,
- @Nullable Brightness statusBarIconBrightness,
- @Nullable Integer systemNavigationBarColor,
- @Nullable Brightness systemNavigationBarIconBrightness,
- @Nullable Integer systemNavigationBarDividerColor
- ) {
- this.statusBarColor = statusBarColor;
- this.statusBarIconBrightness = statusBarIconBrightness;
- this.systemNavigationBarColor = systemNavigationBarColor;
- this.systemNavigationBarIconBrightness = systemNavigationBarIconBrightness;
- this.systemNavigationBarDividerColor = systemNavigationBarDividerColor;
- }
- }
-
- public enum Brightness {
- LIGHT("Brightness.light"),
- DARK("Brightness.dark");
-
- static Brightness fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (Brightness brightness : Brightness.values()) {
- if (brightness.encodedName.equals(encodedName)) {
- return brightness;
- }
- }
- throw new NoSuchFieldException("No such Brightness: " + encodedName);
- }
-
- @NonNull
- private String encodedName;
-
- Brightness(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * Data formats of clipboard content.
- */
- public enum ClipboardContentFormat {
- PLAIN_TEXT("text/plain");
-
- static ClipboardContentFormat fromValue(String encodedName) throws NoSuchFieldException {
- for (ClipboardContentFormat format : ClipboardContentFormat.values()) {
- if (format.encodedName.equals(encodedName)) {
- return format;
- }
- }
- throw new NoSuchFieldException("No such ClipboardContentFormat: " + encodedName);
- }
-
- @NonNull
- private String encodedName;
-
- ClipboardContentFormat(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java
deleted file mode 100644
index fc4b755215..0000000000
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java
+++ /dev/null
@@ -1,406 +0,0 @@
-package io.flutter.embedding.engine.systemchannels;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.view.inputmethod.EditorInfo;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-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;
-
-/**
- * {@link TextInputChannel} is a platform channel between Android and Flutter that is used to
- * communicate information about the user's text input.
- *
- * When the user presses an action button like "done" or "next", that action is sent from Android
- * to Flutter through this {@link TextInputChannel}.
- *
- * When an input system in the Flutter app wants to show the keyboard, or hide it, or configure
- * editing state, etc. a message is sent from Flutter to Android through this {@link TextInputChannel}.
- *
- * {@link TextInputChannel} comes with a default {@link io.flutter.plugin.common.MethodChannel.MethodCallHandler}
- * that parses incoming messages from Flutter. Register a {@link TextInputMethodHandler} to respond
- * to standard Flutter text input messages.
- */
-public class TextInputChannel {
- @NonNull
- public final MethodChannel channel;
- @Nullable
- private TextInputMethodHandler textInputMethodHandler;
-
- private final MethodChannel.MethodCallHandler parsingMethodHandler = new MethodChannel.MethodCallHandler() {
- @Override
- public void onMethodCall(MethodCall call, MethodChannel.Result result) {
- if (textInputMethodHandler == null) {
- // If no explicit TextInputMethodHandler 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;
- switch (method) {
- case "TextInput.show":
- textInputMethodHandler.show();
- result.success(null);
- break;
- case "TextInput.hide":
- textInputMethodHandler.hide();
- result.success(null);
- break;
- case "TextInput.setClient":
- try {
- final JSONArray argumentList = (JSONArray) args;
- final int textInputClientId = argumentList.getInt(0);
- final JSONObject jsonConfiguration = argumentList.getJSONObject(1);
- textInputMethodHandler.setClient(textInputClientId, Configuration.fromJson(jsonConfiguration));
- result.success(null);
- } catch (JSONException | NoSuchFieldException exception) {
- // JSONException: missing keys or bad value types.
- // NoSuchFieldException: one or more values were invalid.
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "TextInput.setEditingState":
- try {
- final JSONObject editingState = (JSONObject) args;
- textInputMethodHandler.setEditingState(TextEditState.fromJson(editingState));
- result.success(null);
- } catch (JSONException exception) {
- result.error("error", exception.getMessage(), null);
- }
- break;
- case "TextInput.clearClient":
- textInputMethodHandler.clearClient();
- result.success(null);
- break;
- default:
- result.notImplemented();
- break;
- }
- }
- };
-
- /**
- * Constructs a {@code TextInputChannel} that connects Android to the Dart code
- * running in {@code dartExecutor}.
- *
- * The given {@code dartExecutor} is permitted to be idle or executing code.
- *
- * See {@link DartExecutor}.
- */
- public TextInputChannel(@NonNull DartExecutor dartExecutor) {
- this.channel = new MethodChannel(dartExecutor, "flutter/textinput", JSONMethodCodec.INSTANCE);
- channel.setMethodCallHandler(parsingMethodHandler);
- }
-
- /**
- * Instructs Flutter to update its text input editing state to reflect the given configuration.
- */
- public void updateEditingState(int inputClientId, String text, int selectionStart, int selectionEnd, int composingStart, int composingEnd) {
- HashMap state = new HashMap<>();
- state.put("text", text);
- state.put("selectionBase", selectionStart);
- state.put("selectionExtent", selectionEnd);
- state.put("composingBase", composingStart);
- state.put("composingExtent", composingEnd);
-
- channel.invokeMethod(
- "TextInputClient.updateEditingState",
- Arrays.asList(inputClientId, state)
- );
- }
-
- /**
- * Instructs Flutter to execute a "newline" action.
- */
- public void newline(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.newline")
- );
- }
-
- /**
- * Instructs Flutter to execute a "go" action.
- */
- public void go(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.go")
- );
- }
-
- /**
- * Instructs Flutter to execute a "search" action.
- */
- public void search(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.search")
- );
- }
-
- /**
- * Instructs Flutter to execute a "send" action.
- */
- public void send(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.send")
- );
- }
-
- /**
- * Instructs Flutter to execute a "done" action.
- */
- public void done(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.done")
- );
- }
-
- /**
- * Instructs Flutter to execute a "next" action.
- */
- public void next(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.next")
- );
- }
-
- /**
- * Instructs Flutter to execute a "previous" action.
- */
- public void previous(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.previous")
- );
- }
-
- /**
- * Instructs Flutter to execute an "unspecified" action.
- */
- public void unspecifiedAction(int inputClientId) {
- channel.invokeMethod(
- "TextInputClient.performAction",
- Arrays.asList(inputClientId, "TextInputAction.unspecified")
- );
- }
-
- /**
- * Sets the {@link TextInputMethodHandler} which receives all events and requests
- * that are parsed from the underlying platform channel.
- */
- public void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler) {
- this.textInputMethodHandler = textInputMethodHandler;
- }
-
- public interface TextInputMethodHandler {
- // TODO(mattcarroll): javadoc
- void show();
-
- // TODO(mattcarroll): javadoc
- void hide();
-
- // TODO(mattcarroll): javadoc
- void setClient(int textInputClientId, @NonNull Configuration configuration);
-
- // TODO(mattcarroll): javadoc
- void setEditingState(@NonNull TextEditState editingState);
-
- // TODO(mattcarroll): javadoc
- void clearClient();
- }
-
- /**
- * A text editing configuration.
- */
- public static class Configuration {
- public static Configuration fromJson(@NonNull JSONObject json) throws JSONException, NoSuchFieldException {
- final String inputActionName = json.getString("inputAction");
- if (inputActionName == null) {
- throw new JSONException("Configuration JSON missing 'inputAction' property.");
- }
-
- final Integer inputAction = inputActionFromTextInputAction(inputActionName);
- return new Configuration(
- json.optBoolean("obscureText"),
- json.optBoolean("autocorrect", true),
- TextCapitalization.fromValue(json.getString("textCapitalization")),
- InputType.fromJson(json.getJSONObject("inputType")),
- inputAction,
- json.optString("actionLabel")
- );
- }
-
- private static Integer inputActionFromTextInputAction(@NonNull String inputAction) {
- switch (inputAction) {
- case "TextInputAction.newline":
- return EditorInfo.IME_ACTION_NONE;
- case "TextInputAction.none":
- return EditorInfo.IME_ACTION_NONE;
- case "TextInputAction.unspecified":
- return EditorInfo.IME_ACTION_UNSPECIFIED;
- case "TextInputAction.done":
- return EditorInfo.IME_ACTION_DONE;
- case "TextInputAction.go":
- return EditorInfo.IME_ACTION_GO;
- case "TextInputAction.search":
- return EditorInfo.IME_ACTION_SEARCH;
- case "TextInputAction.send":
- return EditorInfo.IME_ACTION_SEND;
- case "TextInputAction.next":
- return EditorInfo.IME_ACTION_NEXT;
- case "TextInputAction.previous":
- return EditorInfo.IME_ACTION_PREVIOUS;
- default:
- // Present default key if bad input type is given.
- return EditorInfo.IME_ACTION_UNSPECIFIED;
- }
- }
-
- public final boolean obscureText;
- public final boolean autocorrect;
- @NonNull
- public final TextCapitalization textCapitalization;
- @NonNull
- public final InputType inputType;
- @Nullable
- public final Integer inputAction;
- @Nullable
- public final String actionLabel;
-
- public Configuration(
- boolean obscureText,
- boolean autocorrect,
- @NonNull TextCapitalization textCapitalization,
- @NonNull InputType inputType,
- @Nullable Integer inputAction,
- @Nullable String actionLabel
- ) {
- this.obscureText = obscureText;
- this.autocorrect = autocorrect;
- this.textCapitalization = textCapitalization;
- this.inputType = inputType;
- this.inputAction = inputAction;
- this.actionLabel = actionLabel;
- }
- }
-
- /**
- * A text input type.
- *
- * If the {@link #type} is {@link TextInputType#NUMBER}, this {@code InputType} also
- * reports whether that number {@link #isSigned} and {@link #isDecimal}.
- */
- public static class InputType {
- @NonNull
- public static InputType fromJson(@NonNull JSONObject json) throws JSONException, NoSuchFieldException {
- return new InputType(
- TextInputType.fromValue(json.getString("name")),
- json.optBoolean("signed", false),
- json.optBoolean("decimal", false)
- );
- }
-
- @NonNull
- public final TextInputType type;
- public final boolean isSigned;
- public final boolean isDecimal;
-
- public InputType(@NonNull TextInputType type, boolean isSigned, boolean isDecimal) {
- this.type = type;
- this.isSigned = isSigned;
- this.isDecimal = isDecimal;
- }
- }
-
- /**
- * Types of text input.
- */
- public enum TextInputType {
- DATETIME("TextInputType.datetime"),
- NUMBER("TextInputType.number"),
- PHONE("TextInputType.phone"),
- MULTILINE("TextInputType.multiline"),
- EMAIL_ADDRESS("TextInputType.emailAddress"),
- URL("TextInputType.url");
-
- static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (TextInputType textInputType : TextInputType.values()) {
- if (textInputType.encodedName.equals(encodedName)) {
- return textInputType;
- }
- }
- throw new NoSuchFieldException("No such TextInputType: " + encodedName);
- }
-
- @NonNull
- private final String encodedName;
-
- TextInputType(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * Text capitalization schemes.
- */
- public enum TextCapitalization {
- CHARACTERS("TextCapitalization.characters"),
- WORDS("TextCapitalization.words"),
- SENTENCES("TextCapitalization.sentences");
-
- static TextCapitalization fromValue(@NonNull String encodedName) throws NoSuchFieldException {
- for (TextCapitalization textCapitalization : TextCapitalization.values()) {
- if (textCapitalization.encodedName.equals(encodedName)) {
- return textCapitalization;
- }
- }
- throw new NoSuchFieldException("No such TextCapitalization: " + encodedName);
- }
-
- @NonNull
- private final String encodedName;
-
- TextCapitalization(@NonNull String encodedName) {
- this.encodedName = encodedName;
- }
- }
-
- /**
- * State of an on-going text editing session.
- */
- public static class TextEditState {
- public static TextEditState fromJson(@NonNull JSONObject textEditState) throws JSONException {
- return new TextEditState(
- textEditState.getString("text"),
- textEditState.getInt("selectionBase"),
- textEditState.getInt("selectionExtent")
- );
- }
-
- @NonNull
- public final String text;
- public final int selectionStart;
- public final int selectionEnd;
-
- public TextEditState(@NonNull String text, int selectionStart, int selectionEnd) {
- this.text = text;
- this.selectionStart = selectionStart;
- this.selectionEnd = selectionEnd;
- }
- }
-}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
index b3a287820b..df907be0b7 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
@@ -11,16 +11,17 @@ import android.view.KeyEvent;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-
-import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.common.ErrorLogResult;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.FlutterView;
+import java.util.Arrays;
+import java.util.HashMap;
+
class InputConnectionAdaptor extends BaseInputConnection {
private final FlutterView mFlutterView;
private final int mClient;
- private final TextInputChannel textInputChannel;
+ private final MethodChannel mFlutterChannel;
private final Editable mEditable;
private int mBatchCount;
private InputMethodManager mImm;
@@ -28,16 +29,12 @@ class InputConnectionAdaptor extends BaseInputConnection {
private static final MethodChannel.Result logger =
new ErrorLogResult("FlutterTextInput");
- public InputConnectionAdaptor(
- FlutterView view,
- int client,
- TextInputChannel textInputChannel,
- Editable editable
- ) {
+ public InputConnectionAdaptor(FlutterView view, int client,
+ MethodChannel flutterChannel, Editable editable) {
super(view, true);
mFlutterView = view;
mClient = client;
- this.textInputChannel = textInputChannel;
+ mFlutterChannel = flutterChannel;
mEditable = editable;
mBatchCount = 0;
mImm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -58,14 +55,14 @@ class InputConnectionAdaptor extends BaseInputConnection {
selectionStart, selectionEnd,
composingStart, composingEnd);
- textInputChannel.updateEditingState(
- mClient,
- mEditable.toString(),
- selectionStart,
- selectionEnd,
- composingStart,
- composingEnd
- );
+ HashMap state = new HashMap<>();
+ state.put("text", mEditable.toString());
+ state.put("selectionBase", selectionStart);
+ state.put("selectionExtent", selectionEnd);
+ state.put("composingBase", composingStart);
+ state.put("composingExtent", composingEnd);
+ mFlutterChannel.invokeMethod("TextInputClient.updateEditingState",
+ Arrays.asList(mClient, state), logger);
}
@Override
@@ -181,30 +178,39 @@ class InputConnectionAdaptor extends BaseInputConnection {
@Override
public boolean performEditorAction(int actionCode) {
switch (actionCode) {
+ // TODO(mattcarroll): is newline an appropriate action for "none"?
case EditorInfo.IME_ACTION_NONE:
- textInputChannel.newline(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.newline"), logger);
break;
case EditorInfo.IME_ACTION_UNSPECIFIED:
- textInputChannel.unspecifiedAction(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.unspecified"), logger);
break;
case EditorInfo.IME_ACTION_GO:
- textInputChannel.go(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.go"), logger);
break;
case EditorInfo.IME_ACTION_SEARCH:
- textInputChannel.search(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.search"), logger);
break;
case EditorInfo.IME_ACTION_SEND:
- textInputChannel.send(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.send"), logger);
break;
case EditorInfo.IME_ACTION_NEXT:
- textInputChannel.next(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.next"), logger);
break;
case EditorInfo.IME_ACTION_PREVIOUS:
- textInputChannel.previous(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.previous"), logger);
break;
default:
case EditorInfo.IME_ACTION_DONE:
- textInputChannel.done(mClient);
+ mFlutterChannel.invokeMethod("TextInputClient.performAction",
+ Arrays.asList(mClient, "TextInputAction.done"), logger);
break;
}
return true;
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
index bc54b3ed04..c59ee25148 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
@@ -5,7 +5,6 @@
package io.flutter.plugin.editing;
import android.content.Context;
-import android.support.annotation.NonNull;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
@@ -13,87 +12,84 @@ import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-
-import io.flutter.embedding.engine.dart.DartExecutor;
-import io.flutter.embedding.engine.systemchannels.TextInputChannel;
+import io.flutter.plugin.common.JSONMethodCodec;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.view.FlutterView;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
/**
* Android implementation of the text input plugin.
*/
-public class TextInputPlugin {
+public class TextInputPlugin implements MethodCallHandler {
private final FlutterView mView;
private final InputMethodManager mImm;
- private final TextInputChannel textInputChannel;
+ private final MethodChannel mFlutterChannel;
private int mClient = 0;
- private TextInputChannel.Configuration configuration;
+ private JSONObject mConfiguration;
private Editable mEditable;
private boolean mRestartInputPending;
- public TextInputPlugin(FlutterView view, @NonNull DartExecutor dartExecutor) {
+ public TextInputPlugin(FlutterView view) {
mView = view;
mImm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
-
- textInputChannel = new TextInputChannel(dartExecutor);
- textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
- @Override
- public void show() {
- showTextInput(mView);
- }
-
- @Override
- public void hide() {
- hideTextInput(mView);
- }
-
- @Override
- public void setClient(int textInputClientId, TextInputChannel.Configuration configuration) {
- setTextInputClient(textInputClientId, configuration);
- }
-
- @Override
- public void setEditingState(TextInputChannel.TextEditState editingState) {
- setTextInputEditingState(mView, editingState);
- }
-
- @Override
- public void clearClient() {
- clearTextInputClient();
- }
- });
+ mFlutterChannel = new MethodChannel(view, "flutter/textinput", JSONMethodCodec.INSTANCE);
+ mFlutterChannel.setMethodCallHandler(this);
}
- private static int inputTypeFromTextInputType(
- TextInputChannel.InputType type,
- boolean obscureText,
- boolean autocorrect,
- TextInputChannel.TextCapitalization textCapitalization
- ) {
- if (type.type == TextInputChannel.TextInputType.DATETIME) {
- return InputType.TYPE_CLASS_DATETIME;
- } else if (type.type == TextInputChannel.TextInputType.NUMBER) {
- int textType = InputType.TYPE_CLASS_NUMBER;
- if (type.isSigned) {
- textType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ @Override
+ public void onMethodCall(MethodCall call, Result result) {
+ String method = call.method;
+ Object args = call.arguments;
+ try {
+ if (method.equals("TextInput.show")) {
+ showTextInput(mView);
+ result.success(null);
+ } else if (method.equals("TextInput.hide")) {
+ hideTextInput(mView);
+ result.success(null);
+ } else if (method.equals("TextInput.setClient")) {
+ final JSONArray argumentList = (JSONArray) args;
+ setTextInputClient(mView, argumentList.getInt(0), argumentList.getJSONObject(1));
+ result.success(null);
+ } else if (method.equals("TextInput.setEditingState")) {
+ setTextInputEditingState(mView, (JSONObject) args);
+ result.success(null);
+ } else if (method.equals("TextInput.clearClient")) {
+ clearTextInputClient();
+ result.success(null);
+ } else {
+ result.notImplemented();
}
- if (type.isDecimal) {
- textType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
- }
- return textType;
- } else if (type.type == TextInputChannel.TextInputType.PHONE) {
- return InputType.TYPE_CLASS_PHONE;
+ } catch (JSONException e) {
+ result.error("error", "JSON error: " + e.getMessage(), null);
}
+ }
+
+ private static int inputTypeFromTextInputType(JSONObject type, boolean obscureText,
+ boolean autocorrect, String textCapitalization) throws JSONException {
+ String inputType = type.getString("name");
+ if (inputType.equals("TextInputType.datetime")) return InputType.TYPE_CLASS_DATETIME;
+ if (inputType.equals("TextInputType.number")) {
+ int textType = InputType.TYPE_CLASS_NUMBER;
+ if (type.optBoolean("signed")) textType |= InputType.TYPE_NUMBER_FLAG_SIGNED;
+ if (type.optBoolean("decimal")) textType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
+ return textType;
+ }
+ if (inputType.equals("TextInputType.phone")) return InputType.TYPE_CLASS_PHONE;
int textType = InputType.TYPE_CLASS_TEXT;
- if (type.type == TextInputChannel.TextInputType.MULTILINE) {
+ if (inputType.equals("TextInputType.multiline"))
textType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
- } else if (type.type == TextInputChannel.TextInputType.EMAIL_ADDRESS) {
+ else if (inputType.equals("TextInputType.emailAddress"))
textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
- } else if (type.type == TextInputChannel.TextInputType.URL) {
+ else if (inputType.equals("TextInputType.url"))
textType |= InputType.TYPE_TEXT_VARIATION_URI;
- }
-
if (obscureText) {
// Note: both required. Some devices ignore TYPE_TEXT_FLAG_NO_SUGGESTIONS.
textType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
@@ -101,50 +97,69 @@ public class TextInputPlugin {
} else {
if (autocorrect) textType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
}
-
- if (textCapitalization == TextInputChannel.TextCapitalization.CHARACTERS) {
+ if (textCapitalization.equals("TextCapitalization.characters")) {
textType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
- } else if (textCapitalization == TextInputChannel.TextCapitalization.WORDS) {
+ } else if (textCapitalization.equals("TextCapitalization.words")) {
textType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
- } else if (textCapitalization == TextInputChannel.TextCapitalization.SENTENCES) {
+ } else if (textCapitalization.equals("TextCapitalization.sentences")) {
textType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
}
-
return textType;
}
- public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs) {
+ private static int inputActionFromTextInputAction(String inputAction) {
+ switch (inputAction) {
+ case "TextInputAction.newline":
+ return EditorInfo.IME_ACTION_NONE;
+ case "TextInputAction.none":
+ return EditorInfo.IME_ACTION_NONE;
+ case "TextInputAction.unspecified":
+ return EditorInfo.IME_ACTION_UNSPECIFIED;
+ case "TextInputAction.done":
+ return EditorInfo.IME_ACTION_DONE;
+ case "TextInputAction.go":
+ return EditorInfo.IME_ACTION_GO;
+ case "TextInputAction.search":
+ return EditorInfo.IME_ACTION_SEARCH;
+ case "TextInputAction.send":
+ return EditorInfo.IME_ACTION_SEND;
+ case "TextInputAction.next":
+ return EditorInfo.IME_ACTION_NEXT;
+ case "TextInputAction.previous":
+ return EditorInfo.IME_ACTION_PREVIOUS;
+ default:
+ // Present default key if bad input type is given.
+ return EditorInfo.IME_ACTION_UNSPECIFIED;
+ }
+ }
+
+ public InputConnection createInputConnection(FlutterView view, EditorInfo outAttrs)
+ throws JSONException {
if (mClient == 0) return null;
- outAttrs.inputType = inputTypeFromTextInputType(
- configuration.inputType,
- configuration.obscureText,
- configuration.autocorrect,
- configuration.textCapitalization
- );
+ outAttrs.inputType = inputTypeFromTextInputType(mConfiguration.getJSONObject("inputType"),
+ mConfiguration.optBoolean("obscureText"),
+ mConfiguration.optBoolean("autocorrect", true),
+ mConfiguration.getString("textCapitalization"));
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
int enterAction;
- if (configuration.inputAction == null) {
+ if (mConfiguration.isNull("inputAction")) {
// If an explicit input action isn't set, then default to none for multi-line fields
// and done for single line fields.
enterAction = (InputType.TYPE_TEXT_FLAG_MULTI_LINE & outAttrs.inputType) != 0
? EditorInfo.IME_ACTION_NONE
: EditorInfo.IME_ACTION_DONE;
} else {
- enterAction = configuration.inputAction;
+ enterAction = inputActionFromTextInputAction(mConfiguration.getString("inputAction"));
}
- if (configuration.actionLabel != null) {
- outAttrs.actionLabel = configuration.actionLabel;
+ if (!mConfiguration.isNull("actionLabel")) {
+ outAttrs.actionLabel = mConfiguration.getString("actionLabel");
outAttrs.actionId = enterAction;
}
outAttrs.imeOptions |= enterAction;
- InputConnectionAdaptor connection = new InputConnectionAdaptor(
- view,
- mClient,
- textInputChannel,
- mEditable
- );
+ InputConnectionAdaptor connection =
+ new InputConnectionAdaptor(view, mClient, mFlutterChannel, mEditable);
outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
@@ -160,9 +175,9 @@ public class TextInputPlugin {
mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
}
- private void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
+ private void setTextInputClient(FlutterView view, int client, JSONObject configuration) {
mClient = client;
- this.configuration = configuration;
+ mConfiguration = configuration;
mEditable = Editable.Factory.getInstance().newEditable("");
// setTextInputClient will be followed by a call to setTextInputEditingState.
@@ -170,9 +185,9 @@ public class TextInputPlugin {
mRestartInputPending = true;
}
- private void applyStateToSelection(TextInputChannel.TextEditState state) {
- int selStart = state.selectionStart;
- int selEnd = state.selectionEnd;
+ private void applyStateToSelection(JSONObject state) throws JSONException {
+ int selStart = state.getInt("selectionBase");
+ int selEnd = state.getInt("selectionExtent");
if (selStart >= 0 && selStart <= mEditable.length() && selEnd >= 0
&& selEnd <= mEditable.length()) {
Selection.setSelection(mEditable, selStart, selEnd);
@@ -181,15 +196,15 @@ public class TextInputPlugin {
}
}
- private void setTextInputEditingState(FlutterView view, TextInputChannel.TextEditState state) {
- if (!mRestartInputPending && state.text.equals(mEditable.toString())) {
+ private void setTextInputEditingState(FlutterView view, JSONObject state) throws JSONException {
+ if (!mRestartInputPending && state.getString("text").equals(mEditable.toString())) {
applyStateToSelection(state);
mImm.updateSelection(mView, Math.max(Selection.getSelectionStart(mEditable), 0),
Math.max(Selection.getSelectionEnd(mEditable), 0),
BaseInputConnection.getComposingSpanStart(mEditable),
BaseInputConnection.getComposingSpanEnd(mEditable));
} else {
- mEditable.replace(0, mEditable.length(), state.text);
+ mEditable.replace(0, mEditable.length(), state.getString("text"));
applyStateToSelection(state);
mImm.restartInput(view);
mRestartInputPending = false;
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java
index e9d44001ef..b6f53d4574 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformPlugin.java
@@ -9,159 +9,212 @@ import android.app.ActivityManager.TaskDescription;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.content.pm.ActivityInfo;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.Window;
-
-import java.util.List;
-
-import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.plugin.common.ActivityLifecycleListener;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
/**
* Android implementation of the platform plugin.
*/
-public class PlatformPlugin implements ActivityLifecycleListener {
+public class PlatformPlugin implements MethodCallHandler, ActivityLifecycleListener {
+ private final Activity mActivity;
+ private JSONObject mCurrentTheme;
public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+ private static final String kTextPlainFormat = "text/plain";
- private final Activity activity;
- private final PlatformChannel platformChannel;
- private PlatformChannel.SystemChromeStyle currentTheme;
- private int mEnabledOverlays;
-
- private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = new PlatformChannel.PlatformMessageHandler() {
- @Override
- public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
- PlatformPlugin.this.playSystemSound(soundType);
- }
-
- @Override
- public void vibrateHapticFeedback(@NonNull PlatformChannel.HapticFeedbackType feedbackType) {
- PlatformPlugin.this.vibrateHapticFeedback(feedbackType);
- }
-
- @Override
- public void setPreferredOrientations(int androidOrientation) {
- setSystemChromePreferredOrientations(androidOrientation);
- }
-
- @Override
- public void setApplicationSwitcherDescription(@NonNull PlatformChannel.AppSwitcherDescription description) {
- setSystemChromeApplicationSwitcherDescription(description);
- }
-
- @Override
- public void showSystemOverlays(@NonNull List overlays) {
- setSystemChromeEnabledSystemUIOverlays(overlays);
- }
-
- @Override
- public void restoreSystemUiOverlays() {
- restoreSystemChromeSystemUIOverlays();
- }
-
- @Override
- public void setSystemUiOverlayStyle(@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
- setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
- }
-
- @Override
- public void popSystemNavigator() {
- PlatformPlugin.this.popSystemNavigator();
- }
-
- @Override
- public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentFormat format) {
- return PlatformPlugin.this.getClipboardData(format);
- }
-
- @Override
- public void setClipboardData(@NonNull String text) {
- PlatformPlugin.this.setClipboardData(text);
- }
- };
-
- public PlatformPlugin(Activity activity, PlatformChannel platformChannel) {
- this.activity = activity;
- this.platformChannel = platformChannel;
- this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
-
+ public PlatformPlugin(Activity activity) {
+ mActivity = activity;
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
- private void playSystemSound(PlatformChannel.SoundType soundType) {
- if (soundType == PlatformChannel.SoundType.CLICK) {
- View view = activity.getWindow().getDecorView();
+ @Override
+ public void onMethodCall(MethodCall call, Result result) {
+ String method = call.method;
+ Object arguments = call.arguments;
+ try {
+ if (method.equals("SystemSound.play")) {
+ playSystemSound((String) arguments);
+ result.success(null);
+ } else if (method.equals("HapticFeedback.vibrate")) {
+ vibrateHapticFeedback((String) arguments);
+ result.success(null);
+ } else if (method.equals("SystemChrome.setPreferredOrientations")) {
+ setSystemChromePreferredOrientations((JSONArray) arguments);
+ result.success(null);
+ } else if (method.equals("SystemChrome.setApplicationSwitcherDescription")) {
+ setSystemChromeApplicationSwitcherDescription((JSONObject) arguments);
+ result.success(null);
+ } else if (method.equals("SystemChrome.setEnabledSystemUIOverlays")) {
+ setSystemChromeEnabledSystemUIOverlays((JSONArray) arguments);
+ result.success(null);
+ } else if (method.equals("SystemChrome.restoreSystemUIOverlays")) {
+ restoreSystemChromeSystemUIOverlays();
+ result.success(null);
+ } else if (method.equals("SystemChrome.setSystemUIOverlayStyle")) {
+ setSystemChromeSystemUIOverlayStyle((JSONObject) arguments);
+ result.success(null);
+ } else if (method.equals("SystemNavigator.pop")) {
+ popSystemNavigator();
+ result.success(null);
+ } else if (method.equals("Clipboard.getData")) {
+ result.success(getClipboardData((String) arguments));
+ } else if (method.equals("Clipboard.setData")) {
+ setClipboardData((JSONObject) arguments);
+ result.success(null);
+ } else {
+ result.notImplemented();
+ }
+ } catch (JSONException e) {
+ result.error("error", "JSON error: " + e.getMessage(), null);
+ }
+ }
+
+ private void playSystemSound(String soundType) {
+ if (soundType.equals("SystemSoundType.click")) {
+ View view = mActivity.getWindow().getDecorView();
view.playSoundEffect(SoundEffectConstants.CLICK);
}
}
- private void vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType) {
- View view = activity.getWindow().getDecorView();
- switch (feedbackType) {
- case STANDARD:
- view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- break;
- case LIGHT_IMPACT:
- view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
- break;
- case MEDIUM_IMPACT:
- view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
- break;
- case HEAVY_IMPACT:
- // HapticFeedbackConstants.CONTEXT_CLICK from API level 23.
- view.performHapticFeedback(6);
- break;
- case SELECTION_CLICK:
- view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
- break;
+ private void vibrateHapticFeedback(String feedbackType) {
+ View view = mActivity.getWindow().getDecorView();
+ if (feedbackType == null) {
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ } else if (feedbackType.equals("HapticFeedbackType.lightImpact")) {
+ view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ } else if (feedbackType.equals("HapticFeedbackType.mediumImpact")) {
+ view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
+ } else if (feedbackType.equals("HapticFeedbackType.heavyImpact")) {
+ // HapticFeedbackConstants.CONTEXT_CLICK from API level 23.
+ view.performHapticFeedback(6);
+ } else if (feedbackType.equals("HapticFeedbackType.selectionClick")) {
+ view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
}
}
- private void setSystemChromePreferredOrientations(int androidOrientation) {
- activity.setRequestedOrientation(androidOrientation);
+ private void setSystemChromePreferredOrientations(JSONArray orientations) throws JSONException {
+ int requestedOrientation = 0x00;
+ int firstRequestedOrientation = 0x00;
+ for (int index = 0; index < orientations.length(); index += 1) {
+ if (orientations.getString(index).equals("DeviceOrientation.portraitUp")) {
+ requestedOrientation |= 0x01;
+ } else if (orientations.getString(index).equals("DeviceOrientation.landscapeLeft")) {
+ requestedOrientation |= 0x02;
+ } else if (orientations.getString(index).equals("DeviceOrientation.portraitDown")) {
+ requestedOrientation |= 0x04;
+ } else if (orientations.getString(index).equals("DeviceOrientation.landscapeRight")) {
+ requestedOrientation |= 0x08;
+ }
+ if (firstRequestedOrientation == 0x00) {
+ firstRequestedOrientation = requestedOrientation;
+ }
+ }
+ switch (requestedOrientation) {
+ case 0x00:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ break;
+ case 0x01:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ break;
+ case 0x02:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ break;
+ case 0x04:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ break;
+ case 0x05:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT);
+ break;
+ case 0x08:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ break;
+ case 0x0a:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE);
+ break;
+ case 0x0b:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
+ break;
+ case 0x0f:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
+ break;
+ case 0x03: // portraitUp and landscapeLeft
+ case 0x06: // portraitDown and landscapeLeft
+ case 0x07: // portraitUp, portraitDown, and landscapeLeft
+ case 0x09: // portraitUp and landscapeRight
+ case 0x0c: // portraitDown and landscapeRight
+ case 0x0d: // portraitUp, portraitDown, and landscapeRight
+ case 0x0e: // portraitDown, landscapeLeft, and landscapeRight
+ // Android can't describe these cases, so just default to whatever the first
+ // specified value was.
+ switch (firstRequestedOrientation) {
+ case 0x01:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ break;
+ case 0x02:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ break;
+ case 0x04:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ break;
+ case 0x08:
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ break;
+ }
+ break;
+ }
}
- private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description) {
+ private void setSystemChromeApplicationSwitcherDescription(JSONObject description) throws JSONException {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
+ int color = description.getInt("primaryColor");
+ if (color != 0) { // 0 means color isn't set, use system default
+ color = color | 0xFF000000; // color must be opaque if set
+ }
+
+ String label = description.getString("label");
+
@SuppressWarnings("deprecation")
TaskDescription taskDescription = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
- ? new TaskDescription(description.label, 0, description.color)
- : new TaskDescription(description.label, null, description.color);
+ ? new TaskDescription(label, 0, color)
+ : new TaskDescription(label, null, color);
- activity.setTaskDescription(taskDescription);
+ mActivity.setTaskDescription(taskDescription);
}
- private void setSystemChromeEnabledSystemUIOverlays(List overlaysToShow) {
- // Start by assuming we want to hide all system overlays (like an immersive game).
+ private int mEnabledOverlays;
+
+ private void setSystemChromeEnabledSystemUIOverlays(JSONArray overlays) throws JSONException {
int enabledOverlays = DEFAULT_SYSTEM_UI
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- if (overlaysToShow.size() == 0) {
+ if (overlays.length() == 0) {
enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
}
- // Re-add any desired system overlays.
- for (int i = 0; i < overlaysToShow.size(); ++i) {
- PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
- switch (overlayToShow) {
- case TOP_OVERLAYS:
- enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
- break;
- case BOTTOM_OVERLAYS:
- enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
- enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- break;
+ for (int i = 0; i < overlays.length(); ++i) {
+ String overlay = overlays.getString(i);
+ if (overlay.equals("SystemUiOverlay.top")) {
+ enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
+ } else if (overlay.equals("SystemUiOverlay.bottom")) {
+ enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+ enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
}
}
@@ -170,9 +223,9 @@ public class PlatformPlugin implements ActivityLifecycleListener {
}
private void updateSystemUiOverlays(){
- activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
- if (currentTheme != null) {
- setSystemChromeSystemUIOverlayStyle(currentTheme);
+ mActivity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
+ if (mCurrentTheme != null) {
+ setSystemChromeSystemUIOverlayStyle(mCurrentTheme);
}
}
@@ -180,75 +233,83 @@ public class PlatformPlugin implements ActivityLifecycleListener {
updateSystemUiOverlays();
}
- private void setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle) {
- Window window = activity.getWindow();
+ private void setSystemChromeSystemUIOverlayStyle(JSONObject message) {
+ Window window = mActivity.getWindow();
View view = window.getDecorView();
int flags = view.getSystemUiVisibility();
- // You can change the navigation bar color (including translucent colors)
- // in Android, but you can't change the color of the navigation buttons until Android O.
- // LIGHT vs DARK effectively isn't supported until then.
- // Build.VERSION_CODES.O
- if (Build.VERSION.SDK_INT >= 26) {
- if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
- switch (systemChromeStyle.systemNavigationBarIconBrightness) {
- case DARK:
- //View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
- flags |= 0x10;
- break;
- case LIGHT:
- flags &= ~0x10;
- break;
+ try {
+ // You can change the navigation bar color (including translucent colors)
+ // in Android, but you can't change the color of the navigation buttons until Android O.
+ // LIGHT vs DARK effectively isn't supported until then.
+ // Build.VERSION_CODES.O
+ if (Build.VERSION.SDK_INT >= 26) {
+ if (!message.isNull("systemNavigationBarIconBrightness")) {
+ String systemNavigationBarIconBrightness = message.getString("systemNavigationBarIconBrightness");
+ switch (systemNavigationBarIconBrightness) {
+ case "Brightness.dark":
+ //View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ flags |= 0x10;
+ break;
+ case "Brightness.light":
+ flags &= ~0x10;
+ break;
+ }
+ }
+ if (!message.isNull("systemNavigationBarColor")) {
+ window.setNavigationBarColor(message.getInt("systemNavigationBarColor"));
}
}
- if (systemChromeStyle.systemNavigationBarColor != null) {
- window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor);
- }
- }
- // Build.VERSION_CODES.M
- if (Build.VERSION.SDK_INT >= 23) {
- if (systemChromeStyle.statusBarIconBrightness != null) {
- switch (systemChromeStyle.statusBarIconBrightness) {
- case DARK:
- // View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- flags |= 0x2000;
- break;
- case LIGHT:
- flags &= ~0x2000;
- break;
+ // Build.VERSION_CODES.M
+ if (Build.VERSION.SDK_INT >= 23) {
+ if (!message.isNull("statusBarIconBrightness")) {
+ String statusBarIconBrightness = message.getString("statusBarIconBrightness");
+ switch (statusBarIconBrightness) {
+ case "Brightness.dark":
+ // View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ flags |= 0x2000;
+ break;
+ case "Brightness.light":
+ flags &= ~0x2000;
+ break;
+ }
+ }
+ if (!message.isNull("statusBarColor")) {
+ window.setStatusBarColor(message.getInt("statusBarColor"));
}
}
- if (systemChromeStyle.statusBarColor != null) {
- window.setStatusBarColor(systemChromeStyle.statusBarColor);
+ if (!message.isNull("systemNavigationBarDividerColor")) {
+ // Not availible until Android P.
+ // window.setNavigationBarDividerColor(systemNavigationBarDividerColor);
}
+ view.setSystemUiVisibility(flags);
+ mCurrentTheme = message;
+ } catch (JSONException err) {
+ Log.i("PlatformPlugin", err.toString());
}
- if (systemChromeStyle.systemNavigationBarDividerColor != null) {
- // Not availible until Android P.
- // window.setNavigationBarDividerColor(systemNavigationBarDividerColor);
- }
- view.setSystemUiVisibility(flags);
- currentTheme = systemChromeStyle;
}
private void popSystemNavigator() {
- activity.finish();
+ mActivity.finish();
}
- private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) {
- ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ private JSONObject getClipboardData(String format) throws JSONException {
+ ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
if (clip == null)
return null;
- if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) {
- return clip.getItemAt(0).coerceToText(activity);
+ if (format == null || format.equals(kTextPlainFormat)) {
+ JSONObject result = new JSONObject();
+ result.put("text", clip.getItemAt(0).coerceToText(mActivity));
+ return result;
}
return null;
}
- private void setClipboardData(String text) {
- ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText("text label?", text);
+ private void setClipboardData(JSONObject data) throws JSONException {
+ ClipboardManager clipboard = (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("text label?", data.getString("text"));
clipboard.setPrimaryClip(clip);
}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
index ac5e2f8f3d..d8c2de3749 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java
@@ -9,21 +9,20 @@ import android.graphics.Rect;
import android.opengl.Matrix;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
-
-import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
+import io.flutter.plugin.common.BasicMessageChannel;
+import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.util.Predicate;
import java.nio.ByteBuffer;
import java.util.*;
class AccessibilityBridge
- extends AccessibilityNodeProvider {
+ extends AccessibilityNodeProvider implements BasicMessageChannel.MessageHandler {
private static final String TAG = "FlutterView";
// Constants from higher API levels.
@@ -35,42 +34,19 @@ class AccessibilityBridge
private static final float SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0f;
private static final int ROOT_NODE_ID = 0;
- private final FlutterView owner;
- private final AccessibilityChannel accessibilityChannel;
- private final View decorView;
- private Map objects;
- private Map customAccessibilityActions;
- private boolean accessibilityEnabled = false;
- private SemanticsObject a11yFocusedObject;
- private SemanticsObject inputFocusedObject;
- private SemanticsObject hoveredObject;
+ private Map mObjects;
+ private Map mCustomAccessibilityActions;
+ private final FlutterView mOwner;
+ private boolean mAccessibilityEnabled = false;
+ private SemanticsObject mA11yFocusedObject;
+ private SemanticsObject mInputFocusedObject;
+ private SemanticsObject mHoveredObject;
private int previousRouteId = ROOT_NODE_ID;
private List previousRoutes;
- private Integer lastLeftFrameInset = 0;
+ private final View mDecorView;
+ private Integer mLastLeftFrameInset = 0;
- private final AccessibilityChannel.AccessibilityMessageHandler accessibilityMessageHandler = new AccessibilityChannel.AccessibilityMessageHandler() {
- @Override
- public void announce(@NonNull String message) {
- owner.announceForAccessibility(message);
- }
-
- @Override
- public void onTap(int nodeId) {
- sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_CLICKED);
- }
-
- @Override
- public void onLongPress(int nodeId) {
- sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
- }
-
- @Override
- public void onTooltip(@NonNull String message) {
- AccessibilityEvent e = obtainAccessibilityEvent(ROOT_NODE_ID, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- e.getText().add(message);
- sendAccessibilityEvent(e);
- }
- };
+ private final BasicMessageChannel mFlutterAccessibilityChannel;
enum Action {
TAP(1 << 0),
@@ -130,21 +106,23 @@ class AccessibilityBridge
final int value;
}
- AccessibilityBridge(@NonNull FlutterView owner, @NonNull AccessibilityChannel accessibilityChannel) {
- this.owner = owner;
- this.accessibilityChannel = accessibilityChannel;
- decorView = ((Activity) owner.getContext()).getWindow().getDecorView();
- objects = new HashMap<>();
- customAccessibilityActions = new HashMap<>();
+ AccessibilityBridge(FlutterView owner) {
+ assert owner != null;
+ mOwner = owner;
+ mObjects = new HashMap<>();
+ mCustomAccessibilityActions = new HashMap<>();
previousRoutes = new ArrayList<>();
+ mFlutterAccessibilityChannel = new BasicMessageChannel<>(
+ owner, "flutter/accessibility", StandardMessageCodec.INSTANCE);
+ mDecorView = ((Activity) owner.getContext()).getWindow().getDecorView();
}
void setAccessibilityEnabled(boolean accessibilityEnabled) {
- this.accessibilityEnabled = accessibilityEnabled;
+ mAccessibilityEnabled = accessibilityEnabled;
if (accessibilityEnabled) {
- this.accessibilityChannel.setAccessibilityMessageHandler(accessibilityMessageHandler);
+ mFlutterAccessibilityChannel.setMessageHandler(this);
} else {
- this.accessibilityChannel.setAccessibilityMessageHandler(null);
+ mFlutterAccessibilityChannel.setMessageHandler(null);
}
}
@@ -159,42 +137,42 @@ class AccessibilityBridge
// to set it if we're exiting a list to a non-list, so that we can get the "out of list"
// announcement when A11y focus moves out of a list and not into another list.
return object.scrollChildren > 0
- && (hasSemanticsObjectAncestor(a11yFocusedObject, o -> o == object)
- || !hasSemanticsObjectAncestor(a11yFocusedObject, o -> o.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)));
+ && (hasSemanticsObjectAncestor(mA11yFocusedObject, o -> o == object)
+ || !hasSemanticsObjectAncestor(mA11yFocusedObject, o -> o.hasFlag(Flag.HAS_IMPLICIT_SCROLLING)));
}
@Override
@SuppressWarnings("deprecation")
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
if (virtualViewId == View.NO_ID) {
- AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(owner);
- owner.onInitializeAccessibilityNodeInfo(result);
- if (objects.containsKey(ROOT_NODE_ID)) {
- result.addChild(owner, ROOT_NODE_ID);
+ AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mOwner);
+ mOwner.onInitializeAccessibilityNodeInfo(result);
+ if (mObjects.containsKey(ROOT_NODE_ID)) {
+ result.addChild(mOwner, ROOT_NODE_ID);
}
return result;
}
- SemanticsObject object = objects.get(virtualViewId);
+ SemanticsObject object = mObjects.get(virtualViewId);
if (object == null) {
return null;
}
- AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(owner, virtualViewId);
+ AccessibilityNodeInfo result = AccessibilityNodeInfo.obtain(mOwner, virtualViewId);
// Work around for https://github.com/flutter/flutter/issues/2101
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
result.setViewIdResourceName("");
}
- result.setPackageName(owner.getContext().getPackageName());
+ result.setPackageName(mOwner.getContext().getPackageName());
result.setClassName("android.view.View");
- result.setSource(owner, virtualViewId);
+ result.setSource(mOwner, virtualViewId);
result.setFocusable(object.isFocusable());
- if (inputFocusedObject != null) {
- result.setFocused(inputFocusedObject.id == virtualViewId);
+ if (mInputFocusedObject != null) {
+ result.setFocused(mInputFocusedObject.id == virtualViewId);
}
- if (a11yFocusedObject != null) {
- result.setAccessibilityFocused(a11yFocusedObject.id == virtualViewId);
+ if (mA11yFocusedObject != null) {
+ result.setAccessibilityFocused(mA11yFocusedObject.id == virtualViewId);
}
if (object.hasFlag(Flag.IS_TEXT_FIELD)) {
@@ -208,7 +186,7 @@ class AccessibilityBridge
// Text fields will always be created as a live region when they have input focus,
// so that updates to the label trigger polite announcements. This makes it easy to
// follow a11y guidelines for text fields on Android.
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && a11yFocusedObject != null && a11yFocusedObject.id == virtualViewId) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2 && mA11yFocusedObject != null && mA11yFocusedObject.id == virtualViewId) {
result.setLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
}
@@ -261,10 +239,10 @@ class AccessibilityBridge
if (object.parent != null) {
assert object.id > ROOT_NODE_ID;
- result.setParent(owner, object.parent.id);
+ result.setParent(mOwner, object.parent.id);
} else {
assert object.id == ROOT_NODE_ID;
- result.setParent(owner);
+ result.setParent(mOwner);
}
Rect bounds = object.getGlobalRect();
@@ -384,7 +362,7 @@ class AccessibilityBridge
result.setSelected(object.hasFlag(Flag.IS_SELECTED));
// Accessibility Focus
- if (a11yFocusedObject != null && a11yFocusedObject.id == virtualViewId) {
+ if (mA11yFocusedObject != null && mA11yFocusedObject.id == virtualViewId) {
result.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
} else {
result.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
@@ -403,7 +381,7 @@ class AccessibilityBridge
if (object.childrenInTraversalOrder != null) {
for (SemanticsObject child : object.childrenInTraversalOrder) {
if (!child.hasFlag(Flag.IS_HIDDEN)) {
- result.addChild(owner, child.id);
+ result.addChild(mOwner, child.id);
}
}
}
@@ -413,7 +391,7 @@ class AccessibilityBridge
@Override
public boolean performAction(int virtualViewId, int action, Bundle arguments) {
- SemanticsObject object = objects.get(virtualViewId);
+ SemanticsObject object = mObjects.get(virtualViewId);
if (object == null) {
return false;
}
@@ -422,27 +400,27 @@ class AccessibilityBridge
// Note: TalkBack prior to Oreo doesn't use this handler and instead simulates a
// click event at the center of the SemanticsNode. Other a11y services might go
// through this handler though.
- owner.dispatchSemanticsAction(virtualViewId, Action.TAP);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.TAP);
return true;
}
case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
// Note: TalkBack doesn't use this handler and instead simulates a long click event
// at the center of the SemanticsNode. Other a11y services might go through this
// handler though.
- owner.dispatchSemanticsAction(virtualViewId, Action.LONG_PRESS);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.LONG_PRESS);
return true;
}
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
if (object.hasAction(Action.SCROLL_UP)) {
- owner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_UP);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_UP);
} else if (object.hasAction(Action.SCROLL_LEFT)) {
// TODO(ianh): bidi support using textDirection
- owner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_LEFT);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_LEFT);
} else if (object.hasAction(Action.INCREASE)) {
object.value = object.increasedValue;
// Event causes Android to read out the updated value.
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
- owner.dispatchSemanticsAction(virtualViewId, Action.INCREASE);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.INCREASE);
} else {
return false;
}
@@ -450,15 +428,15 @@ class AccessibilityBridge
}
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
if (object.hasAction(Action.SCROLL_DOWN)) {
- owner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_DOWN);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_DOWN);
} else if (object.hasAction(Action.SCROLL_RIGHT)) {
// TODO(ianh): bidi support using textDirection
- owner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_RIGHT);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SCROLL_RIGHT);
} else if (object.hasAction(Action.DECREASE)) {
object.value = object.decreasedValue;
// Event causes Android to read out the updated value.
sendAccessibilityEvent(virtualViewId, AccessibilityEvent.TYPE_VIEW_SELECTED);
- owner.dispatchSemanticsAction(virtualViewId, Action.DECREASE);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.DECREASE);
} else {
return false;
}
@@ -471,24 +449,24 @@ class AccessibilityBridge
return performCursorMoveAction(object, virtualViewId, arguments, true);
}
case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
- owner.dispatchSemanticsAction(virtualViewId, Action.DID_LOSE_ACCESSIBILITY_FOCUS);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.DID_LOSE_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
- a11yFocusedObject = null;
+ mA11yFocusedObject = null;
return true;
}
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
- owner.dispatchSemanticsAction(virtualViewId, Action.DID_GAIN_ACCESSIBILITY_FOCUS);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.DID_GAIN_ACCESSIBILITY_FOCUS);
sendAccessibilityEvent(
virtualViewId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
- if (a11yFocusedObject == null) {
+ if (mA11yFocusedObject == null) {
// When Android focuses a node, it doesn't invalidate the view.
// (It does when it sends ACTION_CLEAR_ACCESSIBILITY_FOCUS, so
// we only have to worry about this when the focused node is null.)
- owner.invalidate();
+ mOwner.invalidate();
}
- a11yFocusedObject = object;
+ mA11yFocusedObject = object;
if (object.hasAction(Action.INCREASE) || object.hasAction(Action.DECREASE)) {
// SeekBars only announce themselves after this event.
@@ -498,7 +476,7 @@ class AccessibilityBridge
return true;
}
case ACTION_SHOW_ON_SCREEN: {
- owner.dispatchSemanticsAction(virtualViewId, Action.SHOW_ON_SCREEN);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SHOW_ON_SCREEN);
return true;
}
case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
@@ -520,32 +498,32 @@ class AccessibilityBridge
selection.put("base", object.textSelectionExtent);
selection.put("extent", object.textSelectionExtent);
}
- owner.dispatchSemanticsAction(virtualViewId, Action.SET_SELECTION, selection);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.SET_SELECTION, selection);
return true;
}
case AccessibilityNodeInfo.ACTION_COPY: {
- owner.dispatchSemanticsAction(virtualViewId, Action.COPY);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.COPY);
return true;
}
case AccessibilityNodeInfo.ACTION_CUT: {
- owner.dispatchSemanticsAction(virtualViewId, Action.CUT);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.CUT);
return true;
}
case AccessibilityNodeInfo.ACTION_PASTE: {
- owner.dispatchSemanticsAction(virtualViewId, Action.PASTE);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.PASTE);
return true;
}
case AccessibilityNodeInfo.ACTION_DISMISS: {
- owner.dispatchSemanticsAction(virtualViewId, Action.DISMISS);
+ mOwner.dispatchSemanticsAction(virtualViewId, Action.DISMISS);
return true;
}
default:
// might be a custom accessibility action.
final int flutterId = action - firstResourceId;
CustomAccessibilityAction contextAction =
- customAccessibilityActions.get(flutterId);
+ mCustomAccessibilityActions.get(flutterId);
if (contextAction != null) {
- owner.dispatchSemanticsAction(
+ mOwner.dispatchSemanticsAction(
virtualViewId, Action.CUSTOM_ACTION, contextAction.id);
return true;
}
@@ -562,12 +540,12 @@ class AccessibilityBridge
switch (granularity) {
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: {
if (forward && object.hasAction(Action.MOVE_CURSOR_FORWARD_BY_CHARACTER)) {
- owner.dispatchSemanticsAction(virtualViewId,
+ mOwner.dispatchSemanticsAction(virtualViewId,
Action.MOVE_CURSOR_FORWARD_BY_CHARACTER, extendSelection);
return true;
}
if (!forward && object.hasAction(Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER)) {
- owner.dispatchSemanticsAction(virtualViewId,
+ mOwner.dispatchSemanticsAction(virtualViewId,
Action.MOVE_CURSOR_BACKWARD_BY_CHARACTER, extendSelection);
return true;
}
@@ -575,12 +553,12 @@ class AccessibilityBridge
}
case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
if (forward && object.hasAction(Action.MOVE_CURSOR_FORWARD_BY_WORD)) {
- owner.dispatchSemanticsAction(virtualViewId,
+ mOwner.dispatchSemanticsAction(virtualViewId,
Action.MOVE_CURSOR_FORWARD_BY_WORD, extendSelection);
return true;
}
if (!forward && object.hasAction(Action.MOVE_CURSOR_BACKWARD_BY_WORD)) {
- owner.dispatchSemanticsAction(virtualViewId,
+ mOwner.dispatchSemanticsAction(virtualViewId,
Action.MOVE_CURSOR_BACKWARD_BY_WORD, extendSelection);
return true;
}
@@ -595,65 +573,65 @@ class AccessibilityBridge
public AccessibilityNodeInfo findFocus(int focus) {
switch (focus) {
case AccessibilityNodeInfo.FOCUS_INPUT: {
- if (inputFocusedObject != null)
- return createAccessibilityNodeInfo(inputFocusedObject.id);
+ if (mInputFocusedObject != null)
+ return createAccessibilityNodeInfo(mInputFocusedObject.id);
}
// Fall through to check FOCUS_ACCESSIBILITY
case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
- if (a11yFocusedObject != null)
- return createAccessibilityNodeInfo(a11yFocusedObject.id);
+ if (mA11yFocusedObject != null)
+ return createAccessibilityNodeInfo(mA11yFocusedObject.id);
}
}
return null;
}
private SemanticsObject getRootObject() {
- assert objects.containsKey(0);
- return objects.get(0);
+ assert mObjects.containsKey(0);
+ return mObjects.get(0);
}
private SemanticsObject getOrCreateObject(int id) {
- SemanticsObject object = objects.get(id);
+ SemanticsObject object = mObjects.get(id);
if (object == null) {
object = new SemanticsObject();
object.id = id;
- objects.put(id, object);
+ mObjects.put(id, object);
}
return object;
}
private CustomAccessibilityAction getOrCreateAction(int id) {
- CustomAccessibilityAction action = customAccessibilityActions.get(id);
+ CustomAccessibilityAction action = mCustomAccessibilityActions.get(id);
if (action == null) {
action = new CustomAccessibilityAction();
action.id = id;
action.resourceId = id + firstResourceId;
- customAccessibilityActions.put(id, action);
+ mCustomAccessibilityActions.put(id, action);
}
return action;
}
void handleTouchExplorationExit() {
- if (hoveredObject != null) {
- sendAccessibilityEvent(hoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
- hoveredObject = null;
+ if (mHoveredObject != null) {
+ sendAccessibilityEvent(mHoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ mHoveredObject = null;
}
}
void handleTouchExploration(float x, float y) {
- if (objects.isEmpty()) {
+ if (mObjects.isEmpty()) {
return;
}
SemanticsObject newObject = getRootObject().hitTest(new float[] {x, y, 0, 1});
- if (newObject != hoveredObject) {
+ if (newObject != mHoveredObject) {
// sending ENTER before EXIT is how Android wants it
if (newObject != null) {
sendAccessibilityEvent(newObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
}
- if (hoveredObject != null) {
- sendAccessibilityEvent(hoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ if (mHoveredObject != null) {
+ sendAccessibilityEvent(mHoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
}
- hoveredObject = newObject;
+ mHoveredObject = newObject;
}
}
@@ -679,7 +657,7 @@ class AccessibilityBridge
continue;
}
if (object.hasFlag(Flag.IS_FOCUSED)) {
- inputFocusedObject = object;
+ mInputFocusedObject = object;
}
if (object.hadPreviousConfig) {
updated.add(object);
@@ -697,12 +675,12 @@ class AccessibilityBridge
// a11y nodes.
if (Build.VERSION.SDK_INT >= 23) {
Rect visibleFrame = new Rect();
- decorView.getWindowVisibleDisplayFrame(visibleFrame);
- if (!lastLeftFrameInset.equals(visibleFrame.left)) {
+ mDecorView.getWindowVisibleDisplayFrame(visibleFrame);
+ if (!mLastLeftFrameInset.equals(visibleFrame.left)) {
rootObject.globalGeometryDirty = true;
rootObject.inverseTransformDirty = true;
}
- lastLeftFrameInset = visibleFrame.left;
+ mLastLeftFrameInset = visibleFrame.left;
Matrix.translateM(identity, 0, visibleFrame.left, 0, 0);
}
rootObject.updateRecursively(identity, visitedObjects, false);
@@ -729,7 +707,7 @@ class AccessibilityBridge
previousRoutes.add(semanticsObject.id);
}
- Iterator> it = objects.entrySet().iterator();
+ Iterator> it = mObjects.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
SemanticsObject object = entry.getValue();
@@ -809,25 +787,25 @@ class AccessibilityBridge
sendAccessibilityEvent(object.id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
} else if (object.hasFlag(Flag.IS_TEXT_FIELD) && object.didChangeLabel()
- && inputFocusedObject != null && inputFocusedObject.id == object.id) {
+ && mInputFocusedObject != null && mInputFocusedObject.id == object.id) {
// Text fields should announce when their label changes while focused. We use a live
// region tag to do so, and this event triggers that update.
sendAccessibilityEvent(object.id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
- if (a11yFocusedObject != null && a11yFocusedObject.id == object.id
+ if (mA11yFocusedObject != null && mA11yFocusedObject.id == object.id
&& !object.hadFlag(Flag.IS_SELECTED) && object.hasFlag(Flag.IS_SELECTED)) {
AccessibilityEvent event =
obtainAccessibilityEvent(object.id, AccessibilityEvent.TYPE_VIEW_SELECTED);
event.getText().add(object.label);
sendAccessibilityEvent(event);
}
- if (inputFocusedObject != null && inputFocusedObject.id == object.id
+ if (mInputFocusedObject != null && mInputFocusedObject.id == object.id
&& object.hadFlag(Flag.IS_TEXT_FIELD) && object.hasFlag(Flag.IS_TEXT_FIELD)
// If we have a TextField that has InputFocus, we should avoid announcing it if something
// else we track has a11y focus. This needs to still work when, e.g., IME has a11y focus
// or the "PASTE" popup is used though.
// See more discussion at https://github.com/flutter/flutter/issues/23180
- && (a11yFocusedObject == null || (a11yFocusedObject.id == inputFocusedObject.id))) {
+ && (mA11yFocusedObject == null || (mA11yFocusedObject.id == mInputFocusedObject.id))) {
String oldValue = object.previousValue != null ? object.previousValue : "";
String newValue = object.value != null ? object.value : "";
AccessibilityEvent event = createTextChangedEvent(object.id, oldValue, newValue);
@@ -885,27 +863,65 @@ class AccessibilityBridge
private AccessibilityEvent obtainAccessibilityEvent(int virtualViewId, int eventType) {
assert virtualViewId != ROOT_NODE_ID;
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.setPackageName(owner.getContext().getPackageName());
- event.setSource(owner, virtualViewId);
+ event.setPackageName(mOwner.getContext().getPackageName());
+ event.setSource(mOwner, virtualViewId);
return event;
}
private void sendAccessibilityEvent(int virtualViewId, int eventType) {
- if (!accessibilityEnabled) {
+ if (!mAccessibilityEnabled) {
return;
}
if (virtualViewId == ROOT_NODE_ID) {
- owner.sendAccessibilityEvent(eventType);
+ mOwner.sendAccessibilityEvent(eventType);
} else {
sendAccessibilityEvent(obtainAccessibilityEvent(virtualViewId, eventType));
}
}
private void sendAccessibilityEvent(AccessibilityEvent event) {
- if (!accessibilityEnabled) {
+ if (!mAccessibilityEnabled) {
return;
}
- owner.getParent().requestSendAccessibilityEvent(owner, event);
+ mOwner.getParent().requestSendAccessibilityEvent(mOwner, event);
+ }
+
+ // Message Handler for [mFlutterAccessibilityChannel].
+ public void onMessage(Object message, BasicMessageChannel.Reply reply) {
+ @SuppressWarnings("unchecked")
+ final HashMap annotatedEvent = (HashMap) message;
+ final String type = (String) annotatedEvent.get("type");
+ @SuppressWarnings("unchecked")
+ final HashMap data = (HashMap) annotatedEvent.get("data");
+
+ switch (type) {
+ case "announce":
+ mOwner.announceForAccessibility((String) data.get("message"));
+ break;
+ case "longPress": {
+ Integer nodeId = (Integer) annotatedEvent.get("nodeId");
+ if (nodeId == null) {
+ return;
+ }
+ sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ break;
+ }
+ case "tap": {
+ Integer nodeId = (Integer) annotatedEvent.get("nodeId");
+ if (nodeId == null) {
+ return;
+ }
+ sendAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ break;
+ }
+ case "tooltip": {
+ AccessibilityEvent e = obtainAccessibilityEvent(
+ ROOT_NODE_ID, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ e.getText().add((String) data.get("message"));
+ sendAccessibilityEvent(e);
+ break;
+ }
+ }
}
private void createWindowChangeEvent(SemanticsObject route) {
@@ -917,29 +933,29 @@ class AccessibilityBridge
}
private void willRemoveSemanticsObject(SemanticsObject object) {
- assert objects.containsKey(object.id);
- assert objects.get(object.id) == object;
+ assert mObjects.containsKey(object.id);
+ assert mObjects.get(object.id) == object;
object.parent = null;
- if (a11yFocusedObject == object) {
- sendAccessibilityEvent(a11yFocusedObject.id,
+ if (mA11yFocusedObject == object) {
+ sendAccessibilityEvent(mA11yFocusedObject.id,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
- a11yFocusedObject = null;
+ mA11yFocusedObject = null;
}
- if (inputFocusedObject == object) {
- inputFocusedObject = null;
+ if (mInputFocusedObject == object) {
+ mInputFocusedObject = null;
}
- if (hoveredObject == object) {
- hoveredObject = null;
+ if (mHoveredObject == object) {
+ mHoveredObject = null;
}
}
void reset() {
- objects.clear();
- if (a11yFocusedObject != null)
- sendAccessibilityEvent(a11yFocusedObject.id,
+ mObjects.clear();
+ if (mA11yFocusedObject != null)
+ sendAccessibilityEvent(mA11yFocusedObject.id,
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
- a11yFocusedObject = null;
- hoveredObject = null;
+ mA11yFocusedObject = null;
+ mHoveredObject = null;
sendAccessibilityEvent(0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java
index b76c1fee83..c6927a3245 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java
@@ -15,7 +15,6 @@ import android.graphics.SurfaceTexture;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
-import android.os.LocaleList;
import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
@@ -30,18 +29,17 @@ import io.flutter.app.FlutterPluginRegistry;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.android.AndroidKeyProcessor;
import io.flutter.embedding.engine.dart.DartExecutor;
-import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
-import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
-import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.plugin.common.*;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformPlugin;
+import org.json.JSONException;
+import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
@@ -89,21 +87,18 @@ public class FlutterView extends SurfaceView
}
private final DartExecutor dartExecutor;
- private final AccessibilityChannel accessibilityChannel;
private final NavigationChannel navigationChannel;
private final KeyEventChannel keyEventChannel;
private final LifecycleChannel lifecycleChannel;
- private final LocalizationChannel localizationChannel;
- private final PlatformChannel platformChannel;
private final SettingsChannel settingsChannel;
private final SystemChannel systemChannel;
private final InputMethodManager mImm;
private final TextInputPlugin mTextInputPlugin;
private final AndroidKeyProcessor androidKeyProcessor;
- private AccessibilityBridge mAccessibilityNodeProvider;
private final SurfaceHolder.Callback mSurfaceCallback;
private final ViewportMetrics mMetrics;
private final AccessibilityManager mAccessibilityManager;
+ private final MethodChannel mFlutterLocalizationChannel;
private final List mActivityLifecycleListeners;
private final List mFirstFrameListeners;
private final AtomicLong nextTextureId = new AtomicLong(0L);
@@ -165,25 +160,23 @@ public class FlutterView extends SurfaceView
mActivityLifecycleListeners = new ArrayList<>();
mFirstFrameListeners = new ArrayList<>();
- // Create all platform channels
- accessibilityChannel = new AccessibilityChannel(dartExecutor);
+ // Configure the platform plugins and flutter channels.
navigationChannel = new NavigationChannel(dartExecutor);
keyEventChannel = new KeyEventChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
- localizationChannel = new LocalizationChannel(dartExecutor);
- platformChannel = new PlatformChannel(dartExecutor);
systemChannel = new SystemChannel(dartExecutor);
settingsChannel = new SettingsChannel(dartExecutor);
+ mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE);
- // Create and setup plugins
- PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel);
+ PlatformPlugin platformPlugin = new PlatformPlugin(activity);
+ MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
+ flutterPlatformChannel.setMethodCallHandler(platformPlugin);
addActivityLifecycleListener(platformPlugin);
mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- mTextInputPlugin = new TextInputPlugin(this, dartExecutor);
+ mTextInputPlugin = new TextInputPlugin(this);
androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel);
- // Send initial platform information to Dart
- sendLocalesToDart(getResources().getConfiguration());
+ setLocales(getResources().getConfiguration());
sendUserPlatformSettingsToDart();
}
@@ -318,21 +311,39 @@ public class FlutterView extends SurfaceView
.send();
}
- private void sendLocalesToDart(Configuration config) {
- LocaleList localeList = config.getLocales();
- int localeCount = localeList.size();
- List locales = new ArrayList<>();
- for (int index = 0; index < localeCount; ++index) {
- Locale locale = localeList.get(index);
- locales.add(locale);
+ private void setLocales(Configuration config) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ try {
+ // Passes the full list of locales for android API >= 24 with reflection.
+ Object localeList = config.getClass().getDeclaredMethod("getLocales").invoke(config);
+ Method localeListGet = localeList.getClass().getDeclaredMethod("get", int.class);
+ Method localeListSize = localeList.getClass().getDeclaredMethod("size");
+ int localeCount = (int)localeListSize.invoke(localeList);
+ List data = new ArrayList<>();
+ for (int index = 0; index < localeCount; ++index) {
+ Locale locale = (Locale)localeListGet.invoke(localeList, index);
+ data.add(locale.getLanguage());
+ data.add(locale.getCountry());
+ data.add(locale.getScript());
+ data.add(locale.getVariant());
+ }
+ mFlutterLocalizationChannel.invokeMethod("setLocale", data);
+ return;
+ } catch (Exception exception) {
+ // Any exception is a failure. Resort to fallback of sending only one locale.
+ }
}
- localizationChannel.sendLocales(locales);
+ // Fallback single locale passing for android API < 24. Should work always.
+ @SuppressWarnings("deprecation")
+ Locale locale = config.locale;
+ // getScript() is gated because it is added in API 21.
+ mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry(), Build.VERSION.SDK_INT >= 21 ? locale.getScript() : "", locale.getVariant()));
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- sendLocalesToDart(newConfig);
+ setLocales(newConfig);
sendUserPlatformSettingsToDart();
}
@@ -363,7 +374,13 @@ public class FlutterView extends SurfaceView
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- return mTextInputPlugin.createInputConnection(this, outAttrs);
+ try {
+ mLastInputConnection = mTextInputPlugin.createInputConnection(this, outAttrs);
+ return mLastInputConnection;
+ } catch (JSONException e) {
+ Log.e(TAG, "Failed to create input connection", e);
+ return null;
+ }
}
// Must match the PointerChange enum in pointer.dart.
@@ -989,12 +1006,14 @@ public class FlutterView extends SurfaceView
return null;
}
+ private AccessibilityBridge mAccessibilityNodeProvider;
+
void ensureAccessibilityEnabled() {
if (!isAttached())
return;
mAccessibilityEnabled = true;
if (mAccessibilityNodeProvider == null) {
- mAccessibilityNodeProvider = new AccessibilityBridge(this, accessibilityChannel);
+ mAccessibilityNodeProvider = new AccessibilityBridge(this);
}
mNativeView.getFlutterJNI().setSemanticsEnabled(true);
mAccessibilityNodeProvider.setAccessibilityEnabled(true);