From 124d2ffe127c3309fd38ad6598c5a21086033877 Mon Sep 17 00:00:00 2001 From: matthew-carroll Date: Wed, 19 Sep 2018 13:12:56 -0700 Subject: [PATCH] Get rid of FlutterNativeView, introduce FlutterEngine and FlutterRenderer (#6195) * Copied over FlutterView. Copied a number of dependencies due to package change. Grouped related methods within FlutterView. Deleted a few unused methods. * Moved BinaryMessenger out of FlutterView and into FlutterEngine, moved native calls from FlutterView to FlutterEngine. * Introduced concept of a FlutterRenderer. It doesn't have any meaningful behavior yet. Need strategy for resolving FlutterNativeView vs FlutterEngine first. * Removed discovery behavior from FlutterView to match recent PR: https://github.com/flutter/engine/pull/6157 * Replaced FlutterNativeView with FlutterEngine and FlutterRenderer. Eliminated FlutterEngine references from within FlutterView. Centralized all JNI calls within a FlutterJNI and updated the C side accordingly. * Added comments to FlutterEngine, FlutterRender, FlutterFragment, and FlutterView. * Removed some extraneous code. Adjusted first frame notification slightly. * Fixed formatting in platform_view_android_jni.cc * Update licenses file. * Exposed FlutterEngine from plugin system. Updated some access modifiers as needed. * PR Updates. --- ci/licenses_golden/licenses_flutter | 4 +- shell/platform/android/BUILD.gn | 18 + .../io/flutter/embedding/FlutterActivity.java | 12 + .../io/flutter/embedding/FlutterEngine.java | 262 +++ .../io/flutter/embedding/FlutterFragment.java | 189 +- .../io/flutter/embedding/FlutterJNI.java | 104 + .../io/flutter/embedding/FlutterRenderer.java | 285 +++ .../io/flutter/embedding/FlutterView.java | 1721 +++++++---------- .../embedding/legacy/AccessibilityBridge.java | 37 +- .../embedding/legacy/FlutterNativeView.java | 262 --- .../legacy/FlutterPluginRegistry.java | 46 +- .../legacy/InputConnectionAdaptor.java | 8 +- .../embedding/legacy/PlatformViewFactory.java | 1 - .../legacy/PlatformViewRegistry.java | 1 - .../legacy/PlatformViewsController.java | 47 +- .../embedding/legacy/PluginRegistry.java | 14 +- .../legacy/SingleViewPresentation.java | 9 +- .../embedding/legacy/TextInputPlugin.java | 22 +- .../legacy/VirtualDisplayController.java | 9 +- .../android/platform_view_android_jni.cc | 346 +++- 20 files changed, 1943 insertions(+), 1454 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/FlutterEngine.java create mode 100644 shell/platform/android/io/flutter/embedding/FlutterJNI.java create mode 100644 shell/platform/android/io/flutter/embedding/FlutterRenderer.java delete mode 100644 shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index a7ab486700a9b..27d26bc38c9cc 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -93,7 +93,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDele FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEvents.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java @@ -202,6 +201,9 @@ FILE: ../../../flutter/lib/ui/natives.dart FILE: ../../../flutter/lib/ui/window/window.h FILE: ../../../flutter/shell/common/skia_event_tracer_impl.cc FILE: ../../../flutter/shell/platform/android/apk_asset_provider.cc +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterEngine.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterJNI.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/FlutterRenderer.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/JSONUtil.java FILE: ../../../flutter/shell/platform/darwin/desktop/Info.plist FILE: ../../../flutter/shell/platform/darwin/ios/framework/Flutter.podspec diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index f332a60f7a97d..1e526a5ae95e1 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -97,6 +97,24 @@ java_library("flutter_shell_java") { supports_android = true java_files = [ + "io/flutter/embedding/FlutterActivity.java", + "io/flutter/embedding/FlutterEngine.java", + "io/flutter/embedding/FlutterFragment.java", + "io/flutter/embedding/FlutterJNI.java", + "io/flutter/embedding/FlutterRenderer.java", + "io/flutter/embedding/FlutterShellArgs.java", + "io/flutter/embedding/FlutterView.java", + "io/flutter/embedding/legacy/AccessibilityBridge.java", + "io/flutter/embedding/legacy/FlutterPluginRegistry.java", + "io/flutter/embedding/legacy/InputConnectionAdaptor.java", + "io/flutter/embedding/legacy/PlatformViewFactory.java", + "io/flutter/embedding/legacy/PlatformViewRegistry.java", + "io/flutter/embedding/legacy/PlatformViewRegistryImpl.java", + "io/flutter/embedding/legacy/PlatformViewsController.java", + "io/flutter/embedding/legacy/PluginRegistry.java", + "io/flutter/embedding/legacy/SingleViewPresentation.java", + "io/flutter/embedding/legacy/TextInputPlugin.java", + "io/flutter/embedding/legacy/VirtualDisplayController.java", "io/flutter/app/FlutterActivity.java", "io/flutter/app/FlutterActivityDelegate.java", "io/flutter/app/FlutterActivityEvents.java", diff --git a/shell/platform/android/io/flutter/embedding/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/FlutterActivity.java index 4fed2df6411af..61e8ea79708fa 100644 --- a/shell/platform/android/io/flutter/embedding/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/FlutterActivity.java @@ -16,11 +16,13 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; +import io.flutter.embedding.legacy.PluginRegistry; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; @@ -65,6 +67,7 @@ public class FlutterActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate()"); FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent()); // TODO(mattcarroll): Change FlutterMain to accept FlutterShellArgs and move additional constants in @@ -79,6 +82,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onPostResume() { super.onPostResume(); + Log.d(TAG, "onPostResume()"); flutterFragment.onPostResume(); } @@ -90,6 +94,7 @@ protected void onNewIntent(Intent intent) { @Override public void onBackPressed() { + Log.d(TAG, "onBackPressed()"); flutterFragment.onBackPressed(); } @@ -102,11 +107,17 @@ public void onUserLeaveHint() { flutterFragment.onUserLeaveHint(); } + @Nullable + protected FlutterEngine getFlutterEngine() { + return flutterFragment.getFlutterEngine(); + } + /** * Sets up a {@code FrameLayout} that takes up all available space in the {@code Activity}. This * {@code FrameLayout} will hold a {@code FlutterFragment}, which displays a {@code FlutterView}. */ private void createFlutterFragmentContainer() { + Log.d(TAG, "createFlutterFragmentContainer()"); FrameLayout container = new FrameLayout(this); container.setId(CONTAINER_ID); container.setLayoutParams(new ViewGroup.LayoutParams( @@ -122,6 +133,7 @@ private void createFlutterFragmentContainer() { * a reference to that {@code FlutterFragment} is retained in {@code flutterFragment}. */ private void createFlutterFragment() { + Log.d(TAG, "createFlutterFragment()"); FragmentManager fragmentManager = getFragmentManager(); flutterFragment = (FlutterFragment) fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT); diff --git a/shell/platform/android/io/flutter/embedding/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/FlutterEngine.java new file mode 100644 index 0000000000000..dce292bc1225c --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/FlutterEngine.java @@ -0,0 +1,262 @@ +package io.flutter.embedding; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.flutter.embedding.legacy.FlutterPluginRegistry; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.view.FlutterRunArguments; + +/** + * A single Flutter execution environment. + * + * A {@code FlutterEngine} can execute in the background, or it can be rendered to the screen by + * using the accompanying {@link FlutterRenderer}. Rendering can be started and stopped, thus + * allowing a {@code FlutterEngine} to move from UI interaction to data-only processing and then + * back to UI interaction. + * + * To start running Flutter within this {@code FlutterEngine}, use {@link #runFromBundle(FlutterRunArguments)}. + * The {@link #runFromBundle(FlutterRunArguments)} method must not be invoked twice on the same + * {@code FlutterEngine}. + * + * To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a + * {@link FlutterRenderer} and then attach a {@link FlutterRenderer.RenderSurface}. Consider using + * a {@link FlutterView} as a {@link FlutterRenderer.RenderSurface}. + * TODO(mattcarroll): for the above instructions for RenderSurface to be true, we need to refactor + * the native relationships and also the PlatformViewsController relationship. + */ +public class FlutterEngine implements BinaryMessenger { + private static final String TAG = "FlutterEngine"; + + private final Resources resources; + private final FlutterJNI flutterJNI; + private final FlutterRenderer renderer; + private final FlutterPluginRegistry pluginRegistry; + private final Map mMessageHandlers; + private final Map mPendingReplies = new HashMap<>(); + private long nativeObjectReference; + private boolean isBackgroundView; // TODO(mattcarroll): rename to something without "view" + private boolean applicationIsRunning; + private int mNextReplyId = 1; + + public FlutterEngine( + Context context, + Resources resources, + boolean isBackgroundView + ) { + this.flutterJNI = new FlutterJNI(); + this.resources = resources; + this.isBackgroundView = isBackgroundView; + pluginRegistry = new FlutterPluginRegistry(this, context); + mMessageHandlers = new HashMap<>(); + + attach(); + // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if possible. + this.renderer = new FlutterRenderer(flutterJNI, nativeObjectReference); + } + + private void attach() { + // TODO(mattcarroll): what impact does "isBackgroundView' have? + nativeObjectReference = flutterJNI.nativeAttach(this, isBackgroundView); + + if (!isAttached()) { + throw new RuntimeException("FlutterEngine failed to attach to its native Object reference."); + } + } + + private void assertAttached() { + if (!isAttached()) throw new AssertionError("Platform view is not attached"); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean isAttached() { + return nativeObjectReference != 0; + } + + public void detach() { + pluginRegistry.detach(); + // TODO(mattcarroll): why do we have a nativeDetach() method? can we get rid of this? + flutterJNI.nativeDetach(nativeObjectReference); + } + + public void destroy() { + pluginRegistry.destroy(); + flutterJNI.nativeDestroy(nativeObjectReference); + nativeObjectReference = 0; + applicationIsRunning = false; + } + + public void runFromBundle(FlutterRunArguments args) { + if (args.bundlePath == null) { + throw new AssertionError("A bundlePath must be specified"); + } else if (args.entrypoint == null) { + throw new AssertionError("An entrypoint must be specified"); + } + runFromBundleInternal(args.bundlePath, args.entrypoint, args.libraryPath, args.defaultPath); + } + + private void runFromBundleInternal( + String bundlePath, + String entrypoint, + String libraryPath, + String defaultPath + ) { + assertAttached(); + + if (applicationIsRunning) { + throw new AssertionError("This Flutter engine instance is already running an application"); + } + + flutterJNI.nativeRunBundleAndSnapshotFromLibrary(nativeObjectReference, bundlePath, + defaultPath, entrypoint, libraryPath, resources.getAssets()); + + applicationIsRunning = true; + } + + public boolean isApplicationRunning() { + return applicationIsRunning; + } + + // TODO(mattcarroll): what does this callback actually represent? + // Called by native to notify when the engine is restarted (cold reload). + @SuppressWarnings("unused") + private void onPreEngineRestart() { + if (pluginRegistry == null) + return; + pluginRegistry.onPreEngineRestart(); + } + + public FlutterRenderer getRenderer() { + return renderer; + } + + public FlutterPluginRegistry getPluginRegistry() { + return pluginRegistry; + } + + // TODO(mattcarroll): Is this method really all about plugins? or does it have other implications? + public void attachViewAndActivity(Activity activity) { + pluginRegistry.attach(this, activity); + } + + // TODO(mattcarroll): Implement observatory lookup for "flutter attach" + public String getObservatoryUrl() { + return flutterJNI.nativeGetObservatoryUri(); + } + + //------- START ISOLATE METHOD CHANNEL COMMS ------ + @Override + public void setMessageHandler(String channel, BinaryMessageHandler handler) { + if (handler == null) { + mMessageHandlers.remove(channel); + } else { + mMessageHandlers.put(channel, handler); + } + } + + @Override + public void send(String channel, ByteBuffer message) { + send(channel, message, null); + } + + @Override + public void send(String channel, ByteBuffer message, BinaryReply callback) { + if (!isAttached()) { + Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); + return; + } + + int replyId = 0; + if (callback != null) { + replyId = mNextReplyId++; + mPendingReplies.put(replyId, callback); + } + if (message == null) { + flutterJNI.nativeDispatchEmptyPlatformMessage(nativeObjectReference, channel, replyId); + } else { + flutterJNI.nativeDispatchPlatformMessage( + nativeObjectReference, channel, message, message.position(), replyId); + } + } + + // Called by native to send us a platform message. + @SuppressWarnings("unused") + private void handlePlatformMessage(final String channel, byte[] message, final int replyId) { + assertAttached(); + BinaryMessageHandler handler = mMessageHandlers.get(channel); + if (handler != null) { + try { + final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message)); + handler.onMessage(buffer, new BinaryReply() { + private final AtomicBoolean done = new AtomicBoolean(false); + @Override + public void reply(ByteBuffer reply) { + if (!isAttached()) { + Log.d(TAG, + "handlePlatformMessage replying to a detached view, channel=" + + channel); + return; + } + if (done.getAndSet(true)) { + throw new IllegalStateException("Reply already submitted"); + } + if (reply == null) { + flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId); + } else { + flutterJNI.nativeInvokePlatformMessageResponseCallback(nativeObjectReference, replyId, reply, reply.position()); + } + } + }); + } catch (Exception ex) { + Log.e(TAG, "Uncaught exception in binary message listener", ex); + flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId); + } + return; + } + flutterJNI.nativeInvokePlatformMessageEmptyResponseCallback(nativeObjectReference, replyId); + } + + // Called by native to respond to a platform message that we sent. + @SuppressWarnings("unused") + private void handlePlatformMessageResponse(int replyId, byte[] reply) { + BinaryReply callback = mPendingReplies.remove(replyId); + if (callback != null) { + try { + callback.reply(reply == null ? null : ByteBuffer.wrap(reply)); + } catch (Exception ex) { + Log.e(TAG, "Uncaught exception in binary message reply handler", ex); + } + } + } + //------- END ISOLATE METHOD CHANNEL COMMS ----- + + //------ START NATIVE CALLBACKS THAT SHOULD BE MOVED TO FlutterRenderer ------ + // Called by native to update the semantics/accessibility tree. + @SuppressWarnings("unused") + private void updateSemantics(ByteBuffer buffer, String[] strings) { + Log.d(TAG, "updateSemantics()"); + renderer.updateSemantics(buffer, strings); + } + + // Called by native to update the custom accessibility actions. + @SuppressWarnings("unused") + private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { + Log.d(TAG, "updateCustomAccessibilityActions()"); + renderer.updateCustomAccessibilityActions(buffer, strings); + } + + // Called by native to notify first Flutter frame rendered. + @SuppressWarnings("unused") + private void onFirstFrame() { + Log.d(TAG, "onFirstFrame()"); + renderer.onFirstFrameRendered(); + } + //------ END NATIVE CALLBACKS THAT SHOULD BE MOVED TO FlutterRenderer ------ +} diff --git a/shell/platform/android/io/flutter/embedding/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/FlutterFragment.java index 7f7360f2135d1..ea8f3a4d76491 100644 --- a/shell/platform/android/io/flutter/embedding/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/FlutterFragment.java @@ -13,6 +13,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -25,11 +26,17 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; -import io.flutter.app.BuildConfig; -import io.flutter.app.FlutterActivity; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterView; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.JSONMessageCodec; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StringCodec; +import io.flutter.plugin.platform.PlatformPlugin; +import io.flutter.view.FlutterRunArguments; /** * {@code Fragment} which displays a {@link FlutterView} that takes up all available space. @@ -62,7 +69,7 @@ */ @SuppressWarnings("WeakerAccess") @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) -public class FlutterFragment extends Fragment implements PluginRegistry { +public class FlutterFragment extends Fragment { private static final String TAG = "FlutterFragment"; private static final String ARG_IS_SPLASH_SCREEN_DESIRED = "show_splash_screen"; @@ -86,15 +93,22 @@ public static FlutterFragment newInstance(boolean isSplashScreenDesired, return frag; } + private FlutterEngine flutterEngine; private FrameLayout container; private FlutterView flutterView; + private PlatformPlugin platformPlugin; + private BasicMessageChannel mFlutterLifecycleChannel; + private BasicMessageChannel mFlutterSystemChannel; + private MethodChannel mFlutterNavigationChannel; private View launchView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { + Log.e(TAG, "onCreateView()"); createLayout(); - // TODO: Should we start running the FlutterView here or when attached? It was being done here + // TODO: Should we start running the FlutterView here or when attached to window? It was being done here // in the Activity, but maybe that was a problem to begin with? + flutterView.attachToFlutterRenderer(flutterEngine.getRenderer(), flutterEngine); doInitialFlutterViewRun(); return container; @@ -103,45 +117,45 @@ public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle saved @Override public void onStart() { super.onStart(); - flutterView.onStart(); - } - - @Override - public void onResume() { - super.onResume(); - // TODO: should flutterView have an onResume() method? + Log.d(TAG, "onStart()"); + mFlutterLifecycleChannel.send("AppLifecycleState.inactive"); } public void onPostResume() { - flutterView.onPostResume(); + Log.d(TAG, "onPostResume()"); + flutterView.updateAccessibilityFeatures(); + platformPlugin.onPostResume(); + mFlutterLifecycleChannel.send("AppLifecycleState.resumed"); } @Override public void onPause() { super.onPause(); - flutterView.onPause(); + Log.d(TAG, "onPause()"); + mFlutterLifecycleChannel.send("AppLifecycleState.inactive"); } @Override public void onStop() { super.onStop(); - flutterView.onStop(); + Log.d(TAG, "onStop()"); + mFlutterLifecycleChannel.send("AppLifecycleState.paused"); } @Override public void onDestroy() { super.onDestroy(); + Log.d(TAG, "onDestroy()"); // TODO(mattcarroll): re-evaluate how Flutter plugins interact with FlutterView and FlutterNativeView - final boolean detach = flutterView.getPluginRegistry().onViewDestroy( - flutterView.getFlutterNativeView() - ); + final boolean detach = flutterEngine.getPluginRegistry().onViewDestroy(flutterEngine); + flutterView.detachFromFlutterRenderer(); if (detach || retainFlutterIsolateAfterFragmentDestruction()) { // Detach, but do not destroy the FlutterView if a plugin expressed interest in its // FlutterNativeView. - flutterView.detach(); + flutterEngine.detach(); } else { - flutterView.destroy(); + flutterEngine.destroy(); } } @@ -151,7 +165,8 @@ public void onDestroy() { * See {@link Activity#onBackPressed()} */ public void onBackPressed() { - flutterView.popRoute(); + Log.d(TAG, "onBackPressed()"); + popRoute(); } /** @@ -164,7 +179,7 @@ public void onBackPressed() { * @param grantResults permission grants or denials */ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - flutterView.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults); + flutterEngine.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults); } /** @@ -175,12 +190,12 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis * @param intent new Intent */ public void onNewIntent(@NonNull Intent intent) { - flutterView.getPluginRegistry().onNewIntent(intent); + flutterEngine.getPluginRegistry().onNewIntent(intent); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - flutterView.getPluginRegistry().onActivityResult(requestCode, resultCode, data); + flutterEngine.getPluginRegistry().onActivityResult(requestCode, resultCode, data); } /** @@ -190,7 +205,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { * See {@link Activity#onUserLeaveHint()} */ public void onUserLeaveHint() { - flutterView.getPluginRegistry().onUserLeaveHint(); + flutterEngine.getPluginRegistry().onUserLeaveHint(); } @Override @@ -200,14 +215,20 @@ public void onTrimMemory(int level) { // Use a trim level delivered while the application is running so the // framework has a chance to react to the notification. if (level == TRIM_MEMORY_RUNNING_LOW) { - flutterView.onMemoryPressure(); + sendMemoryPressureWarningToFlutter(); } } @Override public void onLowMemory() { super.onLowMemory(); - flutterView.onMemoryPressure(); + sendMemoryPressureWarningToFlutter(); + } + + private void sendMemoryPressureWarningToFlutter() { + Map message = new HashMap<>(1); + message.put("type", "memoryPressure"); + mFlutterSystemChannel.send(message); } /** @@ -235,22 +256,37 @@ private void createLayout() { */ @NonNull protected FlutterView createFlutterView(@NonNull Activity activity) { - FlutterNativeView nativeView = createFlutterNativeView(activity); - return new FlutterView(activity, null, nativeView); + Log.d(TAG, "createFlutterView()"); + flutterEngine = createFlutterEngine(activity); + + platformPlugin = new PlatformPlugin(activity); + MethodChannel flutterPlatformChannel = new MethodChannel(flutterEngine, "flutter/platform", JSONMethodCodec.INSTANCE); + flutterPlatformChannel.setMethodCallHandler(platformPlugin); + + mFlutterLifecycleChannel = new BasicMessageChannel<>(flutterEngine, "flutter/lifecycle", StringCodec.INSTANCE); + mFlutterSystemChannel = new BasicMessageChannel<>(flutterEngine, "flutter/system", JSONMessageCodec.INSTANCE); + mFlutterNavigationChannel = new MethodChannel(flutterEngine, "flutter/navigation", JSONMethodCodec.INSTANCE); + + return new FlutterView(activity, null); } /** - * Hook for subclasses to customize the creation of the {@code FlutterNativeView}. + * Hook for subclasses to customize the creation of the {@code FlutterEngine}. * * This method is only invoked from the default implementation of {@link #createFlutterView(Activity)}. * If {@link #createFlutterView(Activity)} is overridden, then this method will not be invoked unless * it is invoked directly from the subclass. * - * By default, this method returns a standard {@link FlutterNativeView} without any modification. + * By default, this method returns a standard {@link FlutterEngine} without any modification. */ @NonNull - protected FlutterNativeView createFlutterNativeView(@NonNull Context context) { - return new FlutterNativeView(context); + protected FlutterEngine createFlutterEngine(@NonNull Context context) { + Log.d(TAG, "createFlutterEngine()"); + Activity activity = (Activity) context; + FlutterEngine flutterEngine = new FlutterEngine(activity, getResources(), false); + flutterEngine.attachViewAndActivity(activity); + + return flutterEngine; } /** @@ -261,6 +297,16 @@ protected void onFlutterViewCreated(@SuppressWarnings("unused") @NonNull Flutter // no-op } + /** + * The {@link FlutterEngine} that backs the Flutter content presented by this {@code Fragment}. + * + * @return the {@link FlutterEngine} held by this {@code Fragment} + */ + @Nullable + protected FlutterEngine getFlutterEngine() { + return flutterEngine; + } + /** * Creates a {@link View} containing the same {@link Drawable} as the one set as the * {@code windowBackground} of the parent activity for use as a launch splash view. @@ -288,9 +334,10 @@ private View createLaunchView() { // TODO(mattcarroll): this API version error exists in the original code. Why is it there? view.setBackground(launchScreenDrawable); - flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() { + // TODO(mattcarroll): move the launch screen support into FlutterView + flutterEngine.getRenderer().addOnFirstFrameRenderedListener(new FlutterRenderer.OnFirstFrameRenderedListener() { @Override - public void onFirstFrame() { + public void onFirstFrameRendered() { launchView.animate() .alpha(0f) // Use Android's default animation duration. @@ -304,7 +351,7 @@ public void onAnimationEnd(Animator animation) { } }); - flutterView.removeFirstFrameListener(this); + flutterEngine.getRenderer().removeOnFirstFrameRenderedListener(this); } }); @@ -355,32 +402,30 @@ private Drawable getSplashScreenDrawableFromActivityTheme() { } } - /** - * Returns the Flutter view used by this {@code Fragment}; will be null before - * {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} is invoked. Will be - * non-null after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} is invoked, up - * until {@link #onDestroyView()} is invoked. - */ - @SuppressWarnings("unused") - @Nullable - public FlutterView getFlutterView() { - return flutterView; - } - /** * Starts running Dart within the FlutterView for the first time. * * Reloading/restarting Dart within a given FlutterView is not supported. */ private void doInitialFlutterViewRun() { - if (BuildConfig.DEBUG && flutterView.getFlutterNativeView().isApplicationRunning()) { +// if (BuildConfig.DEBUG && flutterView.getFlutterNativeView().isApplicationRunning()) { + if (flutterEngine.isApplicationRunning()) { throw new RuntimeException("Tried to initialize Dart execution in Flutter engine that is already running."); } if (getInitialRoute() != null) { - flutterView.setInitialRoute(getInitialRoute()); + setInitialRoute(getInitialRoute()); } - flutterView.runFromBundle(getAppBundlePath(), null, "main", false); + + // TODO(mattcarroll): are FlutterRunArguments and FlutterShellArgs the same thing? consolidate if they are + FlutterRunArguments args = new FlutterRunArguments(); + args.bundlePath = getAppBundlePath(); + args.entrypoint = "main"; + args.defaultPath = null; + flutterEngine.runFromBundle(args); + + // TODO(mattcarroll): why do we need to resetAccessibilityTree in this method? Can we call that from within FlutterView somewhere? + flutterView.resetAccessibilityTree(); } @Nullable @@ -396,24 +441,6 @@ private String getAppBundlePath() { return getArguments().getString(ARG_APP_BUNDLE_PATH); } - @Override - public final boolean hasPlugin(@NonNull String key) { - return flutterView.getPluginRegistry().hasPlugin(key); - } - - @Override - @Nullable - @SuppressWarnings("unchecked") - public T valuePublishedByPlugin(@NonNull String pluginKey) { - return (T) flutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey); - } - - @Override - @NonNull - public PluginRegistry.Registrar registrarFor(@NonNull String pluginKey) { - return flutterView.getPluginRegistry().registrarFor(pluginKey); - } - /** * Should the Flutter isolate that is connected to this {@code FlutterFragment} * be retained after this {@code FlutterFragment} is destroyed? @@ -439,8 +466,22 @@ protected boolean retainFlutterIsolateAfterFragmentDestruction() { @NonNull private Context getContextCompat() { - return Build.VERSION.SDK_INT >= 23 - ? getContext() - : getActivity(); + return getActivity(); + // TODO(mattcarroll): bring back when target SDK is rev'd +// return Build.VERSION.SDK_INT >= 23 +// ? getContext() +// : getActivity(); + } + + private void setInitialRoute(String route) { + mFlutterNavigationChannel.invokeMethod("setInitialRoute", route); + } + + private void pushRoute(String route) { + mFlutterNavigationChannel.invokeMethod("pushRoute", route); + } + + private void popRoute() { + mFlutterNavigationChannel.invokeMethod("popRoute", null); } } diff --git a/shell/platform/android/io/flutter/embedding/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/FlutterJNI.java new file mode 100644 index 0000000000000..ef083e10dfe1e --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/FlutterJNI.java @@ -0,0 +1,104 @@ +package io.flutter.embedding; + +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.view.Surface; + +import java.nio.ByteBuffer; + +public class FlutterJNI { + //----- Start from FlutterView ----- + public native void nativeSurfaceCreated(long nativePlatformViewAndroid, Surface surface); + + public native void nativeSurfaceChanged(long nativePlatformViewAndroid, + int width, + int height); + + public native void nativeSurfaceDestroyed(long nativePlatformViewAndroid); + + public native void nativeSetViewportMetrics(long nativePlatformViewAndroid, + float devicePixelRatio, + int physicalWidth, + int physicalHeight, + int physicalPaddingTop, + int physicalPaddingRight, + int physicalPaddingBottom, + int physicalPaddingLeft, + int physicalViewInsetTop, + int physicalViewInsetRight, + int physicalViewInsetBottom, + int physicalViewInsetLeft); + + public native Bitmap nativeGetBitmap(long nativePlatformViewAndroid); + + public native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, + ByteBuffer buffer, + int position); + + public native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, + int id, + int action, + ByteBuffer args, + int argsPosition); + + public native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled); + + public native void nativeSetAccessibilityFeatures(long nativePlatformViewAndroid, int flags); + + public native boolean nativeGetIsSoftwareRenderingEnabled(); + + public native void nativeRegisterTexture(long nativePlatformViewAndroid, long textureId, SurfaceTexture surfaceTexture); + + public native void nativeMarkTextureFrameAvailable(long nativePlatformViewAndroid, long textureId); + + public native void nativeUnregisterTexture(long nativePlatformViewAndroid, long textureId); + //------- End from FlutterView ----- + + //------ Start from FlutterNativeView ---- + public native long nativeAttach(FlutterEngine engine, boolean isBackgroundView); + public native void nativeDestroy(long nativePlatformViewAndroid); + public native void nativeDetach(long nativePlatformViewAndroid); + + public native void nativeRunBundleAndSnapshotFromLibrary( + long nativePlatformViewAndroid, + String bundlePath, + String defaultPath, + String entrypoint, + String libraryUrl, + AssetManager manager + ); + + public native String nativeGetObservatoryUri(); + + // Send an empty platform message to Dart. + public native void nativeDispatchEmptyPlatformMessage( + long nativePlatformViewAndroid, + String channel, + int responseId + ); + + // Send a data-carrying platform message to Dart. + public native void nativeDispatchPlatformMessage( + long nativePlatformViewAndroid, + String channel, + ByteBuffer message, + int position, + int responseId + ); + + // Send an empty response to a platform message received from Dart. + public native void nativeInvokePlatformMessageEmptyResponseCallback( + long nativePlatformViewAndroid, + int responseId + ); + + // Send a data-carrying response to a platform message received from Dart. + public native void nativeInvokePlatformMessageResponseCallback( + long nativePlatformViewAndroid, + int responseId, + ByteBuffer message, + int position + ); + //------ End from FlutterNativeView ---- +} diff --git a/shell/platform/android/io/flutter/embedding/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/FlutterRenderer.java new file mode 100644 index 0000000000000..0b17a6f8392ce --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/FlutterRenderer.java @@ -0,0 +1,285 @@ +package io.flutter.embedding; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; +import android.os.Build; +import android.support.annotation.NonNull; +import android.view.Surface; + +import java.nio.ByteBuffer; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicLong; + +import io.flutter.view.TextureRegistry; + +/** + * {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to create an + * interactive Flutter UI. + * + * {@code FlutterRenderer} manages textures for rendering, and forwards messages to native Flutter + * code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out + * certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy. + * + * {@link FlutterView} is an implementation of a {@link RenderSurface}. + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public class FlutterRenderer implements TextureRegistry { + + private final FlutterJNI flutterJNI; + private final long nativeObjectReference; + private final AtomicLong nextTextureId = new AtomicLong(0L); + private final Set firstFrameListeners = new CopyOnWriteArraySet<>(); + private RenderSurface renderSurface; + + FlutterRenderer(@NonNull FlutterJNI flutterJNI, long nativeObjectReference) { + this.flutterJNI = flutterJNI; + this.nativeObjectReference = nativeObjectReference; + } + + public void attachToRenderSurface(@NonNull RenderSurface renderSurface) { + // TODO(mattcarroll): what is our desired behavior when attaching to an already attached renderer? + if (this.renderSurface != null) { + detachFromRenderSurface(); + } + + this.renderSurface = renderSurface; + } + + public void detachFromRenderSurface() { + // TODO(mattcarroll): do we care if we're asked to detach without first being attached? + if (this.renderSurface != null) { + this.renderSurface = null; + } + } + + public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { + firstFrameListeners.add(listener); + } + + public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { + firstFrameListeners.remove(listener); + } + + private void notifyFirstFrameListeners() { + for (OnFirstFrameRenderedListener listener : firstFrameListeners) { + listener.onFirstFrameRendered(); + } + } + + //------ START TextureRegistry IMPLEMENTATION ----- + // TODO(mattcarroll): this method probably shouldn't be public. It's part of an obscure relationship + // with PlatformViewsController. Re-evaluate that relationship and see if we can get rid of + // PlatformViewsController. + // However, I did find that this method is called from the Camera plugin. + // TODO(mattcarroll): detachFromGLContext requires API 16. what should we do for earlier APIs? + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Override + public SurfaceTextureEntry createSurfaceTexture() { + final SurfaceTexture surfaceTexture = new SurfaceTexture(0); + surfaceTexture.detachFromGLContext(); + final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry( + nextTextureId.getAndIncrement(), + surfaceTexture + ); + registerTexture(entry.id(), surfaceTexture); + return entry; + } + + final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { + private final long id; + private final SurfaceTexture surfaceTexture; + private boolean released; + + SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { + this.id = id; + this.surfaceTexture = surfaceTexture; + this.surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture texture) { + markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id); + } + }); + } + + @Override + public SurfaceTexture surfaceTexture() { + return surfaceTexture; + } + + @Override + public long id() { + return id; + } + + @Override + public void release() { + if (released) { + return; + } + unregisterTexture(id); + surfaceTexture.release(); + released = true; + } + } + //------ END TextureRegistry IMPLEMENTATION ---- + + // TODO(mattcarroll): what exactly is this method intended to do? + public void surfaceCreated(Surface surface) { + flutterJNI.nativeSurfaceCreated(nativeObjectReference, surface); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void surfaceChanged(int width, int height) { + flutterJNI.nativeSurfaceChanged(nativeObjectReference, width, height); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void surfaceDestroyed() { + flutterJNI.nativeSurfaceDestroyed(nativeObjectReference); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void setViewportMetrics(float devicePixelRatio, + int physicalWidth, + int physicalHeight, + int physicalPaddingTop, + int physicalPaddingRight, + int physicalPaddingBottom, + int physicalPaddingLeft, + int physicalViewInsetTop, + int physicalViewInsetRight, + int physicalViewInsetBottom, + int physicalViewInsetLeft) { + flutterJNI.nativeSetViewportMetrics( + nativeObjectReference, + devicePixelRatio, + physicalWidth, + physicalHeight, + physicalPaddingTop, + physicalPaddingRight, + physicalPaddingBottom, + physicalPaddingLeft, + physicalViewInsetTop, + physicalViewInsetRight, + physicalViewInsetBottom, + physicalViewInsetLeft + ); + } + + // TODO(mattcarroll): why does this method exist? + public Bitmap getBitmap() { + return flutterJNI.nativeGetBitmap(nativeObjectReference); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void dispatchPointerDataPacket(ByteBuffer buffer, int position) { + flutterJNI.nativeDispatchPointerDataPacket(nativeObjectReference, buffer, position); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + private void registerTexture(long textureId, SurfaceTexture surfaceTexture) { + flutterJNI.nativeRegisterTexture(nativeObjectReference, textureId, surfaceTexture); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + private void markTextureFrameAvailable(long textureId) { + flutterJNI.nativeMarkTextureFrameAvailable(nativeObjectReference, textureId); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + private void unregisterTexture(long textureId) { + flutterJNI.nativeUnregisterTexture(nativeObjectReference, textureId); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public boolean isSoftwareRenderingEnabled() { + return flutterJNI.nativeGetIsSoftwareRenderingEnabled(); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void setAccessibilityFeatures(int flags) { + flutterJNI.nativeSetAccessibilityFeatures(nativeObjectReference, flags); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void setSemanticsEnabled(boolean enabled) { + flutterJNI.nativeSetSemanticsEnabled(nativeObjectReference, enabled); + } + + // TODO(mattcarroll): what exactly is this method intended to do? + public void dispatchSemanticsAction(int id, + int action, + ByteBuffer args, + int argsPosition) { + flutterJNI.nativeDispatchSemanticsAction( + nativeObjectReference, + id, + action, + args, + argsPosition + ); + } + + // TODO(mattcarroll): change the JNI code to call these directly rather than forward these calls from FlutterEngine + //------ START PACKAGE PRIVATE MESSAGES FROM FlutterEngine ------ + void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { + if (renderSurface != null) { + renderSurface.updateCustomAccessibilityActions(buffer, strings); + } + } + + void updateSemantics(ByteBuffer buffer, String[] strings) { + if (renderSurface != null) { + renderSurface.updateSemantics(buffer, strings); + } + } + + void onFirstFrameRendered() { + if (renderSurface != null) { + renderSurface.onFirstFrameRendered(); + } + notifyFirstFrameListeners(); + } + //------ END PACKAGE PRIVATE MESSAGES FROM FlutterEngine ------ + + public interface OnFirstFrameRenderedListener { + /** + * A {@link FlutterRenderer} has painted its first frame since being initialized. + * + * This method will not be invoked if this listener is added after the first frame is rendered. + */ + void onFirstFrameRendered(); + } + + /** + * Delegate used in conjunction with a {@link FlutterRenderer} to create an interactive Flutter + * UI. + * + * A {@code RenderSurface} is responsible for carrying out behaviors that are needed by a + * corresponding {@link FlutterRenderer}, e.g., {@link #updateSemantics(ByteBuffer, String[])}. + * + * A {@code RenderSurface} also receives callbacks for important events, e.g., + * {@link #onFirstFrameRendered()}. + */ + public interface RenderSurface { + // TODO(mattcarroll): what is this method supposed to do? + void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings); + + // TODO(mattcarroll): what is this method supposed to do? + void updateSemantics(ByteBuffer buffer, String[] strings); + + /** + * The {@link FlutterRenderer} corresponding to this {@code RenderSurface} has painted its + * first frame since being initialized. + * + * "Initialized" refers to Flutter engine initialization, not the first frame after attaching + * to the {@link FlutterRenderer}. Therefore, the first frame may have already rendered by + * the time a {@code RenderSurface} has called {@link #attachToRenderSurface(RenderSurface)} + * on a {@link FlutterRenderer}. In such a situation, {@link #onFirstFrameRendered()} will + * never be called. + */ + void onFirstFrameRendered(); + } +} diff --git a/shell/platform/android/io/flutter/embedding/FlutterView.java b/shell/platform/android/io/flutter/embedding/FlutterView.java index fb2b789250786..7aae7c0c707de 100644 --- a/shell/platform/android/io/flutter/embedding/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/FlutterView.java @@ -4,21 +4,17 @@ package io.flutter.embedding; -import android.app.Activity; -import android.content.BroadcastReceiver; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.database.ContentObserver; -import android.graphics.Bitmap; import android.graphics.Rect; -import android.graphics.SurfaceTexture; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.provider.Settings; +import android.support.annotation.NonNull; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.Log; @@ -26,7 +22,6 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.WindowInsets; @@ -38,1015 +33,781 @@ import android.view.inputmethod.InputMethodManager; import org.json.JSONException; -import org.json.JSONObject; -import java.net.URI; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import io.flutter.app.FlutterPluginRegistry; -import io.flutter.plugin.common.ActivityLifecycleListener; +import io.flutter.embedding.legacy.AccessibilityBridge; +import io.flutter.embedding.legacy.TextInputPlugin; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.JSONMessageCodec; import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.StandardMessageCodec; -import io.flutter.plugin.common.StringCodec; -import io.flutter.plugin.editing.TextInputPlugin; -import io.flutter.plugin.platform.PlatformPlugin; -import io.flutter.embedding.legacy.AccessibilityBridge; -import io.flutter.view.FlutterMain; -import io.flutter.embedding.legacy.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; -import io.flutter.view.TextureRegistry; import io.flutter.view.VsyncWaiter; /** - * An Android view containing a Flutter app. + * {@code View} which can render a Flutter UI by attaching itself to a {@link FlutterRenderer}. + * + * {@code FlutterView} does not take in any Flutter references within its constructor. This is done + * so that {@code FlutterView} can be instantiated from XML, and recreated on configuration change + * without introducing asymmetric APIs for construction. As a result, users of {@code FlutterView} + * must explicitly attach to Flutter, and detach from Flutter, as desired. + * + * To start rendering a Flutter UI, use {@link #attachToFlutterRenderer(FlutterRenderer, BinaryMessenger)}. + * + * To stop rendering a Flutter UI, use {@link #detachFromFlutterRenderer()}. + * + * {@code FlutterView} extends {@link SurfaceView} because Flutter renders directly to a + * {@link android.view.Surface}, which is then displayed by this {@link SurfaceView}. + * + * {@code FlutterView} implements {@link FlutterRenderer.RenderSurface} so that it can operate with + * a {@link FlutterRenderer} to render an interactive Flutter UI. {@code FlutterView} sends commands + * to its {@link FlutterRenderer}, and the {@link FlutterRenderer} sends updates to this + * {@code FlutterView} by utilizing it as a {@link FlutterRenderer.RenderSurface}. + * + * See also + * - {@link SurfaceView}, which displays {@link android.view.Surface}s. + * - {@link FlutterRenderer.RenderSurface}, which is the interface that is implemented by anything + * that wants to render a Flutter UI. + * - {@link android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener}, which + * receives callbacks when Android accessibility settings are changed. */ -public class FlutterView extends SurfaceView - implements BinaryMessenger, TextureRegistry, AccessibilityManager.AccessibilityStateChangeListener { - /** - * Interface for those objects that maintain and expose a reference to a - * {@code FlutterView} (such as a full-screen Flutter activity). - * - *

- * This indirection is provided to support applications that use an activity - * other than {@link io.flutter.app.FlutterActivity} (e.g. Android v4 support - * library's {@code FragmentActivity}). It allows Flutter plugins to deal in - * this interface and not require that the activity be a subclass of - * {@code FlutterActivity}. - *

- */ - public interface Provider { - /** - * Returns a reference to the Flutter view maintained by this object. This may - * be {@code null}. - */ - FlutterView getFlutterView(); - } - - private static final String TAG = "FlutterView"; - - private static final String ACTION_DISCOVER = "io.flutter.view.DISCOVER"; - - static final class ViewportMetrics { - float devicePixelRatio = 1.0f; - int physicalWidth = 0; - int physicalHeight = 0; - int physicalPaddingTop = 0; - int physicalPaddingRight = 0; - int physicalPaddingBottom = 0; - int physicalPaddingLeft = 0; - int physicalViewInsetTop = 0; - int physicalViewInsetRight = 0; - int physicalViewInsetBottom = 0; - int physicalViewInsetLeft = 0; - } - - private final InputMethodManager mImm; - private final TextInputPlugin mTextInputPlugin; - private final SurfaceHolder.Callback mSurfaceCallback; - private final ViewportMetrics mMetrics; - private final AccessibilityManager mAccessibilityManager; - private final MethodChannel mFlutterLocalizationChannel; - private final MethodChannel mFlutterNavigationChannel; - private final BasicMessageChannel mFlutterKeyEventChannel; - private final BasicMessageChannel mFlutterLifecycleChannel; - private final BasicMessageChannel mFlutterSystemChannel; - private final BasicMessageChannel mFlutterSettingsChannel; - private final BroadcastReceiver mDiscoveryReceiver; - private final List mActivityLifecycleListeners; - private final List mFirstFrameListeners; - private final AtomicLong nextTextureId = new AtomicLong(0L); - private FlutterNativeView mNativeView; - private final AnimationScaleObserver mAnimationScaleObserver; - private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not - private InputConnection mLastInputConnection; - - public FlutterView(Context context) { - this(context, null); - } - - public FlutterView(Context context, AttributeSet attrs) { - this(context, attrs, null); - } - - public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) { - super(context, attrs); - - mIsSoftwareRenderingEnabled = nativeGetIsSoftwareRenderingEnabled(); - mAnimationScaleObserver = new AnimationScaleObserver(new Handler()); - mMetrics = new ViewportMetrics(); - mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density; - setFocusable(true); - setFocusableInTouchMode(true); - - Activity activity = (Activity) getContext(); - if (nativeView == null) { - mNativeView = new FlutterNativeView(activity.getApplicationContext()); - } else { - mNativeView = nativeView; - } - mNativeView.attachViewAndActivity(this, activity); - - int color = 0xFF000000; - TypedValue typedValue = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true); - if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { - color = typedValue.data; - } - // TODO(abarth): Consider letting the developer override this color. - final int backgroundColor = color; - - mSurfaceCallback = new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - assertAttached(); - nativeSurfaceCreated(mNativeView.get(), holder.getSurface(), backgroundColor); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - assertAttached(); - nativeSurfaceChanged(mNativeView.get(), width, height); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - assertAttached(); - nativeSurfaceDestroyed(mNativeView.get()); - } - }; - getHolder().addCallback(mSurfaceCallback); - - mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - mActivityLifecycleListeners = new ArrayList<>(); - mFirstFrameListeners = new ArrayList<>(); - - // Configure the platform plugins and flutter channels. - mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE); - mFlutterNavigationChannel = new MethodChannel(this, "flutter/navigation", JSONMethodCodec.INSTANCE); - mFlutterKeyEventChannel = new BasicMessageChannel<>(this, "flutter/keyevent", JSONMessageCodec.INSTANCE); - mFlutterLifecycleChannel = new BasicMessageChannel<>(this, "flutter/lifecycle", StringCodec.INSTANCE); - mFlutterSystemChannel = new BasicMessageChannel<>(this, "flutter/system", JSONMessageCodec.INSTANCE); - mFlutterSettingsChannel = new BasicMessageChannel<>(this, "flutter/settings", JSONMessageCodec.INSTANCE); - - 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); - - setLocale(getResources().getConfiguration().locale); - setUserSettings(); - - if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - mDiscoveryReceiver = new DiscoveryReceiver(); - context.registerReceiver(mDiscoveryReceiver, new IntentFilter(ACTION_DISCOVER)); - } else { - mDiscoveryReceiver = null; - } - } - - private void encodeKeyEvent(KeyEvent event, Map message) { - message.put("flags", event.getFlags()); - message.put("codePoint", event.getUnicodeChar()); - message.put("keyCode", event.getKeyCode()); - message.put("scanCode", event.getScanCode()); - message.put("metaState", event.getMetaState()); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (!isAttached()) { - return super.onKeyUp(keyCode, event); - } - - Map message = new HashMap<>(); - message.put("type", "keyup"); - message.put("keymap", "android"); - encodeKeyEvent(event, message); - mFlutterKeyEventChannel.send(message); - return super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isAttached()) { - return super.onKeyDown(keyCode, event); - } - - if (event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD) { - if (mLastInputConnection != null && mImm.isAcceptingText()) { - mLastInputConnection.sendKeyEvent(event); - } - } - - Map message = new HashMap<>(); - message.put("type", "keydown"); - message.put("keymap", "android"); - encodeKeyEvent(event, message); - mFlutterKeyEventChannel.send(message); - return super.onKeyDown(keyCode, event); - } - - public FlutterNativeView getFlutterNativeView() { - return mNativeView; - } - - public FlutterPluginRegistry getPluginRegistry() { - return mNativeView.getPluginRegistry(); - } - - public String getLookupKeyForAsset(String asset) { - return FlutterMain.getLookupKeyForAsset(asset); - } - - public String getLookupKeyForAsset(String asset, String packageName) { - return FlutterMain.getLookupKeyForAsset(asset, packageName); - } - - public void addActivityLifecycleListener(ActivityLifecycleListener listener) { - mActivityLifecycleListeners.add(listener); - } - - public void onStart() { - mFlutterLifecycleChannel.send("AppLifecycleState.inactive"); - } - - public void onPause() { - mFlutterLifecycleChannel.send("AppLifecycleState.inactive"); - } - - public void onPostResume() { - updateAccessibilityFeatures(); - for (ActivityLifecycleListener listener : mActivityLifecycleListeners) { - listener.onPostResume(); - } - mFlutterLifecycleChannel.send("AppLifecycleState.resumed"); - } - - public void onStop() { - mFlutterLifecycleChannel.send("AppLifecycleState.paused"); - } - - public void onMemoryPressure() { - Map message = new HashMap<>(1); - message.put("type", "memoryPressure"); - mFlutterSystemChannel.send(message); - } - - /** - * Provide a listener that will be called once when the FlutterView renders its - * first frame to the underlaying SurfaceView. - */ - public void addFirstFrameListener(FirstFrameListener listener) { - mFirstFrameListeners.add(listener); - } - - /** - * Remove an existing first frame listener. - */ - public void removeFirstFrameListener(FirstFrameListener listener) { - mFirstFrameListeners.remove(listener); - } - - public void setInitialRoute(String route) { - mFlutterNavigationChannel.invokeMethod("setInitialRoute", route); - } - - public void pushRoute(String route) { - mFlutterNavigationChannel.invokeMethod("pushRoute", route); - } - - public void popRoute() { - mFlutterNavigationChannel.invokeMethod("popRoute", null); - } - - private void setUserSettings() { - Map message = new HashMap<>(); - message.put("textScaleFactor", getResources().getConfiguration().fontScale); - message.put("alwaysUse24HourFormat", DateFormat.is24HourFormat(getContext())); - mFlutterSettingsChannel.send(message); - } - - private void setLocale(Locale locale) { - mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry())); - } - - @Override - protected void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - setLocale(newConfig.locale); - setUserSettings(); - } - - float getDevicePixelRatio() { - return mMetrics.devicePixelRatio; - } - - public FlutterNativeView detach() { - if (!isAttached()) - return null; - if (mDiscoveryReceiver != null) { - getContext().unregisterReceiver(mDiscoveryReceiver); - } - getHolder().removeCallback(mSurfaceCallback); - mNativeView.detach(); - - FlutterNativeView view = mNativeView; - mNativeView = null; - return view; - } - - public void destroy() { - if (!isAttached()) - return; - - if (mDiscoveryReceiver != null) { - getContext().unregisterReceiver(mDiscoveryReceiver); - } - - getHolder().removeCallback(mSurfaceCallback); - - mNativeView.destroy(); - mNativeView = null; - } - - @Override - public InputConnection onCreateInputConnection(EditorInfo 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. - private static final int kPointerChangeCancel = 0; - private static final int kPointerChangeAdd = 1; - private static final int kPointerChangeRemove = 2; - private static final int kPointerChangeHover = 3; - private static final int kPointerChangeDown = 4; - private static final int kPointerChangeMove = 5; - private static final int kPointerChangeUp = 6; - - // Must match the PointerDeviceKind enum in pointer.dart. - private static final int kPointerDeviceKindTouch = 0; - private static final int kPointerDeviceKindMouse = 1; - private static final int kPointerDeviceKindStylus = 2; - private static final int kPointerDeviceKindInvertedStylus = 3; - private static final int kPointerDeviceKindUnknown = 4; - - private int getPointerChangeForAction(int maskedAction) { - // Primary pointer: - if (maskedAction == MotionEvent.ACTION_DOWN) { - return kPointerChangeDown; - } - if (maskedAction == MotionEvent.ACTION_UP) { - return kPointerChangeUp; - } - // Secondary pointer: - if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) { - return kPointerChangeDown; - } - if (maskedAction == MotionEvent.ACTION_POINTER_UP) { - return kPointerChangeUp; - } - // All pointers: - if (maskedAction == MotionEvent.ACTION_MOVE) { - return kPointerChangeMove; - } - if (maskedAction == MotionEvent.ACTION_CANCEL) { - return kPointerChangeCancel; - } - return -1; - } - - private int getPointerDeviceTypeForToolType(int toolType) { - switch (toolType) { - case MotionEvent.TOOL_TYPE_FINGER: - return kPointerDeviceKindTouch; - case MotionEvent.TOOL_TYPE_STYLUS: - return kPointerDeviceKindStylus; - case MotionEvent.TOOL_TYPE_MOUSE: - return kPointerDeviceKindMouse; - case MotionEvent.TOOL_TYPE_ERASER: - return kPointerDeviceKindInvertedStylus; - default: - // MotionEvent.TOOL_TYPE_UNKNOWN will reach here. - return kPointerDeviceKindUnknown; - } - } - - private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer packet) { - int pointerChange = getPointerChangeForAction(event.getActionMasked()); - if (pointerChange == -1) { - return; - } - - int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex)); - - long timeStamp = event.getEventTime() * 1000; // Convert from milliseconds to microseconds. - - packet.putLong(timeStamp); // time_stamp - packet.putLong(pointerChange); // change - packet.putLong(pointerKind); // kind - packet.putLong(event.getPointerId(pointerIndex)); // device - packet.putDouble(event.getX(pointerIndex)); // physical_x - packet.putDouble(event.getY(pointerIndex)); // physical_y - - if (pointerKind == kPointerDeviceKindMouse) { - packet.putLong(event.getButtonState() & 0x1F); // buttons - } else if (pointerKind == kPointerDeviceKindStylus) { - packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons - } else { - packet.putLong(0); // buttons - } - - packet.putLong(0); // obscured - - // TODO(eseidel): Could get the calibrated range if necessary: - // event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE) - packet.putDouble(event.getPressure(pointerIndex)); // pressure - packet.putDouble(0.0); // pressure_min - packet.putDouble(1.0); // pressure_max - - if (pointerKind == kPointerDeviceKindStylus) { - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance - packet.putDouble(0.0); // distance_max - } else { - packet.putDouble(0.0); // distance - packet.putDouble(0.0); // distance_max - } - - packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major - packet.putDouble(event.getToolMinor(pointerIndex)); // radius_minor - - packet.putDouble(0.0); // radius_min - packet.putDouble(0.0); // radius_max - - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation - - if (pointerKind == kPointerDeviceKindStylus) { - packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt - } else { - packet.putDouble(0.0); // tilt - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isAttached()) { - return false; - } - - // TODO(abarth): This version check might not be effective in some - // versions of Android that statically compile code and will be upset - // at the lack of |requestUnbufferedDispatch|. Instead, we should factor - // version-dependent code into separate classes for each supported - // version and dispatch dynamically. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - requestUnbufferedDispatch(event); - } - - // These values must match the unpacking code in hooks.dart. - final int kPointerDataFieldCount = 19; - final int kBytePerField = 8; - - int pointerCount = event.getPointerCount(); - - ByteBuffer packet = ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField); - packet.order(ByteOrder.LITTLE_ENDIAN); - - int maskedAction = event.getActionMasked(); - // ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN - // only apply to a single pointer, other events apply to all pointers. - if (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP - || maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { - addPointerForIndex(event, event.getActionIndex(), packet); - } else { - // ACTION_MOVE may not actually mean all pointers have moved - // but it's the responsibility of a later part of the system to - // ignore 0-deltas if desired. - for (int p = 0; p < pointerCount; p++) { - addPointerForIndex(event, p, packet); - } - } - - assert packet.position() % (kPointerDataFieldCount * kBytePerField) == 0; - nativeDispatchPointerDataPacket(mNativeView.get(), packet, packet.position()); - return true; - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - if (!isAttached()) { - return false; - } - - boolean handled = handleAccessibilityHoverEvent(event); - if (!handled) { - // TODO(ianh): Expose hover events to the platform, - // implementing ADD, REMOVE, etc. - } - return handled; - } - +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class FlutterView extends SurfaceView implements + FlutterRenderer.RenderSurface, + AccessibilityManager.AccessibilityStateChangeListener { + + private static final String TAG = "FlutterView"; + + // Must match the PointerChange enum in pointer.dart. + private static final int kPointerChangeCancel = 0; + private static final int kPointerChangeAdd = 1; + private static final int kPointerChangeRemove = 2; + private static final int kPointerChangeHover = 3; + private static final int kPointerChangeDown = 4; + private static final int kPointerChangeMove = 5; + private static final int kPointerChangeUp = 6; + + // Must match the PointerDeviceKind enum in pointer.dart. + private static final int kPointerDeviceKindTouch = 0; + private static final int kPointerDeviceKindMouse = 1; + private static final int kPointerDeviceKindStylus = 2; + private static final int kPointerDeviceKindInvertedStylus = 3; + private static final int kPointerDeviceKindUnknown = 4; + + static final class ViewportMetrics { + float devicePixelRatio = 1.0f; + int physicalWidth = 0; + int physicalHeight = 0; + int physicalPaddingTop = 0; + int physicalPaddingRight = 0; + int physicalPaddingBottom = 0; + int physicalPaddingLeft = 0; + int physicalViewInsetTop = 0; + int physicalViewInsetRight = 0; + int physicalViewInsetBottom = 0; + int physicalViewInsetLeft = 0; + } + + private final InputMethodManager mImm; + private final ViewportMetrics mMetrics; + private final AccessibilityManager mAccessibilityManager; + private final AnimationScaleObserver mAnimationScaleObserver; + + private FlutterRenderer flutterRenderer; + private BinaryMessenger pluginMessenger; + + private TextInputPlugin mTextInputPlugin; + private MethodChannel mFlutterLocalizationChannel; + private BasicMessageChannel mFlutterKeyEventChannel; + private BasicMessageChannel mFlutterSettingsChannel; + private InputConnection mLastInputConnection; + + private boolean isAttachedToRenderer = false; + private boolean mIsSoftwareRenderingEnabled = false; // using the software renderer or not + private int backgroundColor; + + // Accessibility + private boolean mAccessibilityEnabled = false; + private boolean mTouchExplorationEnabled = false; + private int mAccessibilityFeatureFlags = 0; + private AccessibilityBridge mAccessibilityNodeProvider; + private TouchExplorationListener mTouchExplorationListener; + + // Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code. + // Callbacks are received by this Object and then those messages are forwarded to our + // FlutterRenderer, and then on to the JNI bridge over to native Flutter code. + private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { @Override - protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { - mMetrics.physicalWidth = width; - mMetrics.physicalHeight = height; - updateViewportMetrics(); - super.onSizeChanged(width, height, oldWidth, oldHeight); + public void surfaceCreated(SurfaceHolder holder) { + assertAttachedToFlutterRenderer(); + flutterRenderer.surfaceCreated(holder.getSurface()); } @Override - public final WindowInsets onApplyWindowInsets(WindowInsets insets) { - // Status bar, left/right system insets partially obscure content (padding). - mMetrics.physicalPaddingTop = insets.getSystemWindowInsetTop(); - mMetrics.physicalPaddingRight = insets.getSystemWindowInsetRight(); - mMetrics.physicalPaddingBottom = 0; - mMetrics.physicalPaddingLeft = insets.getSystemWindowInsetLeft(); - - // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). - mMetrics.physicalViewInsetTop = 0; - mMetrics.physicalViewInsetRight = 0; - mMetrics.physicalViewInsetBottom = insets.getSystemWindowInsetBottom(); - mMetrics.physicalViewInsetLeft = 0; - updateViewportMetrics(); - return super.onApplyWindowInsets(insets); + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + assertAttachedToFlutterRenderer(); + flutterRenderer.surfaceChanged(width, height); } @Override - @SuppressWarnings("deprecation") - protected boolean fitSystemWindows(Rect insets) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - // Status bar, left/right system insets partially obscure content (padding). - mMetrics.physicalPaddingTop = insets.top; - mMetrics.physicalPaddingRight = insets.right; - mMetrics.physicalPaddingBottom = 0; - mMetrics.physicalPaddingLeft = insets.left; - - // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). - mMetrics.physicalViewInsetTop = 0; - mMetrics.physicalViewInsetRight = 0; - mMetrics.physicalViewInsetBottom = insets.bottom; - mMetrics.physicalViewInsetLeft = 0; - updateViewportMetrics(); - return true; - } else { - return super.fitSystemWindows(insets); - } - } - - private boolean isAttached() { - return mNativeView != null && mNativeView.isAttached(); - } - - void assertAttached() { - if (!isAttached()) - throw new AssertionError("Platform view is not attached"); - } - - private void preRun() { - resetAccessibilityTree(); - } - - private void postRun() { - } - - public void runFromBundle(FlutterRunArguments args) { - assertAttached(); - preRun(); - mNativeView.runFromBundle(args); - postRun(); - } - - /** - * @deprecated - * Please use runFromBundle with `FlutterRunArguments`. - */ - @Deprecated - public void runFromBundle(String bundlePath, String defaultPath) { - runFromBundle(bundlePath, defaultPath, "main", false); - } - - /** - * @deprecated - * Please use runFromBundle with `FlutterRunArguments`. - */ - @Deprecated - public void runFromBundle(String bundlePath, String defaultPath, String entrypoint) { - runFromBundle(bundlePath, defaultPath, entrypoint, false); - } - - /** - * @deprecated - * Please use runFromBundle with `FlutterRunArguments`. - * Parameter `reuseRuntimeController` has no effect. - */ - @Deprecated - public void runFromBundle(String bundlePath, String defaultPath, String entrypoint, boolean reuseRuntimeController) { - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = bundlePath; - args.entrypoint = entrypoint; - args.defaultPath = defaultPath; - runFromBundle(args); - } - - /** - * Return the most recent frame as a bitmap. - * - * @return A bitmap. - */ - public Bitmap getBitmap() { - assertAttached(); - return nativeGetBitmap(mNativeView.get()); - } - - private static native void nativeSurfaceCreated(long nativePlatformViewAndroid, Surface surface, - int backgroundColor); - - private static native void nativeSurfaceChanged(long nativePlatformViewAndroid, int width, int height); - - private static native void nativeSurfaceDestroyed(long nativePlatformViewAndroid); - - private static native void nativeSetViewportMetrics(long nativePlatformViewAndroid, float devicePixelRatio, - int physicalWidth, int physicalHeight, int physicalPaddingTop, int physicalPaddingRight, - int physicalPaddingBottom, int physicalPaddingLeft, int physicalViewInsetTop, int physicalViewInsetRight, - int physicalViewInsetBottom, int physicalViewInsetLeft); - - private static native Bitmap nativeGetBitmap(long nativePlatformViewAndroid); - - private static native void nativeDispatchPointerDataPacket(long nativePlatformViewAndroid, ByteBuffer buffer, - int position); + public void surfaceDestroyed(SurfaceHolder holder) { + assertAttachedToFlutterRenderer(); + flutterRenderer.surfaceDestroyed(); + } + }; + + //------ START VIEW OVERRIDES ----- + public FlutterView(Context context) { + this(context, null); + } + + public FlutterView(Context context, AttributeSet attrs) { + super(context, attrs); + + // Cache references to Objects used throughout FlutterView. + mMetrics = new ViewportMetrics(); + mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density; + mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); + mAnimationScaleObserver = new AnimationScaleObserver(new Handler()); + + // Process any theme and attribute preferences. + readBackgroundColorFromThemeAndAttributes(); + + // Initialize this View as needed. + setFocusable(true); + setFocusableInTouchMode(true); + } + + // TODO(mattcarroll): add XML attribute support for background color + private void readBackgroundColorFromThemeAndAttributes() { + int color = 0xFF000000; + TypedValue typedValue = new TypedValue(); + getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, typedValue, true); + if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { + color = typedValue.data; + } + // TODO(abarth): Consider letting the developer override this color. + backgroundColor = color; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + Log.d(TAG, "onAttachedToWindow()"); + + // Read accessibility settings. + mAccessibilityEnabled = mAccessibilityManager.isEnabled(); + mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); + if (mAccessibilityEnabled || mTouchExplorationEnabled) { + ensureAccessibilityEnabled(); + } + if (mTouchExplorationEnabled) { + mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; + } + + // Apply additional accessibility settings + updateAccessibilityFeatures(); + resetWillNotDraw(); + mAccessibilityManager.addAccessibilityStateChangeListener(this); + + // Start listening for changes to accessibility settings. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (mTouchExplorationListener == null) { + mTouchExplorationListener = new TouchExplorationListener(); + } + mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener); + } + + // Start listening for changes to Android's animation scale setting. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE); + getContext().getContentResolver().registerContentObserver(transitionUri, false, mAnimationScaleObserver); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + Log.d(TAG, "onDetachedFromWindow()"); + + // Stop listening for changes to Android's animation scale setting. + getContext().getContentResolver().unregisterContentObserver(mAnimationScaleObserver); + + // Stop listening for changes to accessibility settings. + mAccessibilityManager.removeAccessibilityStateChangeListener(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener); + } + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + try { + mLastInputConnection = mTextInputPlugin.createInputConnection(this, outAttrs); + return mLastInputConnection; + } catch (JSONException e) { + Log.e(TAG, "Failed to create input connection", e); + return null; + } + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + setFlutterLocale(newConfig.locale); + setFlutterUserSettings(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isAttachedToRenderer) { + return false; + } + + // TODO(abarth): This version check might not be effective in some + // versions of Android that statically compile code and will be upset + // at the lack of |requestUnbufferedDispatch|. Instead, we should factor + // version-dependent code into separate classes for each supported + // version and dispatch dynamically. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + requestUnbufferedDispatch(event); + } - private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, int action, - ByteBuffer args, int argsPosition); + // These values must match the unpacking code in hooks.dart. + final int kPointerDataFieldCount = 19; + final int kBytePerField = 8; - private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled); + int pointerCount = event.getPointerCount(); + + ByteBuffer packet = ByteBuffer.allocateDirect(pointerCount * kPointerDataFieldCount * kBytePerField); + packet.order(ByteOrder.LITTLE_ENDIAN); - private static native void nativeSetAccessibilityFeatures(long nativePlatformViewAndroid, int flags); - - private static native boolean nativeGetIsSoftwareRenderingEnabled(); - - private static native void nativeRegisterTexture(long nativePlatformViewAndroid, long textureId, - SurfaceTexture surfaceTexture); - - private static native void nativeMarkTextureFrameAvailable(long nativePlatformViewAndroid, long textureId); - - private static native void nativeUnregisterTexture(long nativePlatformViewAndroid, long textureId); - - private void updateViewportMetrics() { - if (!isAttached()) - return; - nativeSetViewportMetrics(mNativeView.get(), mMetrics.devicePixelRatio, mMetrics.physicalWidth, - mMetrics.physicalHeight, mMetrics.physicalPaddingTop, mMetrics.physicalPaddingRight, - mMetrics.physicalPaddingBottom, mMetrics.physicalPaddingLeft, mMetrics.physicalViewInsetTop, - mMetrics.physicalViewInsetRight, mMetrics.physicalViewInsetBottom, mMetrics.physicalViewInsetLeft); - - WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); - float fps = wm.getDefaultDisplay().getRefreshRate(); - VsyncWaiter.refreshPeriodNanos = (long) (1000000000.0 / fps); - } - - // Called by native to update the semantics/accessibility tree. - public void updateSemantics(ByteBuffer buffer, String[] strings) { - try { - if (mAccessibilityNodeProvider != null) { - buffer.order(ByteOrder.LITTLE_ENDIAN); - mAccessibilityNodeProvider.updateSemantics(buffer, strings); - } - } catch (Exception ex) { - Log.e(TAG, "Uncaught exception while updating semantics", ex); - } - } - - public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { - try { - if (mAccessibilityNodeProvider != null) { - buffer.order(ByteOrder.LITTLE_ENDIAN); - mAccessibilityNodeProvider.updateCustomAccessibilityActions(buffer, strings); - } - } catch (Exception ex) { - Log.e(TAG, "Uncaught exception while updating local context actions", ex); - } - } - - // Called by native to notify first Flutter frame rendered. - public void onFirstFrame() { - // Allow listeners to remove themselves when they are called. - List listeners = new ArrayList<>(mFirstFrameListeners); - for (FirstFrameListener listener : listeners) { - listener.onFirstFrame(); - } - } - - // ACCESSIBILITY - - private boolean mAccessibilityEnabled = false; - private boolean mTouchExplorationEnabled = false; - private int mAccessibilityFeatureFlags = 0; - private TouchExplorationListener mTouchExplorationListener; - - public void dispatchSemanticsAction(int id, AccessibilityBridge.Action action) { - dispatchSemanticsAction(id, action, null); - } - - public void dispatchSemanticsAction(int id, AccessibilityBridge.Action action, Object args) { - if (!isAttached()) - return; - ByteBuffer encodedArgs = null; - int position = 0; - if (args != null) { - encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args); - position = encodedArgs.position(); - } - nativeDispatchSemanticsAction(mNativeView.get(), id, action.value, encodedArgs, position); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mAccessibilityEnabled = mAccessibilityManager.isEnabled(); - mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - Uri transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE); - getContext().getContentResolver().registerContentObserver(transitionUri, false, mAnimationScaleObserver); - } - - if (mAccessibilityEnabled || mTouchExplorationEnabled) { - ensureAccessibilityEnabled(); - } - if (mTouchExplorationEnabled) { - mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; - } - // Apply additional accessibility settings - updateAccessibilityFeatures(); - resetWillNotDraw(); - mAccessibilityManager.addAccessibilityStateChangeListener(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (mTouchExplorationListener == null) { - mTouchExplorationListener = new TouchExplorationListener(); - } - mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationListener); - } - } + int maskedAction = event.getActionMasked(); + // ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN + // only apply to a single pointer, other events apply to all pointers. + if (maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP + || maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + addPointerForIndex(event, event.getActionIndex(), packet); + } else { + // ACTION_MOVE may not actually mean all pointers have moved + // but it's the responsibility of a later part of the system to + // ignore 0-deltas if desired. + for (int p = 0; p < pointerCount; p++) { + addPointerForIndex(event, p, packet); + } + } + + assert packet.position() % (kPointerDataFieldCount * kBytePerField) == 0; + flutterRenderer.dispatchPointerDataPacket(packet, packet.position()); + return true; + } + + private void addPointerForIndex(MotionEvent event, int pointerIndex, ByteBuffer packet) { + int pointerChange = getPointerChangeForAction(event.getActionMasked()); + if (pointerChange == -1) { + return; + } + + int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex)); - private void updateAccessibilityFeatures() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(), - Settings.Global.TRANSITION_ANIMATION_SCALE); - if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) { - mAccessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; - } else { - mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; - } - } - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); + long timeStamp = event.getEventTime() * 1000; // Convert from milliseconds to microseconds. + + packet.putLong(timeStamp); // time_stamp + packet.putLong(pointerChange); // change + packet.putLong(pointerKind); // kind + packet.putLong(event.getPointerId(pointerIndex)); // device + packet.putDouble(event.getX(pointerIndex)); // physical_x + packet.putDouble(event.getY(pointerIndex)); // physical_y + + if (pointerKind == kPointerDeviceKindMouse) { + packet.putLong(event.getButtonState() & 0x1F); // buttons + } else if (pointerKind == kPointerDeviceKindStylus) { + packet.putLong((event.getButtonState() >> 4) & 0xF); // buttons + } else { + packet.putLong(0); // buttons + } + + packet.putLong(0); // obscured + + // TODO(eseidel): Could get the calibrated range if necessary: + // event.getDevice().getMotionRange(MotionEvent.AXIS_PRESSURE) + packet.putDouble(event.getPressure(pointerIndex)); // pressure + packet.putDouble(0.0); // pressure_min + packet.putDouble(1.0); // pressure_max + + if (pointerKind == kPointerDeviceKindStylus) { + packet.putDouble(event.getAxisValue(MotionEvent.AXIS_DISTANCE, pointerIndex)); // distance + packet.putDouble(0.0); // distance_max + } else { + packet.putDouble(0.0); // distance + packet.putDouble(0.0); // distance_max + } + + packet.putDouble(event.getToolMajor(pointerIndex)); // radius_major + packet.putDouble(event.getToolMinor(pointerIndex)); // radius_minor + + packet.putDouble(0.0); // radius_min + packet.putDouble(0.0); // radius_max + + packet.putDouble(event.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex)); // orientation + + if (pointerKind == kPointerDeviceKindStylus) { + packet.putDouble(event.getAxisValue(MotionEvent.AXIS_TILT, pointerIndex)); // tilt + } else { + packet.putDouble(0.0); // tilt + } + } + + private int getPointerChangeForAction(int maskedAction) { + // Primary pointer: + if (maskedAction == MotionEvent.ACTION_DOWN) { + return kPointerChangeDown; + } + if (maskedAction == MotionEvent.ACTION_UP) { + return kPointerChangeUp; + } + // Secondary pointer: + if (maskedAction == MotionEvent.ACTION_POINTER_DOWN) { + return kPointerChangeDown; + } + if (maskedAction == MotionEvent.ACTION_POINTER_UP) { + return kPointerChangeUp; + } + // All pointers: + if (maskedAction == MotionEvent.ACTION_MOVE) { + return kPointerChangeMove; + } + if (maskedAction == MotionEvent.ACTION_CANCEL) { + return kPointerChangeCancel; + } + return -1; + } + + private int getPointerDeviceTypeForToolType(int toolType) { + switch (toolType) { + case MotionEvent.TOOL_TYPE_FINGER: + return kPointerDeviceKindTouch; + case MotionEvent.TOOL_TYPE_STYLUS: + return kPointerDeviceKindStylus; + case MotionEvent.TOOL_TYPE_MOUSE: + return kPointerDeviceKindMouse; + case MotionEvent.TOOL_TYPE_ERASER: + return kPointerDeviceKindInvertedStylus; + default: + // MotionEvent.TOOL_TYPE_UNKNOWN will reach here. + return kPointerDeviceKindUnknown; + } + } + + @Override + public boolean onHoverEvent(MotionEvent event) { + if (!isAttachedToRenderer) { + return false; + } + + boolean handled = handleAccessibilityHoverEvent(event); + if (!handled) { + // TODO(ianh): Expose hover events to the platform, + // implementing ADD, REMOVE, etc. + } + return handled; + } + + private boolean handleAccessibilityHoverEvent(MotionEvent event) { + if (!mTouchExplorationEnabled) { + return false; + } + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { + mAccessibilityNodeProvider.handleTouchExploration(event.getX(), event.getY()); + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + mAccessibilityNodeProvider.handleTouchExplorationExit(); + } else { + Log.d("flutter", "unexpected accessibility hover event: " + event); + return false; + } + return true; + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + mMetrics.physicalWidth = width; + mMetrics.physicalHeight = height; + updateViewportMetrics(); + super.onSizeChanged(width, height, oldWidth, oldHeight); + } + + // TODO(mattcarroll): window insets are API 20. what should we do for lower APIs? + @SuppressLint("NewApi") + @Override + public final WindowInsets onApplyWindowInsets(WindowInsets insets) { + // Status bar, left/right system insets partially obscure content (padding). + mMetrics.physicalPaddingTop = insets.getSystemWindowInsetTop(); + mMetrics.physicalPaddingRight = insets.getSystemWindowInsetRight(); + mMetrics.physicalPaddingBottom = 0; + mMetrics.physicalPaddingLeft = insets.getSystemWindowInsetLeft(); + + // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). + mMetrics.physicalViewInsetTop = 0; + mMetrics.physicalViewInsetRight = 0; + mMetrics.physicalViewInsetBottom = insets.getSystemWindowInsetBottom(); + mMetrics.physicalViewInsetLeft = 0; + updateViewportMetrics(); + return super.onApplyWindowInsets(insets); + } + + @Override + @SuppressWarnings("deprecation") + protected boolean fitSystemWindows(Rect insets) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { + // Status bar, left/right system insets partially obscure content (padding). + mMetrics.physicalPaddingTop = insets.top; + mMetrics.physicalPaddingRight = insets.right; + mMetrics.physicalPaddingBottom = 0; + mMetrics.physicalPaddingLeft = insets.left; + + // Bottom system inset (keyboard) should adjust scrollable bottom edge (inset). + mMetrics.physicalViewInsetTop = 0; + mMetrics.physicalViewInsetRight = 0; + mMetrics.physicalViewInsetBottom = insets.bottom; + mMetrics.physicalViewInsetLeft = 0; + updateViewportMetrics(); + return true; + } else { + return super.fitSystemWindows(insets); + } + } + + private void updateViewportMetrics() { + if (!isAttachedToRenderer) + return; + + flutterRenderer.setViewportMetrics( + mMetrics.devicePixelRatio, + mMetrics.physicalWidth, + mMetrics.physicalHeight, + mMetrics.physicalPaddingTop, + mMetrics.physicalPaddingRight, + mMetrics.physicalPaddingBottom, + mMetrics.physicalPaddingLeft, + mMetrics.physicalViewInsetTop, + mMetrics.physicalViewInsetRight, + mMetrics.physicalViewInsetBottom, + mMetrics.physicalViewInsetLeft + ); + + WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); + float fps = wm.getDefaultDisplay().getRefreshRate(); + VsyncWaiter.refreshPeriodNanos = (long) (1000000000.0 / fps); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (!isAttachedToRenderer) { + return super.onKeyUp(keyCode, event); + } + + Map message = new HashMap<>(); + message.put("type", "keyup"); + message.put("keymap", "android"); + encodeKeyEvent(event, message); + mFlutterKeyEventChannel.send(message); + return super.onKeyUp(keyCode, event); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (!isAttachedToRenderer) { + return super.onKeyDown(keyCode, event); + } + + if (event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD) { + if (mLastInputConnection != null && mImm.isAcceptingText()) { + mLastInputConnection.sendKeyEvent(event); + } + } + + Map message = new HashMap<>(); + message.put("type", "keydown"); + message.put("keymap", "android"); + encodeKeyEvent(event, message); + mFlutterKeyEventChannel.send(message); + return super.onKeyDown(keyCode, event); + } + + private void encodeKeyEvent(KeyEvent event, Map message) { + message.put("flags", event.getFlags()); + message.put("codePoint", event.getUnicodeChar()); + message.put("keyCode", event.getKeyCode()); + message.put("scanCode", event.getScanCode()); + message.put("metaState", event.getMetaState()); + } + //------ END VIEW OVERRIDES ---- + + //----- START AccessibilityStateChangeListener ----- + @Override + public void onAccessibilityStateChanged(boolean enabled) { + if (enabled) { + ensureAccessibilityEnabled(); + } else { + mAccessibilityEnabled = false; + if (mAccessibilityNodeProvider != null) { + mAccessibilityNodeProvider.setAccessibilityEnabled(false); + } + flutterRenderer.setSemanticsEnabled(false); + } + resetWillNotDraw(); + } + + @Override + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + if (mAccessibilityEnabled) + return mAccessibilityNodeProvider; + // TODO(goderbauer): when a11y is off this should return a one-off snapshot of + // the a11y + // tree. + return null; + } + + private void resetWillNotDraw() { + if (!mIsSoftwareRenderingEnabled) { + setWillNotDraw(!(mAccessibilityEnabled || mTouchExplorationEnabled)); + } else { + setWillNotDraw(false); + } + } + //----- END AccessibilityStateChangeListener ---- + + //------- START ACCESSIBILITY ------ + public void dispatchSemanticsAction(int id, AccessibilityBridge.Action action) { + dispatchSemanticsAction(id, action, null); + } + + public void dispatchSemanticsAction(int id, AccessibilityBridge.Action action, Object args) { + if (!isAttachedToRenderer) + return; + ByteBuffer encodedArgs = null; + int position = 0; + if (args != null) { + encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args); + position = encodedArgs.position(); + } + flutterRenderer.dispatchSemanticsAction(id, action.value, encodedArgs, position); + } + + public void updateAccessibilityFeatures() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + String transitionAnimationScale = Settings.Global.getString(getContext().getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE); + if (transitionAnimationScale != null && transitionAnimationScale.equals("0")) { + mAccessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; + } else { + mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; + } + } + flutterRenderer.setAccessibilityFeatures(mAccessibilityFeatureFlags); + } + + void ensureAccessibilityEnabled() { + if (!isAttachedToRenderer) + return; + mAccessibilityEnabled = true; + if (mAccessibilityNodeProvider == null) { + mAccessibilityNodeProvider = new AccessibilityBridge(this, pluginMessenger); + } + flutterRenderer.setSemanticsEnabled(true); + mAccessibilityNodeProvider.setAccessibilityEnabled(true); + } + + public void resetAccessibilityTree() { + if (mAccessibilityNodeProvider != null) { + mAccessibilityNodeProvider.reset(); + } + } + //------- END ACCESSIBILITY ---- + + //----- START FLUTTER INTEGRATION ----- + + /** + * Start rendering the UI for the given {@link FlutterRenderer}. + * + * @param flutterRenderer the FlutterRenderer for which this FlutterView will be a RenderSurface + * @param pluginMessenger the BinaryMessenger to use with any plugins that this FlutterView needs + * to register + */ + public void attachToFlutterRenderer(@NonNull FlutterRenderer flutterRenderer, @NonNull BinaryMessenger pluginMessenger) { + if (isAttachedToRenderer) { + detachFromFlutterRenderer(); + } + + // TODO(mattcarroll): what should we do if we're not currently attached to the window? + this.flutterRenderer = flutterRenderer; + this.pluginMessenger = pluginMessenger; + + // Instruct our FlutterRenderer that we are now its designated RenderSurface. + this.flutterRenderer.attachToRenderSurface(this); + isAttachedToRenderer = true; + + // Configure the platform plugins and flutter channels. + mFlutterLocalizationChannel = new MethodChannel(pluginMessenger, "flutter/localization", JSONMethodCodec.INSTANCE); + mFlutterKeyEventChannel = new BasicMessageChannel<>(pluginMessenger, "flutter/keyevent", JSONMessageCodec.INSTANCE); + mFlutterSettingsChannel = new BasicMessageChannel<>(pluginMessenger, "flutter/settings", JSONMessageCodec.INSTANCE); + mTextInputPlugin = new TextInputPlugin(this, pluginMessenger); + + setFlutterLocale(getResources().getConfiguration().locale); + setFlutterUserSettings(); + + // Grab a reference to our underlying Surface and register callbacks with that Surface so we + // can monitor changes and forward those changes on to native Flutter code. + getHolder().addCallback(mSurfaceCallback); + + mIsSoftwareRenderingEnabled = flutterRenderer.isSoftwareRenderingEnabled(); + } + + private void assertAttachedToFlutterRenderer() { + if (!isAttachedToRenderer) { + throw new AssertionError("FlutterView is not attached to a FlutterRenderer."); + } + } + + /** + * Stop rendering the UI for a given {@link FlutterRenderer}. + * + * If no {@link FlutterRenderer} is currently attached, this method does nothing. + */ + public void detachFromFlutterRenderer() { + if (!isAttachedToRenderer) { + return; + } + + // Stop forwarding messages from our underlying Surface to native Flutter code. + getHolder().removeCallback(mSurfaceCallback); + + // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. + flutterRenderer.detachFromRenderSurface(); + flutterRenderer = null; + isAttachedToRenderer = false; + } + + /** + * Send the given {@link Locale} configuration to Flutter. + * @param locale the user's locale + */ + private void setFlutterLocale(@NonNull Locale locale) { + mFlutterLocalizationChannel.invokeMethod("setLocale", Arrays.asList(locale.getLanguage(), locale.getCountry())); + } + + /** + * Send various user preferences of this Android device to Flutter. + * + * For example, sends the user's "text scale factor" preferences, as well as the user's clock + * format preference. + */ + private void setFlutterUserSettings() { + Map message = new HashMap<>(); + message.put("textScaleFactor", getResources().getConfiguration().fontScale); + message.put("alwaysUse24HourFormat", DateFormat.is24HourFormat(getContext())); + mFlutterSettingsChannel.send(message); + } + //----- END FLUTTER INTEGRATION ----- + + //------ START RenderingSurface ----- + @Override + public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { + try { + if (mAccessibilityNodeProvider != null) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + mAccessibilityNodeProvider.updateCustomAccessibilityActions(buffer, strings); + } + } catch (Exception ex) { + Log.e(TAG, "Uncaught exception while updating local context actions", ex); + } + } + + @Override + public void updateSemantics(ByteBuffer buffer, String[] strings) { + try { + if (mAccessibilityNodeProvider != null) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + mAccessibilityNodeProvider.updateSemantics(buffer, strings); + } + } catch (Exception ex) { + Log.e(TAG, "Uncaught exception while updating semantics", ex); + } + } + + @Override + public void onFirstFrameRendered() { + // no-op + } + //------ END RenderingSurface ---- + + /// Must match the enum defined in window.dart. + private enum AccessibilityFeature { + ACCESSIBLE_NAVIGATION(1 << 0), + INVERT_COLORS(1 << 1), // NOT SUPPORTED + DISABLE_ANIMATIONS(1 << 2); + + AccessibilityFeature(int value) { + this.value = value; + } + + final int value; + } + + // Listens to the global TRANSITION_ANIMATION_SCALE property and notifies us so + // that we can disable animations in Flutter. + private class AnimationScaleObserver extends ContentObserver { + AnimationScaleObserver(Handler handler) { + super(handler); } @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getContext().getContentResolver().unregisterContentObserver(mAnimationScaleObserver); - mAccessibilityManager.removeAccessibilityStateChangeListener(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationListener); - } - } - - private void resetWillNotDraw() { - if (!mIsSoftwareRenderingEnabled) { - setWillNotDraw(!(mAccessibilityEnabled || mTouchExplorationEnabled)); - } else { - setWillNotDraw(false); - } + public void onChange(boolean selfChange) { + this.onChange(selfChange, null); } + // TODO(mattcarroll): getString() requires API 17. what should we do for earlier APIs? + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override - public void onAccessibilityStateChanged(boolean enabled) { - if (enabled) { - ensureAccessibilityEnabled(); - } else { - mAccessibilityEnabled = false; - if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.setAccessibilityEnabled(false); - } - nativeSetSemanticsEnabled(mNativeView.get(), false); - } - resetWillNotDraw(); - } - - /// Must match the enum defined in window.dart. - private enum AccessibilityFeature { - ACCESSIBLE_NAVIGATION(1 << 0), - INVERT_COLORS(1 << 1), // NOT SUPPORTED - DISABLE_ANIMATIONS(1 << 2); - - AccessibilityFeature(int value) { - this.value = value; - } - - final int value; - } - - // Listens to the global TRANSITION_ANIMATION_SCALE property and notifies us so - // that we can disable animations in Flutter. - private class AnimationScaleObserver extends ContentObserver { - public AnimationScaleObserver(Handler handler) { - super(handler); - } - - @Override - public void onChange(boolean selfChange) { - this.onChange(selfChange, null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - String value = Settings.Global.getString(getContext().getContentResolver(), - Settings.Global.TRANSITION_ANIMATION_SCALE); - if (value == "0") { - mAccessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; - } else { - mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; - } - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); - } - } - - class TouchExplorationListener implements AccessibilityManager.TouchExplorationStateChangeListener { - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - if (enabled) { - mTouchExplorationEnabled = true; - ensureAccessibilityEnabled(); - mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); - } else { - mTouchExplorationEnabled = false; - if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.handleTouchExplorationExit(); - } - mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); - } - resetWillNotDraw(); - } - } - + public void onChange(boolean selfChange, Uri uri) { + String value = Settings.Global.getString(getContext().getContentResolver(), + Settings.Global.TRANSITION_ANIMATION_SCALE); + if (value.equals("0")) { + mAccessibilityFeatureFlags ^= AccessibilityFeature.DISABLE_ANIMATIONS.value; + } else { + mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; + } + flutterRenderer.setAccessibilityFeatures(mAccessibilityFeatureFlags); + } + } + + // TODO: TouchExplorationStateChangeListener requires API 19. What do we do about earlier APIs? + @SuppressLint("NewApi") + class TouchExplorationListener implements AccessibilityManager.TouchExplorationStateChangeListener { @Override - public AccessibilityNodeProvider getAccessibilityNodeProvider() { - if (mAccessibilityEnabled) - return mAccessibilityNodeProvider; - // TODO(goderbauer): when a11y is off this should return a one-off snapshot of - // the a11y - // tree. - return null; - } - - private AccessibilityBridge mAccessibilityNodeProvider; - - void ensureAccessibilityEnabled() { - if (!isAttached()) - return; - mAccessibilityEnabled = true; - if (mAccessibilityNodeProvider == null) { - mAccessibilityNodeProvider = new AccessibilityBridge(this); - } - nativeSetSemanticsEnabled(mNativeView.get(), true); - mAccessibilityNodeProvider.setAccessibilityEnabled(true); - } - - void resetAccessibilityTree() { + public void onTouchExplorationStateChanged(boolean enabled) { + if (enabled) { + mTouchExplorationEnabled = true; + ensureAccessibilityEnabled(); + mAccessibilityFeatureFlags ^= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; + flutterRenderer.setAccessibilityFeatures(mAccessibilityFeatureFlags); + } else { + mTouchExplorationEnabled = false; if (mAccessibilityNodeProvider != null) { - mAccessibilityNodeProvider.reset(); - } - } - - private boolean handleAccessibilityHoverEvent(MotionEvent event) { - if (!mTouchExplorationEnabled) { - return false; - } - if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER || event.getAction() == MotionEvent.ACTION_HOVER_MOVE) { - mAccessibilityNodeProvider.handleTouchExploration(event.getX(), event.getY()); - } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { - mAccessibilityNodeProvider.handleTouchExplorationExit(); - } else { - Log.d("flutter", "unexpected accessibility hover event: " + event); - return false; - } - return true; - } - - @Override - public void send(String channel, ByteBuffer message) { - send(channel, message, null); - } - - @Override - public void send(String channel, ByteBuffer message, BinaryReply callback) { - if (!isAttached()) { - Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); - return; - } - mNativeView.send(channel, message, callback); - } - - @Override - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - mNativeView.setMessageHandler(channel, handler); - } - - /** - * Broadcast receiver used to discover active Flutter instances. - * - * This is used by the `flutter` tool to find the observatory ports for all the - * active Flutter views. We dump the data to the logs and the tool scrapes the - * log lines for the data. - */ - private class DiscoveryReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - URI observatoryUri = URI.create(FlutterNativeView.getObservatoryUri()); - JSONObject discover = new JSONObject(); - try { - discover.put("id", getContext().getPackageName()); - discover.put("observatoryPort", observatoryUri.getPort()); - Log.i(TAG, "DISCOVER: " + discover); // The tool looks for this data. See - // android_device.dart. - } catch (JSONException e) { - } - } - } - - /** - * Listener will be called on the Android UI thread once when Flutter renders - * the first frame. - */ - public interface FirstFrameListener { - void onFirstFrame(); - } - - @Override - public SurfaceTextureEntry createSurfaceTexture() { - final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - surfaceTexture.detachFromGLContext(); - final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), - surfaceTexture); - nativeRegisterTexture(mNativeView.get(), entry.id(), surfaceTexture); - return entry; - } - - final class SurfaceTextureRegistryEntry implements SurfaceTextureEntry { - private final long id; - private final SurfaceTexture surfaceTexture; - private boolean released; - - SurfaceTextureRegistryEntry(long id, SurfaceTexture surfaceTexture) { - this.id = id; - this.surfaceTexture = surfaceTexture; - this.surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() { - @Override - public void onFrameAvailable(SurfaceTexture texture) { - nativeMarkTextureFrameAvailable(mNativeView.get(), SurfaceTextureRegistryEntry.this.id); - } - }); - } - - @Override - public SurfaceTexture surfaceTexture() { - return surfaceTexture; - } - - @Override - public long id() { - return id; - } - - @Override - public void release() { - if (released) { - return; - } - released = true; - nativeUnregisterTexture(mNativeView.get(), id); - surfaceTexture.release(); + mAccessibilityNodeProvider.handleTouchExplorationExit(); } + mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; + flutterRenderer.setAccessibilityFeatures(mAccessibilityFeatureFlags); + } + resetWillNotDraw(); } + } } diff --git a/shell/platform/android/io/flutter/embedding/legacy/AccessibilityBridge.java b/shell/platform/android/io/flutter/embedding/legacy/AccessibilityBridge.java index c03992b2f9097..7ff2e09176836 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/embedding/legacy/AccessibilityBridge.java @@ -4,6 +4,7 @@ package io.flutter.embedding.legacy; +import android.annotation.TargetApi; import android.app.Activity; import android.graphics.Rect; import android.opengl.Matrix; @@ -15,20 +16,16 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import io.flutter.embedding.FlutterEngine; import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.embedding.FlutterView; +import java.nio.ByteBuffer; +import java.util.*; + +@TargetApi(Build.VERSION_CODES.KITKAT) public class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMessageChannel.MessageHandler { private static final String TAG = "FlutterView"; @@ -56,7 +53,7 @@ public class AccessibilityBridge private final BasicMessageChannel mFlutterAccessibilityChannel; - enum Action { + public enum Action { TAP(1 << 0), LONG_PRESS(1 << 1), SCROLL_LEFT(1 << 2), @@ -83,7 +80,7 @@ enum Action { this.value = value; } - final int value; + public final int value; } enum Flag { @@ -114,18 +111,18 @@ enum Flag { final int value; } - AccessibilityBridge(FlutterView owner) { + public AccessibilityBridge(FlutterView owner, BinaryMessenger pluginMessenger) { assert owner != null; mOwner = owner; mObjects = new HashMap(); mCustomAccessibilityActions = new HashMap(); previousRoutes = new ArrayList<>(); mFlutterAccessibilityChannel = new BasicMessageChannel<>( - owner, "flutter/accessibility", StandardMessageCodec.INSTANCE); + pluginMessenger, "flutter/accessibility", StandardMessageCodec.INSTANCE); mDecorView = ((Activity) owner.getContext()).getWindow().getDecorView(); } - void setAccessibilityEnabled(boolean accessibilityEnabled) { + public void setAccessibilityEnabled(boolean accessibilityEnabled) { mAccessibilityEnabled = accessibilityEnabled; if (accessibilityEnabled) { mFlutterAccessibilityChannel.setMessageHandler(this); @@ -579,14 +576,14 @@ private CustomAccessibilityAction getOrCreateAction(int id) { return action; } - void handleTouchExplorationExit() { + public void handleTouchExplorationExit() { if (mHoveredObject != null) { sendAccessibilityEvent(mHoveredObject.id, AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); mHoveredObject = null; } } - void handleTouchExploration(float x, float y) { + public void handleTouchExploration(float x, float y) { if (mObjects.isEmpty()) { return; } @@ -603,7 +600,7 @@ void handleTouchExploration(float x, float y) { } } - void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { + public void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { ArrayList updatedActions = new ArrayList(); while (buffer.hasRemaining()) { @@ -617,7 +614,7 @@ void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { } } - void updateSemantics(ByteBuffer buffer, String[] strings) { + public void updateSemantics(ByteBuffer buffer, String[] strings) { ArrayList updated = new ArrayList(); while (buffer.hasRemaining()) { int id = buffer.getInt(); @@ -898,7 +895,7 @@ private void willRemoveSemanticsObject(SemanticsObject object) { } } - void reset() { + public void reset() { mObjects.clear(); if (mA11yFocusedObject != null) sendAccessibilityEvent(mA11yFocusedObject.id, diff --git a/shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java b/shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java deleted file mode 100644 index 3319281c0a1f4..0000000000000 --- a/shell/platform/android/io/flutter/embedding/legacy/FlutterNativeView.java +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.embedding.legacy; - -import android.app.Activity; -import android.content.Context; -import android.content.res.AssetManager; -import android.util.Log; - -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.view.FlutterRunArguments; -import io.flutter.embedding.FlutterView; - -public class FlutterNativeView implements BinaryMessenger { - private static final String TAG = "FlutterNativeView"; - - private final Map mMessageHandlers; - private int mNextReplyId = 1; - private final Map mPendingReplies = new HashMap<>(); - - private final FlutterPluginRegistry mPluginRegistry; - private long mNativePlatformView; - private FlutterView mFlutterView; - private final Context mContext; - private boolean applicationIsRunning; - - public FlutterNativeView(Context context) { - this(context, false); - } - - public FlutterNativeView(Context context, boolean isBackgroundView) { - mContext = context; - mPluginRegistry = new FlutterPluginRegistry(this, context); - attach(this, isBackgroundView); - assertAttached(); - mMessageHandlers = new HashMap<>(); - } - - public void detach() { - mPluginRegistry.detach(); - mFlutterView = null; - nativeDetach(mNativePlatformView); - } - - public void destroy() { - mPluginRegistry.destroy(); - mFlutterView = null; - nativeDestroy(mNativePlatformView); - mNativePlatformView = 0; - applicationIsRunning = false; - } - - public FlutterPluginRegistry getPluginRegistry() { - return mPluginRegistry; - } - - public void attachViewAndActivity(FlutterView flutterView, Activity activity) { - mFlutterView = flutterView; - mPluginRegistry.attach(flutterView, activity); - } - - public boolean isAttached() { - return mNativePlatformView != 0; - } - - public long get() { - return mNativePlatformView; - } - - public void assertAttached() { - if (!isAttached()) throw new AssertionError("Platform view is not attached"); - } - - public void runFromBundle(FlutterRunArguments args) { - if (args.bundlePath == null) { - throw new AssertionError("A bundlePath must be specified"); - } else if (args.entrypoint == null) { - throw new AssertionError("An entrypoint must be specified"); - } - runFromBundleInternal(args.bundlePath, args.entrypoint, args.libraryPath, args.defaultPath); - } - - /** - * @deprecated - * Please use runFromBundle with `FlutterRunArguments`. - * Parameter `reuseRuntimeController` has no effect. - */ - @Deprecated - public void runFromBundle(String bundlePath, String defaultPath, String entrypoint, - boolean reuseRuntimeController) { - runFromBundleInternal(bundlePath, entrypoint, null, defaultPath); - } - - private void runFromBundleInternal(String bundlePath, String entrypoint, - String libraryPath, String defaultPath) { - assertAttached(); - if (applicationIsRunning) - throw new AssertionError( - "This Flutter engine instance is already running an application"); - nativeRunBundleAndSnapshotFromLibrary(mNativePlatformView, bundlePath, - defaultPath, entrypoint, libraryPath, mContext.getResources().getAssets()); - - applicationIsRunning = true; - } - - public boolean isApplicationRunning() { - return applicationIsRunning; - } - - public static String getObservatoryUri() { - return nativeGetObservatoryUri(); - } - - @Override - public void send(String channel, ByteBuffer message) { - send(channel, message, null); - } - - @Override - public void send(String channel, ByteBuffer message, BinaryReply callback) { - if (!isAttached()) { - Log.d(TAG, "FlutterView.send called on a detached view, channel=" + channel); - return; - } - - int replyId = 0; - if (callback != null) { - replyId = mNextReplyId++; - mPendingReplies.put(replyId, callback); - } - if (message == null) { - nativeDispatchEmptyPlatformMessage(mNativePlatformView, channel, replyId); - } else { - nativeDispatchPlatformMessage( - mNativePlatformView, channel, message, message.position(), replyId); - } - } - - @Override - public void setMessageHandler(String channel, BinaryMessageHandler handler) { - if (handler == null) { - mMessageHandlers.remove(channel); - } else { - mMessageHandlers.put(channel, handler); - } - } - - private void attach(FlutterNativeView view, boolean isBackgroundView) { - mNativePlatformView = nativeAttach(view, isBackgroundView); - } - - // Called by native to send us a platform message. - private void handlePlatformMessage(final String channel, byte[] message, final int replyId) { - assertAttached(); - BinaryMessageHandler handler = mMessageHandlers.get(channel); - if (handler != null) { - try { - final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message)); - handler.onMessage(buffer, new BinaryReply() { - private final AtomicBoolean done = new AtomicBoolean(false); - @Override - public void reply(ByteBuffer reply) { - if (!isAttached()) { - Log.d(TAG, - "handlePlatformMessage replying to a detached view, channel=" - + channel); - return; - } - if (done.getAndSet(true)) { - throw new IllegalStateException("Reply already submitted"); - } - if (reply == null) { - nativeInvokePlatformMessageEmptyResponseCallback( - mNativePlatformView, replyId); - } else { - nativeInvokePlatformMessageResponseCallback( - mNativePlatformView, replyId, reply, reply.position()); - } - } - }); - } catch (Exception ex) { - Log.e(TAG, "Uncaught exception in binary message listener", ex); - nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId); - } - return; - } - nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId); - } - - // Called by native to respond to a platform message that we sent. - private void handlePlatformMessageResponse(int replyId, byte[] reply) { - BinaryReply callback = mPendingReplies.remove(replyId); - if (callback != null) { - try { - callback.reply(reply == null ? null : ByteBuffer.wrap(reply)); - } catch (Exception ex) { - Log.e(TAG, "Uncaught exception in binary message reply handler", ex); - } - } - } - - // Called by native to update the semantics/accessibility tree. - private void updateSemantics(ByteBuffer buffer, String[] strings) { - if (mFlutterView == null) return; - mFlutterView.updateSemantics(buffer, strings); - } - - // Called by native to update the custom accessibility actions. - private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) { - if (mFlutterView == null) - return; - mFlutterView.updateCustomAccessibilityActions(buffer, strings); - } - - // Called by native to notify first Flutter frame rendered. - private void onFirstFrame() { - if (mFlutterView == null) return; - mFlutterView.onFirstFrame(); - } - - // Called by native to notify when the engine is restarted (cold reload). - @SuppressWarnings("unused") - private void onPreEngineRestart() { - if (mPluginRegistry == null) - return; - mPluginRegistry.onPreEngineRestart(); - } - - private static native long nativeAttach(FlutterNativeView view, boolean isBackgroundView); - private static native void nativeDestroy(long nativePlatformViewAndroid); - private static native void nativeDetach(long nativePlatformViewAndroid); - - private static native void nativeRunBundleAndSnapshotFromLibrary( - long nativePlatformViewAndroid, String bundlePath, - String defaultPath, String entrypoint, String libraryUrl, - AssetManager manager); - - private static native String nativeGetObservatoryUri(); - - // Send an empty platform message to Dart. - private static native void nativeDispatchEmptyPlatformMessage( - long nativePlatformViewAndroid, String channel, int responseId); - - // Send a data-carrying platform message to Dart. - private static native void nativeDispatchPlatformMessage(long nativePlatformViewAndroid, - String channel, ByteBuffer message, int position, int responseId); - - // Send an empty response to a platform message received from Dart. - private static native void nativeInvokePlatformMessageEmptyResponseCallback( - long nativePlatformViewAndroid, int responseId); - - // Send a data-carrying response to a platform message received from Dart. - private static native void nativeInvokePlatformMessageResponseCallback( - long nativePlatformViewAndroid, int responseId, ByteBuffer message, int position); -} diff --git a/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java b/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java index 7069c97c84ca9..37def30554432 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java +++ b/shell/platform/android/io/flutter/embedding/legacy/FlutterPluginRegistry.java @@ -8,16 +8,16 @@ import android.content.Context; import android.content.Intent; +import io.flutter.embedding.FlutterEngine; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.view.FlutterMain; +import io.flutter.view.TextureRegistry; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.view.FlutterMain; -import io.flutter.embedding.FlutterView; -import io.flutter.view.TextureRegistry; - public class FlutterPluginRegistry implements PluginRegistry, PluginRegistry.RequestPermissionsResultListener, @@ -29,8 +29,8 @@ public class FlutterPluginRegistry private Activity mActivity; private Context mAppContext; - private FlutterNativeView mNativeView; - private FlutterView mFlutterView; + private BinaryMessenger mMessenger; + private FlutterEngine flutterEngine; private final PlatformViewsController mPlatformViewsController; private final Map mPluginMap = new LinkedHashMap<>(0); @@ -40,8 +40,8 @@ public class FlutterPluginRegistry private final List mUserLeaveHintListeners = new ArrayList<>(0); private final List mViewDestroyListeners = new ArrayList<>(0); - public FlutterPluginRegistry(FlutterNativeView nativeView, Context context) { - mNativeView = nativeView; + public FlutterPluginRegistry(BinaryMessenger messenger, Context context) { + mMessenger = messenger; mAppContext = context; mPlatformViewsController = new PlatformViewsController(); } @@ -66,16 +66,16 @@ public Registrar registrarFor(String pluginKey) { return new FlutterRegistrar(pluginKey); } - public void attach(FlutterView flutterView, Activity activity) { - mFlutterView = flutterView; + public void attach(FlutterEngine flutterEngine, Activity activity) { + this.flutterEngine = flutterEngine; mActivity = activity; - mPlatformViewsController.attachFlutterView(flutterView); + mPlatformViewsController.attachFlutterEngine(flutterEngine, activity); } public void detach() { - mPlatformViewsController.detachFlutterView(); + mPlatformViewsController.detachFlutterEngine(); mPlatformViewsController.onFlutterViewDestroyed(); - mFlutterView = null; + flutterEngine = null; mActivity = null; } @@ -90,6 +90,11 @@ private class FlutterRegistrar implements Registrar { this.pluginKey = pluginKey; } + @Override + public FlutterEngine getFlutterEngine() { + return flutterEngine; + } + @Override public Activity activity() { return mActivity; @@ -107,12 +112,12 @@ public Context activeContext() { @Override public BinaryMessenger messenger() { - return mNativeView; + return mMessenger; } @Override public TextureRegistry textures() { - return mFlutterView; + return flutterEngine.getRenderer(); } @Override @@ -120,11 +125,6 @@ public PlatformViewRegistry platformViewRegistry() { return mPlatformViewsController.getRegistry(); } - @Override - public FlutterView view() { - return mFlutterView; - } - @Override public String lookupKeyForAsset(String asset) { return FlutterMain.getLookupKeyForAsset(asset); @@ -229,10 +229,10 @@ public void onUserLeaveHint() { } @Override - public boolean onViewDestroy(FlutterNativeView view) { + public boolean onViewDestroy(FlutterEngine engine) { boolean handled = false; for (ViewDestroyListener listener : mViewDestroyListeners) { - if (listener.onViewDestroy(view)) { + if (listener.onViewDestroy(engine)) { handled = true; } } diff --git a/shell/platform/android/io/flutter/embedding/legacy/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/embedding/legacy/InputConnectionAdaptor.java index a05febd397f39..1c3e94ef66c6e 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/embedding/legacy/InputConnectionAdaptor.java @@ -4,20 +4,22 @@ package io.flutter.embedding.legacy; +import android.annotation.TargetApi; import android.content.Context; +import android.os.Build; import android.text.Editable; import android.text.Selection; import android.view.KeyEvent; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.embedding.FlutterView; import java.util.Arrays; import java.util.HashMap; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.view.FlutterView; - +@TargetApi(Build.VERSION_CODES.CUPCAKE) class InputConnectionAdaptor extends BaseInputConnection { private final FlutterView mFlutterView; private final int mClient; diff --git a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewFactory.java b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewFactory.java index ba96316e1031f..b4c6bc4acbdc1 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewFactory.java +++ b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewFactory.java @@ -5,7 +5,6 @@ package io.flutter.embedding.legacy; import android.content.Context; - import io.flutter.plugin.common.MessageCodec; import io.flutter.plugin.platform.PlatformView; diff --git a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewRegistry.java b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewRegistry.java index 67fc5acf5435a..382afa7b47c47 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewRegistry.java +++ b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewRegistry.java @@ -4,7 +4,6 @@ package io.flutter.embedding.legacy; - /** * Registry for platform view factories. *

diff --git a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewsController.java b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewsController.java index e5b8ceeea6bdd..4bbb80236c75b 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/embedding/legacy/PlatformViewsController.java @@ -5,23 +5,25 @@ package io.flutter.embedding.legacy; import android.annotation.TargetApi; +import android.content.Context; import android.os.Build; import android.util.Log; import android.view.MotionEvent; import android.view.View; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import io.flutter.embedding.FlutterEngine; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.embedding.FlutterView; import io.flutter.view.TextureRegistry; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static android.view.MotionEvent.PointerCoords; import static android.view.MotionEvent.PointerProperties; @@ -41,7 +43,8 @@ public class PlatformViewsController implements MethodChannel.MethodCallHandler private final PlatformViewRegistryImpl mRegistry; - private FlutterView mFlutterView; + private FlutterEngine flutterEngine; + private Context context; private final HashMap vdControllers; @@ -50,20 +53,21 @@ public PlatformViewsController() { vdControllers = new HashMap<>(); } - public void attachFlutterView(FlutterView view) { - if (mFlutterView != null) + public void attachFlutterEngine(FlutterEngine flutterEngine, Context context) { + if (this.flutterEngine != null) throw new AssertionError( - "A PlatformViewsController can only be attached to a single FlutterView.\n" + - "attachFlutterView was called while a FlutterView was already attached." + "A PlatformViewsController can only be attached to a single FlutterEngine.\n" + + "attachFlutterEngine was called while a FlutterEngine was already attached." ); - mFlutterView = view; - MethodChannel channel = new MethodChannel(view, CHANNEL_NAME, StandardMethodCodec.INSTANCE); + this.flutterEngine = flutterEngine; + this.context = context; + MethodChannel channel = new MethodChannel(flutterEngine, CHANNEL_NAME, StandardMethodCodec.INSTANCE); channel.setMethodCallHandler(this); } - public void detachFlutterView() { - mFlutterView.setMessageHandler(CHANNEL_NAME, null); - mFlutterView = null; + public void detachFlutterEngine() { + flutterEngine.setMessageHandler(CHANNEL_NAME, null); + flutterEngine = null; } public PlatformViewRegistry getRegistry() { @@ -147,9 +151,9 @@ private void createPlatformView(MethodCall call, MethodChannel.Result result) { createParams = viewFactory.getCreateArgsCodec().decodeMessage(ByteBuffer.wrap((byte[]) args.get("params"))); } - TextureRegistry.SurfaceTextureEntry textureEntry = mFlutterView.createSurfaceTexture(); + TextureRegistry.SurfaceTextureEntry textureEntry = flutterEngine.getRenderer().createSurfaceTexture(); VirtualDisplayController vdController = VirtualDisplayController.create( - mFlutterView.getContext(), + context, viewFactory, textureEntry.surfaceTexture(), toPhysicalPixels(logicalWidth), @@ -220,10 +224,11 @@ public void run() { ); } + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) private void onTouch(MethodCall call, MethodChannel.Result result) { List args = call.arguments(); - float density = mFlutterView.getContext().getResources().getDisplayMetrics().density; + float density = context.getResources().getDisplayMetrics().density; int id = (int) args.get(0); Number downTime = (Number) args.get(1); @@ -318,6 +323,7 @@ private static List parsePointerPropertiesList(Object rawProp return pointerProperties; } + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @SuppressWarnings("unchecked") private static PointerProperties parsePointerProperties(Object rawProperties) { List propertiesList = (List) rawProperties; @@ -337,6 +343,7 @@ private static List parsePointerCoordsList(Object rawCoordsList, return pointerCoords; } + @TargetApi(Build.VERSION_CODES.GINGERBREAD) @SuppressWarnings("unchecked") private static PointerCoords parsePointerCoords(Object rawCoords, float density) { List coordsList = (List) rawCoords; @@ -354,7 +361,7 @@ private static PointerCoords parsePointerCoords(Object rawCoords, float density) } private int toPhysicalPixels(double logicalPixels) { - float density = mFlutterView.getContext().getResources().getDisplayMetrics().density; + float density = context.getResources().getDisplayMetrics().density; return (int) Math.round(logicalPixels * density); } diff --git a/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java b/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java index 8e229d1643d25..2d315a884b544 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java +++ b/shell/platform/android/io/flutter/embedding/legacy/PluginRegistry.java @@ -8,8 +8,8 @@ import android.content.Context; import android.content.Intent; +import io.flutter.embedding.FlutterEngine; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.embedding.FlutterView; import io.flutter.view.TextureRegistry; /** @@ -61,6 +61,9 @@ public interface PluginRegistry { * Receiver of registrations from a single plugin. */ interface Registrar { + // TODO(mattcarroll): Added because plugins need to requisition SurfaceTextures + FlutterEngine getFlutterEngine(); + /** * Returns the {@link Activity} that forms the plugin's operating context. * @@ -107,13 +110,6 @@ interface Registrar { */ PlatformViewRegistry platformViewRegistry(); - /** - * Returns the {@link FlutterView} that's instantiated by this plugin's - * {@link #activity() activity}. - */ - FlutterView view(); - - /** * Returns the file name for the given asset. * The returned file name can be used to access the asset in the APK @@ -283,7 +279,7 @@ interface UserLeaveHintListener { * adopt the FlutterNativeView by retaining a reference and returning true. */ interface ViewDestroyListener { - boolean onViewDestroy(FlutterNativeView view); + boolean onViewDestroy(FlutterEngine engine); } /** diff --git a/shell/platform/android/io/flutter/embedding/legacy/SingleViewPresentation.java b/shell/platform/android/io/flutter/embedding/legacy/SingleViewPresentation.java index b684e40190bb1..161f3f24e48be 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/embedding/legacy/SingleViewPresentation.java @@ -12,20 +12,15 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; -import android.view.Display; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; +import android.view.*; import android.widget.FrameLayout; +import io.flutter.plugin.platform.PlatformView; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import io.flutter.plugin.platform.PlatformView; - import static android.content.Context.WINDOW_SERVICE; /* diff --git a/shell/platform/android/io/flutter/embedding/legacy/TextInputPlugin.java b/shell/platform/android/io/flutter/embedding/legacy/TextInputPlugin.java index 6cd34ff836792..dd0759fa618d1 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/embedding/legacy/TextInputPlugin.java @@ -4,7 +4,9 @@ package io.flutter.embedding.legacy; +import android.annotation.TargetApi; import android.content.Context; +import android.os.Build; import android.text.Editable; import android.text.InputType; import android.text.Selection; @@ -13,20 +15,22 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - +import io.flutter.embedding.FlutterEngine; +import io.flutter.plugin.common.BinaryMessenger; 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 io.flutter.embedding.FlutterView; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; /** * Android implementation of the text input plugin. */ +@TargetApi(Build.VERSION_CODES.CUPCAKE) public class TextInputPlugin implements MethodCallHandler { private final FlutterView mView; private final InputMethodManager mImm; @@ -36,11 +40,11 @@ public class TextInputPlugin implements MethodCallHandler { private Editable mEditable; private boolean mRestartInputPending; - public TextInputPlugin(FlutterView view) { + public TextInputPlugin(FlutterView view, BinaryMessenger pluginMessenger) { mView = view; mImm = (InputMethodManager) view.getContext().getSystemService( Context.INPUT_METHOD_SERVICE); - mFlutterChannel = new MethodChannel(view, "flutter/textinput", JSONMethodCodec.INSTANCE); + mFlutterChannel = new MethodChannel(pluginMessenger, "flutter/textinput", JSONMethodCodec.INSTANCE); mFlutterChannel.setMethodCallHandler(this); } @@ -160,8 +164,8 @@ public InputConnection createInputConnection(FlutterView view, EditorInfo outAtt } outAttrs.imeOptions |= enterAction; - io.flutter.embedding.legacy.InputConnectionAdaptor connection = - new io.flutter.embedding.legacy.InputConnectionAdaptor(view, mClient, mFlutterChannel, mEditable); + InputConnectionAdaptor connection = + new InputConnectionAdaptor(view, mClient, mFlutterChannel, mEditable); outAttrs.initialSelStart = Selection.getSelectionStart(mEditable); outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable); diff --git a/shell/platform/android/io/flutter/embedding/legacy/VirtualDisplayController.java b/shell/platform/android/io/flutter/embedding/legacy/VirtualDisplayController.java index 9e59cfb7d25bd..516028ca6ca7a 100644 --- a/shell/platform/android/io/flutter/embedding/legacy/VirtualDisplayController.java +++ b/shell/platform/android/io/flutter/embedding/legacy/VirtualDisplayController.java @@ -13,7 +13,6 @@ import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; - import io.flutter.plugin.platform.PlatformView; @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) @@ -54,7 +53,7 @@ public static VirtualDisplayController create( private final int mDensityDpi; private final SurfaceTexture mSurfaceTexture; private VirtualDisplay mVirtualDisplay; - private io.flutter.embedding.legacy.SingleViewPresentation mPresentation; + private SingleViewPresentation mPresentation; private Surface mSurface; @@ -72,13 +71,13 @@ private VirtualDisplayController( mContext = context; mVirtualDisplay = virtualDisplay; mDensityDpi = context.getResources().getDisplayMetrics().densityDpi; - mPresentation = new io.flutter.embedding.legacy.SingleViewPresentation( + mPresentation = new SingleViewPresentation( context, mVirtualDisplay.getDisplay(), viewFactory, viewId, createParams); mPresentation.show(); } public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) { - final io.flutter.embedding.legacy.SingleViewPresentation.PresentationState presentationState = mPresentation.detachState(); + final SingleViewPresentation.PresentationState presentationState = mPresentation.detachState(); // We detach the surface to prevent it being destroyed when releasing the vd. // // setSurface is only available starting API 20. We could support API 19 by re-creating a new @@ -123,7 +122,7 @@ public void run() { public void onViewDetachedFromWindow(View v) {} }); - mPresentation = new io.flutter.embedding.legacy.SingleViewPresentation(mContext, mVirtualDisplay.getDisplay(), presentationState); + mPresentation = new SingleViewPresentation(mContext, mVirtualDisplay.getDisplay(), presentationState); mPresentation.show(); } diff --git a/shell/platform/android/platform_view_android_jni.cc b/shell/platform/android/platform_view_android_jni.cc index d93f9357144a6..5d4e06d18ad2d 100644 --- a/shell/platform/android/platform_view_android_jni.cc +++ b/shell/platform/android/platform_view_android_jni.cc @@ -46,9 +46,21 @@ bool CheckException(JNIEnv* env) { static fml::jni::ScopedJavaGlobalRef* g_flutter_callback_info_class = nullptr; + +// FlutterView.java, from original embedding API. Not used in 2nd iteration of +// embedding. static fml::jni::ScopedJavaGlobalRef* g_flutter_view_class = nullptr; + +// FlutterNativeView.java, from original embedding API. Not used in 2nd +// iteration of embedding. static fml::jni::ScopedJavaGlobalRef* g_flutter_native_view_class = nullptr; + +// FlutterJNI.java, used in 2nd iteration of embedding to centralize all JNI +// calls. +static fml::jni::ScopedJavaGlobalRef* g_flutter_jni_class = nullptr; +static fml::jni::ScopedJavaGlobalRef* g_flutter_engine_class = nullptr; + static fml::jni::ScopedJavaGlobalRef* g_surface_texture_class = nullptr; // Called By Native @@ -150,6 +162,7 @@ static jlong Attach(JNIEnv* env, jclass clazz, jobject flutterView, jboolean is_background_view) { + FML_LOG(ERROR) << "This is a test!"; fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterView); auto shell_holder = std::make_unique( FlutterMain::Get().GetSettings(), java_object, is_background_view); @@ -160,14 +173,37 @@ static jlong Attach(JNIEnv* env, } } +static jlong AttachJNI(JNIEnv* env, + jclass clazz, + jobject flutterJNI, + jboolean is_background_view) { + FML_LOG(ERROR) << "Attaching to FlutterJNI"; + fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI); + auto shell_holder = std::make_unique( + FlutterMain::Get().GetSettings(), java_object, is_background_view); + if (shell_holder->IsValid()) { + return reinterpret_cast(shell_holder.release()); + } else { + return 0; + } +} + static void Detach(JNIEnv* env, jobject jcaller, jlong shell_holder) { // Nothing to do. } +static void DetachJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { + // Nothing to do. +} + static void Destroy(JNIEnv* env, jobject jcaller, jlong shell_holder) { delete ANDROID_SHELL_HOLDER; } +static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { + delete ANDROID_SHELL_HOLDER; +} + static jstring GetObservatoryUri(JNIEnv* env, jclass clazz) { return env->NewStringUTF( blink::DartServiceIsolate::GetObservatoryUri().c_str()); @@ -541,39 +577,18 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env, ); } -bool PlatformViewAndroid::Register(JNIEnv* env) { - if (env == nullptr) { - return false; - } - - g_flutter_callback_info_class = new fml::jni::ScopedJavaGlobalRef( - env, env->FindClass("io/flutter/view/FlutterCallbackInformation")); - if (g_flutter_callback_info_class->is_null()) { - return false; - } - - g_flutter_callback_info_constructor = env->GetMethodID( - g_flutter_callback_info_class->obj(), "", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); - if (g_flutter_callback_info_constructor == nullptr) { - return false; - } - +bool RegisterOldApi(JNIEnv* env) { g_flutter_view_class = new fml::jni::ScopedJavaGlobalRef( env, env->FindClass("io/flutter/view/FlutterView")); if (g_flutter_view_class->is_null()) { + FML_LOG(ERROR) << "Could not locate FlutterView class"; return false; } g_flutter_native_view_class = new fml::jni::ScopedJavaGlobalRef( env, env->FindClass("io/flutter/view/FlutterNativeView")); if (g_flutter_native_view_class->is_null()) { - return false; - } - - g_surface_texture_class = new fml::jni::ScopedJavaGlobalRef( - env, env->FindClass("android/graphics/SurfaceTexture")); - if (g_surface_texture_class->is_null()) { + FML_LOG(ERROR) << "Could not locate FlutterNativeView class"; return false; } @@ -699,28 +714,16 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { }, }; - static const JNINativeMethod callback_info_methods[] = { - { - .name = "nativeLookupCallbackInformation", - .signature = "(J)Lio/flutter/view/FlutterCallbackInformation;", - .fnPtr = reinterpret_cast(&shell::LookupCallbackInformation), - }, - }; - if (env->RegisterNatives(g_flutter_native_view_class->obj(), native_view_methods, arraysize(native_view_methods)) != 0) { + FML_LOG(ERROR) << "Failed to RegisterNatives with FlutterNativeView."; return false; } if (env->RegisterNatives(g_flutter_view_class->obj(), view_methods, arraysize(view_methods)) != 0) { - return false; - } - - if (env->RegisterNatives(g_flutter_callback_info_class->obj(), - callback_info_methods, - arraysize(callback_info_methods)) != 0) { + FML_LOG(ERROR) << "Failed to RegisterNatives with FlutterView"; return false; } @@ -729,6 +732,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { "handlePlatformMessage", "(Ljava/lang/String;[BI)V"); if (g_handle_platform_message_method == nullptr) { + FML_LOG(ERROR) << "Could not locate handlePlatformMessage method"; return false; } @@ -737,6 +741,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { "handlePlatformMessageResponse", "(I[B)V"); if (g_handle_platform_message_response_method == nullptr) { + FML_LOG(ERROR) << "Could not locate handlePlatformMessageResponse method"; return false; } @@ -745,6 +750,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { "(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V"); if (g_update_semantics_method == nullptr) { + FML_LOG(ERROR) << "Could not locate updateSemantics method"; return false; } @@ -753,6 +759,8 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { "(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V"); if (g_update_custom_accessibility_actions_method == nullptr) { + FML_LOG(ERROR) + << "Could not locate updateCustomAccessibilityActions method"; return false; } @@ -760,6 +768,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { "onFirstFrame", "()V"); if (g_on_first_frame_method == nullptr) { + FML_LOG(ERROR) << "Could not locate onFirstFrame method"; return false; } @@ -767,6 +776,257 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { g_flutter_native_view_class->obj(), "onPreEngineRestart", "()V"); if (g_on_engine_restart_method == nullptr) { + FML_LOG(ERROR) << "Could not locate onEngineRestart method"; + return false; + } + + return true; +} + +bool RegisterNewApi(JNIEnv* env) { + g_flutter_engine_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("io/flutter/embedding/FlutterEngine")); + if (g_flutter_engine_class->is_null()) { + FML_LOG(ERROR) << "Failed to find FlutterEngine Class."; + return false; + } + + static const JNINativeMethod flutter_jni_methods[] = { + // Start of methods from FlutterNativeView + { + .name = "nativeAttach", + .signature = "(Lio/flutter/embedding/FlutterEngine;Z)J", + .fnPtr = reinterpret_cast(&shell::AttachJNI), + }, + { + .name = "nativeDetach", + .signature = "(J)V", + .fnPtr = reinterpret_cast(&shell::DetachJNI), + }, + { + .name = "nativeDestroy", + .signature = "(J)V", + .fnPtr = reinterpret_cast(&shell::DestroyJNI), + }, + { + .name = "nativeRunBundleAndSnapshotFromLibrary", + .signature = + "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;Landroid/content/res/AssetManager;)V", + .fnPtr = + reinterpret_cast(&shell::RunBundleAndSnapshotFromLibrary), + }, + { + .name = "nativeGetObservatoryUri", + .signature = "()Ljava/lang/String;", + .fnPtr = reinterpret_cast(&shell::GetObservatoryUri), + }, + { + .name = "nativeDispatchEmptyPlatformMessage", + .signature = "(JLjava/lang/String;I)V", + .fnPtr = + reinterpret_cast(&shell::DispatchEmptyPlatformMessage), + }, + { + .name = "nativeDispatchPlatformMessage", + .signature = "(JLjava/lang/String;Ljava/nio/ByteBuffer;II)V", + .fnPtr = reinterpret_cast(&shell::DispatchPlatformMessage), + }, + { + .name = "nativeInvokePlatformMessageResponseCallback", + .signature = "(JILjava/nio/ByteBuffer;I)V", + .fnPtr = reinterpret_cast( + &shell::InvokePlatformMessageResponseCallback), + }, + { + .name = "nativeInvokePlatformMessageEmptyResponseCallback", + .signature = "(JI)V", + .fnPtr = reinterpret_cast( + &shell::InvokePlatformMessageEmptyResponseCallback), + }, + + // Start of methods from FlutterView + { + .name = "nativeGetBitmap", + .signature = "(J)Landroid/graphics/Bitmap;", + .fnPtr = reinterpret_cast(&shell::GetBitmap), + }, + { + .name = "nativeSurfaceCreated", + .signature = "(JLandroid/view/Surface;)V", + .fnPtr = reinterpret_cast(&shell::SurfaceCreated), + }, + { + .name = "nativeSurfaceChanged", + .signature = "(JII)V", + .fnPtr = reinterpret_cast(&shell::SurfaceChanged), + }, + { + .name = "nativeSurfaceDestroyed", + .signature = "(J)V", + .fnPtr = reinterpret_cast(&shell::SurfaceDestroyed), + }, + { + .name = "nativeSetViewportMetrics", + .signature = "(JFIIIIIIIIII)V", + .fnPtr = reinterpret_cast(&shell::SetViewportMetrics), + }, + { + .name = "nativeDispatchPointerDataPacket", + .signature = "(JLjava/nio/ByteBuffer;I)V", + .fnPtr = reinterpret_cast(&shell::DispatchPointerDataPacket), + }, + { + .name = "nativeDispatchSemanticsAction", + .signature = "(JIILjava/nio/ByteBuffer;I)V", + .fnPtr = reinterpret_cast(&shell::DispatchSemanticsAction), + }, + { + .name = "nativeSetSemanticsEnabled", + .signature = "(JZ)V", + .fnPtr = reinterpret_cast(&shell::SetSemanticsEnabled), + }, + { + .name = "nativeSetAccessibilityFeatures", + .signature = "(JI)V", + .fnPtr = reinterpret_cast(&shell::SetAccessibilityFeatures), + }, + { + .name = "nativeGetIsSoftwareRenderingEnabled", + .signature = "()Z", + .fnPtr = reinterpret_cast(&shell::GetIsSoftwareRendering), + }, + { + .name = "nativeRegisterTexture", + .signature = "(JJLandroid/graphics/SurfaceTexture;)V", + .fnPtr = reinterpret_cast(&shell::RegisterTexture), + }, + { + .name = "nativeMarkTextureFrameAvailable", + .signature = "(JJ)V", + .fnPtr = reinterpret_cast(&shell::MarkTextureFrameAvailable), + }, + { + .name = "nativeUnregisterTexture", + .signature = "(JJ)V", + .fnPtr = reinterpret_cast(&shell::UnregisterTexture), + }, + }; + + if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods, + arraysize(flutter_jni_methods)) != 0) { + FML_LOG(ERROR) << "Failed to RegisterNatives with FlutterJNI"; + return false; + } + + g_handle_platform_message_method = + env->GetMethodID(g_flutter_engine_class->obj(), "handlePlatformMessage", + "(Ljava/lang/String;[BI)V"); + + if (g_handle_platform_message_method == nullptr) { + FML_LOG(ERROR) << "Could not locate handlePlatformMessage method"; + return false; + } + + g_handle_platform_message_response_method = env->GetMethodID( + g_flutter_engine_class->obj(), "handlePlatformMessageResponse", "(I[B)V"); + + if (g_handle_platform_message_response_method == nullptr) { + FML_LOG(ERROR) << "Could not locate handlePlatformMessageResponse method"; + return false; + } + + g_update_semantics_method = + env->GetMethodID(g_flutter_engine_class->obj(), "updateSemantics", + "(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V"); + + if (g_update_semantics_method == nullptr) { + FML_LOG(ERROR) << "Could not locate updateSemantics method"; + return false; + } + + g_update_custom_accessibility_actions_method = env->GetMethodID( + g_flutter_engine_class->obj(), "updateCustomAccessibilityActions", + "(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V"); + + if (g_update_custom_accessibility_actions_method == nullptr) { + FML_LOG(ERROR) + << "Could not locate updateCustomAccessibilityActions method"; + return false; + } + + g_on_first_frame_method = + env->GetMethodID(g_flutter_engine_class->obj(), "onFirstFrame", "()V"); + + if (g_on_first_frame_method == nullptr) { + FML_LOG(ERROR) << "Could not locate onFirstFrame method"; + return false; + } + + g_on_engine_restart_method = env->GetMethodID(g_flutter_engine_class->obj(), + "onPreEngineRestart", "()V"); + + if (g_on_engine_restart_method == nullptr) { + FML_LOG(ERROR) << "Could not locate onEngineRestart method"; + return false; + } + + return true; +} + +bool PlatformViewAndroid::Register(JNIEnv* env) { + FML_LOG(ERROR) << "Registering"; + if (env == nullptr) { + FML_LOG(ERROR) << "No JNIEnv provided"; + return false; + } + + g_flutter_callback_info_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("io/flutter/view/FlutterCallbackInformation")); + if (g_flutter_callback_info_class->is_null()) { + FML_LOG(ERROR) << "Could not locate FlutterCallbackInformation class"; + return false; + } + + g_flutter_callback_info_constructor = env->GetMethodID( + g_flutter_callback_info_class->obj(), "", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + if (g_flutter_callback_info_constructor == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterCallbackInformation constructor"; + return false; + } + + // TODO(mattcarroll): this assumes the use of the new API is based on what + // we're compiling against but that's not true. Need to support both + // simultaneously. + bool is_using_new_api = false; + g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("io/flutter/embedding/FlutterJNI")); + if (g_flutter_jni_class->is_null()) { + FML_LOG(ERROR) << "Failed to find FlutterJNI Class. Assuming old API"; + } else { + is_using_new_api = true; + } + + g_surface_texture_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("android/graphics/SurfaceTexture")); + if (g_surface_texture_class->is_null()) { + FML_LOG(ERROR) << "Could not locate SurfaceTexture class"; + return false; + } + + static const JNINativeMethod callback_info_methods[] = { + { + .name = "nativeLookupCallbackInformation", + .signature = "(J)Lio/flutter/view/FlutterCallbackInformation;", + .fnPtr = reinterpret_cast(&shell::LookupCallbackInformation), + }, + }; + + if (env->RegisterNatives(g_flutter_callback_info_class->obj(), + callback_info_methods, + arraysize(callback_info_methods)) != 0) { + FML_LOG(ERROR) << "Failed to RegisterNatives with FlutterCallbackInfo"; return false; } @@ -774,6 +1034,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { g_surface_texture_class->obj(), "attachToGLContext", "(I)V"); if (g_attach_to_gl_context_method == nullptr) { + FML_LOG(ERROR) << "Could not locate attachToGlContext method"; return false; } @@ -781,6 +1042,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { env->GetMethodID(g_surface_texture_class->obj(), "updateTexImage", "()V"); if (g_update_tex_image_method == nullptr) { + FML_LOG(ERROR) << "Could not locate updateTexImage method"; return false; } @@ -788,6 +1050,7 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { g_surface_texture_class->obj(), "getTransformMatrix", "([F)V"); if (g_get_transform_matrix_method == nullptr) { + FML_LOG(ERROR) << "Could not locate getTransformMatrix method"; return false; } @@ -795,10 +1058,15 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { g_surface_texture_class->obj(), "detachFromGLContext", "()V"); if (g_detach_from_gl_context_method == nullptr) { + FML_LOG(ERROR) << "Could not locate detachFromGlContext method"; return false; } - return true; + if (is_using_new_api) { + return RegisterNewApi(env); + } else { + return RegisterOldApi(env); + } } } // namespace shell