diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index dce9a455e9a7a..19e1ea2b11192 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -416,6 +416,11 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityEven
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterApplication.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/engine/FlutterEngine.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/common/BinaryCodec.java
diff --git a/shell/platform/android/.gitignore b/shell/platform/android/.gitignore
index 2e8ec55e9bf6b..598adcf6cd3d0 100644
--- a/shell/platform/android/.gitignore
+++ b/shell/platform/android/.gitignore
@@ -1,2 +1,5 @@
# Generated by Intellij's Android plugin
gen
+android.iml
+out/
+
diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn
index 73e2bac819af3..7842dcac1eceb 100644
--- a/shell/platform/android/BUILD.gn
+++ b/shell/platform/android/BUILD.gn
@@ -102,6 +102,11 @@ java_library("flutter_shell_java") {
"io/flutter/app/FlutterApplication.java",
"io/flutter/app/FlutterFragmentActivity.java",
"io/flutter/app/FlutterPluginRegistry.java",
+ "io/flutter/embedding/engine/FlutterEngine.java",
+ "io/flutter/embedding/engine/FlutterJNI.java",
+ "io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
+ "io/flutter/embedding/engine/renderer/FlutterRenderer.java",
+ "io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java",
"io/flutter/plugin/common/ActivityLifecycleListener.java",
"io/flutter/plugin/common/BasicMessageChannel.java",
"io/flutter/plugin/common/BinaryCodec.java",
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
new file mode 100644
index 0000000000000..4f98a8c73eb89
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
@@ -0,0 +1,42 @@
+// Copyright 2013 The Flutter 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.engine;
+
+/**
+ * A single Flutter execution environment.
+ *
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ *
+ * 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.
+ *
+ * Multiple {@code FlutterEngine}s may exist, execute Dart code, and render UIs within a single
+ * Android app.
+ *
+ * To start running Flutter within this {@code FlutterEngine}, get a reference to this engine's
+ * {@link DartExecutor} and then use {@link DartExecutor#runFromBundle(FlutterRunArguments)}.
+ * The {@link DartExecutor#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 io.flutter.embedding.android.FlutterView} as a {@link FlutterRenderer.RenderSurface}.
+ */
+public class FlutterEngine {
+ // TODO(mattcarroll): bring in FlutterEngine implementation in future PR
+
+ /**
+ * Lifecycle callbacks for Flutter engine lifecycle events.
+ */
+ public interface EngineLifecycleListener {
+ /**
+ * Lifecycle callback invoked before a hot restart of the Flutter engine.
+ */
+ void onPreEngineRestart();
+ }
+}
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
new file mode 100644
index 0000000000000..4923f1c77c341
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
@@ -0,0 +1,526 @@
+// Copyright 2013 The Flutter 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.engine;
+
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.util.HashSet;
+import java.util.Set;
+
+import io.flutter.embedding.engine.dart.PlatformMessageHandler;
+import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
+import io.flutter.embedding.engine.renderer.FlutterRenderer;
+import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
+
+/**
+ * Interface between Flutter embedding's Java code and Flutter engine's C/C++ code.
+ *
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ *
+ * Flutter's engine is built with C/C++. The Android Flutter embedding is responsible for
+ * coordinating Android OS events and app user interactions with the C/C++ engine. Such coordination
+ * requires messaging from an Android app in Java code to the C/C++ engine code. This
+ * communication requires a JNI (Java Native Interface) API to cross the Java/native boundary.
+ *
+ * The entirety of Flutter's JNI API is codified in {@code FlutterJNI}. There are multiple reasons
+ * that all such calls are centralized in one class. First, JNI calls are inherently static and
+ * contain no Java implementation, therefore there is little reason to associate calls with different
+ * classes. Second, every JNI call must be registered in C/C++ code and this registration becomes
+ * more complicated with every additional Java class that contains JNI calls. Third, most Android
+ * developers are not familiar with native development or JNI intricacies, therefore it is in the
+ * interest of future maintenance to reduce the API surface that includes JNI declarations. Thus,
+ * all Flutter JNI calls are centralized in {@code FlutterJNI}.
+ *
+ * Despite the fact that individual JNI calls are inherently static, there is state that exists
+ * within {@code FlutterJNI}. Most calls within {@code FlutterJNI} correspond to a specific
+ * "platform view", of which there may be many. Therefore, each {@code FlutterJNI} instance holds
+ * onto a "native platform view ID" after {@link #attachToNative(boolean)}, which is shared with
+ * the native C/C++ engine code. That ID is passed to every platform-view-specific native method.
+ * ID management is handled within {@code FlutterJNI} so that developers don't have to hold onto
+ * that ID.
+ *
+ * To connect part of an Android app to Flutter's C/C++ engine, instantiate a {@code FlutterJNI} and
+ * then attach it to the native side:
+ *
+ * {@code
+ * // Instantiate FlutterJNI and attach to the native side.
+ * FlutterJNI flutterJNI = new FlutterJNI();
+ * flutterJNI.attachToNative();
+ *
+ * // Use FlutterJNI as desired.
+ * flutterJNI.dispatchPointerDataPacket(...);
+ *
+ * // Destroy the connection to the native side and cleanup.
+ * flutterJNI.detachFromNativeAndReleaseResources();
+ * }
+ *
+ * To provide a visual, interactive surface for Flutter rendering and touch events, register a
+ * {@link FlutterRenderer.RenderSurface} with {@link #setRenderSurface(FlutterRenderer.RenderSurface)}
+ *
+ * To receive callbacks for certain events that occur on the native side, register listeners:
+ *
+ *
+ * - {@link #addEngineLifecycleListener(EngineLifecycleListener)}
+ * - {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}
+ *
+ *
+ * To facilitate platform messages between Java and Dart running in Flutter, register a handler:
+ *
+ * {@link #setPlatformMessageHandler(PlatformMessageHandler)}
+ *
+ * To invoke a native method that is not associated with a platform view, invoke it statically:
+ *
+ * {@code
+ * String uri = FlutterJNI.nativeGetObservatoryUri();
+ * }
+ */
+public class FlutterJNI {
+ private static final String TAG = "FlutterJNI";
+
+ @UiThread
+ public static native boolean nativeGetIsSoftwareRenderingEnabled();
+
+ @UiThread
+ public static native String nativeGetObservatoryUri();
+
+ private Long nativePlatformViewId;
+ private FlutterRenderer.RenderSurface renderSurface;
+ private PlatformMessageHandler platformMessageHandler;
+ private final Set engineLifecycleListeners = new HashSet<>();
+ private final Set firstFrameListeners = new HashSet<>();
+
+ /**
+ * Sets the {@link FlutterRenderer.RenderSurface} delegate for the attached Flutter context.
+ *
+ * Flutter expects a user interface to exist on the platform side (Android), and that interface
+ * is expected to offer some capabilities that Flutter depends upon. The {@link FlutterRenderer.RenderSurface}
+ * interface represents those expectations. For example, Flutter expects to be able to request
+ * that its user interface "update custom accessibility actions" and therefore the delegate interface
+ * declares a corresponding method, {@link FlutterRenderer.RenderSurface#updateCustomAccessibilityActions(ByteBuffer, String[])}.
+ *
+ * If an app includes a user interface that renders a Flutter UI then a {@link FlutterRenderer.RenderSurface}
+ * should be set (this is the typical Flutter scenario). If no UI is being rendered, such as a
+ * Flutter app that is running Dart code in the background, then no registration may be necessary.
+ *
+ * If no {@link FlutterRenderer.RenderSurface} is registered then related messages coming from
+ * Flutter will be dropped (ignored).
+ */
+ @UiThread
+ public void setRenderSurface(@Nullable FlutterRenderer.RenderSurface renderSurface) {
+ this.renderSurface = renderSurface;
+ }
+
+ /**
+ * Call invoked by native to be forwarded to an {@link io.flutter.view.AccessibilityBridge}.
+ *
+ * The {@code buffer} and {@code strings} form a communication protocol that is implemented here:
+ * https://github.com/flutter/engine/blob/master/shell/platform/android/platform_view_android.cc#L207
+ */
+ @SuppressWarnings("unused")
+ @UiThread
+ private void updateSemantics(ByteBuffer buffer, String[] strings) {
+ if (renderSurface != null) {
+ renderSurface.updateSemantics(buffer, strings);
+ }
+ // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
+ }
+
+ /**
+ * Call invoked by native to be forwarded to an {@link io.flutter.view.AccessibilityBridge}.
+ *
+ * The {@code buffer} and {@code strings} form a communication protocol that is implemented here:
+ * https://github.com/flutter/engine/blob/master/shell/platform/android/platform_view_android.cc#L207
+ *
+ * // TODO(cbracken): expand these docs to include more actionable information.
+ */
+ @SuppressWarnings("unused")
+ @UiThread
+ private void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings) {
+ if (renderSurface != null) {
+ renderSurface.updateCustomAccessibilityActions(buffer, strings);
+ }
+ // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
+ }
+
+ // Called by native to notify first Flutter frame rendered.
+ @SuppressWarnings("unused")
+ @UiThread
+ private void onFirstFrame() {
+ if (renderSurface != null) {
+ renderSurface.onFirstFrameRendered();
+ }
+ // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
+
+ for (OnFirstFrameRenderedListener listener : firstFrameListeners) {
+ listener.onFirstFrameRendered();
+ }
+ }
+
+ /**
+ * Sets the handler for all platform messages that come from the attached platform view to Java.
+ *
+ * Communication between a specific Flutter context (Dart) and the host platform (Java) is
+ * accomplished by passing messages. Messages can be sent from Java to Dart with the corresponding
+ * {@code FlutterJNI} methods:
+ *
+ * - {@link #dispatchPlatformMessage(String, ByteBuffer, int, int)}
+ * - {@link #dispatchEmptyPlatformMessage(String, int)}
+ *
+ *
+ * {@code FlutterJNI} is also the recipient of all platform messages sent from its attached
+ * Flutter context (AKA platform view). {@code FlutterJNI} does not know what to do with these
+ * messages, so a handler is exposed to allow these messages to be processed in whatever manner is
+ * desired:
+ *
+ * {@code setPlatformMessageHandler(PlatformMessageHandler)}
+ *
+ * If a message is received but no {@link PlatformMessageHandler} is registered, that message will
+ * be dropped (ignored). Therefore, when using {@code FlutterJNI} to integrate a Flutter context
+ * in an app, a {@link PlatformMessageHandler} must be registered for 2-way Java/Dart communication
+ * to operate correctly. Moreover, the handler must be implemented such that fundamental platform
+ * messages are handled as expected. See {@link FlutterNativeView} for an example implementation.
+ */
+ @UiThread
+ public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
+ this.platformMessageHandler = platformMessageHandler;
+ }
+
+ // Called by native.
+ @SuppressWarnings("unused")
+ private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
+ if (platformMessageHandler != null) {
+ platformMessageHandler.handlePlatformMessage(channel, message, replyId);
+ }
+ // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
+ }
+
+ // Called by native to respond to a platform message that we sent.
+ @SuppressWarnings("unused")
+ private void handlePlatformMessageResponse(int replyId, byte[] reply) {
+ if (platformMessageHandler != null) {
+ platformMessageHandler.handlePlatformMessageResponse(replyId, reply);
+ }
+ // TODO(mattcarroll): log dropped messages when in debug mode (https://github.com/flutter/flutter/issues/25391)
+ }
+
+ @UiThread
+ public void addEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) {
+ engineLifecycleListeners.add(engineLifecycleListener);
+ }
+
+ @UiThread
+ public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) {
+ engineLifecycleListeners.remove(engineLifecycleListener);
+ }
+
+ @UiThread
+ public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
+ firstFrameListeners.add(listener);
+ }
+
+ @UiThread
+ public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
+ firstFrameListeners.remove(listener);
+ }
+
+ // TODO(mattcarroll): rename comments after refactor is done and their origin no longer matters (https://github.com/flutter/flutter/issues/25533)
+ //----- Start from FlutterView -----
+ @UiThread
+ public void onSurfaceCreated(@NonNull Surface surface) {
+ ensureAttachedToNative();
+ nativeSurfaceCreated(nativePlatformViewId, surface);
+ }
+
+ private native void nativeSurfaceCreated(long nativePlatformViewId, Surface surface);
+
+ @UiThread
+ public void onSurfaceChanged(int width, int height) {
+ ensureAttachedToNative();
+ nativeSurfaceChanged(nativePlatformViewId, width, height);
+ }
+
+ private native void nativeSurfaceChanged(long nativePlatformViewId, int width, int height);
+
+ @UiThread
+ public void onSurfaceDestroyed() {
+ ensureAttachedToNative();
+ nativeSurfaceDestroyed(nativePlatformViewId);
+ }
+
+ private native void nativeSurfaceDestroyed(long nativePlatformViewId);
+
+ @UiThread
+ 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
+ ) {
+ ensureAttachedToNative();
+ nativeSetViewportMetrics(
+ nativePlatformViewId,
+ devicePixelRatio,
+ physicalWidth,
+ physicalHeight,
+ physicalPaddingTop,
+ physicalPaddingRight,
+ physicalPaddingBottom,
+ physicalPaddingLeft,
+ physicalViewInsetTop,
+ physicalViewInsetRight,
+ physicalViewInsetBottom,
+ physicalViewInsetLeft
+ );
+ }
+
+ private native void nativeSetViewportMetrics(
+ long nativePlatformViewId,
+ float devicePixelRatio,
+ int physicalWidth,
+ int physicalHeight,
+ int physicalPaddingTop,
+ int physicalPaddingRight,
+ int physicalPaddingBottom,
+ int physicalPaddingLeft,
+ int physicalViewInsetTop,
+ int physicalViewInsetRight,
+ int physicalViewInsetBottom,
+ int physicalViewInsetLeft
+ );
+
+ @UiThread
+ public Bitmap getBitmap() {
+ ensureAttachedToNative();
+ return nativeGetBitmap(nativePlatformViewId);
+ }
+
+ private native Bitmap nativeGetBitmap(long nativePlatformViewId);
+
+ @UiThread
+ public void dispatchPointerDataPacket(ByteBuffer buffer, int position) {
+ ensureAttachedToNative();
+ nativeDispatchPointerDataPacket(nativePlatformViewId, buffer, position);
+ }
+
+ private native void nativeDispatchPointerDataPacket(long nativePlatformViewId,
+ ByteBuffer buffer,
+ int position);
+
+ @UiThread
+ public void dispatchSemanticsAction(int id, int action, ByteBuffer args, int argsPosition) {
+ ensureAttachedToNative();
+ nativeDispatchSemanticsAction(nativePlatformViewId, id, action, args, argsPosition);
+ }
+
+ private native void nativeDispatchSemanticsAction(
+ long nativePlatformViewId,
+ int id,
+ int action,
+ ByteBuffer args,
+ int argsPosition
+ );
+
+ @UiThread
+ public void setSemanticsEnabled(boolean enabled) {
+ ensureAttachedToNative();
+ nativeSetSemanticsEnabled(nativePlatformViewId, enabled);
+ }
+
+ private native void nativeSetSemanticsEnabled(long nativePlatformViewId, boolean enabled);
+
+ @UiThread
+ public void setAccessibilityFeatures(int flags) {
+ ensureAttachedToNative();
+ nativeSetAccessibilityFeatures(nativePlatformViewId, flags);
+ }
+
+ private native void nativeSetAccessibilityFeatures(long nativePlatformViewId, int flags);
+
+ @UiThread
+ public void registerTexture(long textureId, SurfaceTexture surfaceTexture) {
+ ensureAttachedToNative();
+ nativeRegisterTexture(nativePlatformViewId, textureId, surfaceTexture);
+ }
+
+ private native void nativeRegisterTexture(long nativePlatformViewId, long textureId, SurfaceTexture surfaceTexture);
+
+ @UiThread
+ public void markTextureFrameAvailable(long textureId) {
+ ensureAttachedToNative();
+ nativeMarkTextureFrameAvailable(nativePlatformViewId, textureId);
+ }
+
+ private native void nativeMarkTextureFrameAvailable(long nativePlatformViewId, long textureId);
+
+ @UiThread
+ public void unregisterTexture(long textureId) {
+ ensureAttachedToNative();
+ nativeUnregisterTexture(nativePlatformViewId, textureId);
+ }
+
+ private native void nativeUnregisterTexture(long nativePlatformViewId, long textureId);
+ //------- End from FlutterView -----
+
+ // TODO(mattcarroll): rename comments after refactor is done and their origin no longer matters (https://github.com/flutter/flutter/issues/25533)
+ //------ Start from FlutterNativeView ----
+ public boolean isAttached() {
+ return nativePlatformViewId != null;
+ }
+
+ @UiThread
+ public void attachToNative(boolean isBackgroundView) {
+ ensureNotAttachedToNative();
+ nativePlatformViewId = nativeAttach(this, isBackgroundView);
+ }
+
+ private native long nativeAttach(FlutterJNI flutterJNI, boolean isBackgroundView);
+
+ @UiThread
+ public void detachFromNativeButKeepNativeResources() {
+ ensureAttachedToNative();
+ nativeDetach(nativePlatformViewId);
+ nativePlatformViewId = null;
+ }
+
+ private native void nativeDetach(long nativePlatformViewId);
+
+ @UiThread
+ public void detachFromNativeAndReleaseResources() {
+ ensureAttachedToNative();
+ nativeDestroy(nativePlatformViewId);
+ nativePlatformViewId = null;
+ }
+
+ private native void nativeDestroy(long nativePlatformViewId);
+
+ @UiThread
+ public void runBundleAndSnapshotFromLibrary(
+ @NonNull String[] prioritizedBundlePaths,
+ @Nullable String entrypointFunctionName,
+ @Nullable String pathToEntrypointFunction,
+ @NonNull AssetManager assetManager
+ ) {
+ ensureAttachedToNative();
+ nativeRunBundleAndSnapshotFromLibrary(
+ nativePlatformViewId,
+ prioritizedBundlePaths,
+ entrypointFunctionName,
+ pathToEntrypointFunction,
+ assetManager
+ );
+ }
+
+ private native void nativeRunBundleAndSnapshotFromLibrary(
+ long nativePlatformViewId,
+ @NonNull String[] prioritizedBundlePaths,
+ @Nullable String entrypointFunctionName,
+ @Nullable String pathToEntrypointFunction,
+ @NonNull AssetManager manager
+ );
+
+ @UiThread
+ public void dispatchEmptyPlatformMessage(String channel, int responseId) {
+ ensureAttachedToNative();
+ nativeDispatchEmptyPlatformMessage(nativePlatformViewId, channel, responseId);
+ }
+
+ // Send an empty platform message to Dart.
+ private native void nativeDispatchEmptyPlatformMessage(
+ long nativePlatformViewId,
+ String channel,
+ int responseId
+ );
+
+ @UiThread
+ public void dispatchPlatformMessage(String channel, ByteBuffer message, int position, int responseId) {
+ ensureAttachedToNative();
+ nativeDispatchPlatformMessage(
+ nativePlatformViewId,
+ channel,
+ message,
+ position,
+ responseId
+ );
+ }
+
+ // Send a data-carrying platform message to Dart.
+ private native void nativeDispatchPlatformMessage(
+ long nativePlatformViewId,
+ String channel,
+ ByteBuffer message,
+ int position,
+ int responseId
+ );
+
+ @UiThread
+ public void invokePlatformMessageEmptyResponseCallback(int responseId) {
+ ensureAttachedToNative();
+ nativeInvokePlatformMessageEmptyResponseCallback(nativePlatformViewId, responseId);
+ }
+
+ // Send an empty response to a platform message received from Dart.
+ private native void nativeInvokePlatformMessageEmptyResponseCallback(
+ long nativePlatformViewId,
+ int responseId
+ );
+
+ @UiThread
+ public void invokePlatformMessageResponseCallback(int responseId, ByteBuffer message, int position) {
+ ensureAttachedToNative();
+ nativeInvokePlatformMessageResponseCallback(
+ nativePlatformViewId,
+ responseId,
+ message,
+ position
+ );
+ }
+
+ // Send a data-carrying response to a platform message received from Dart.
+ private native void nativeInvokePlatformMessageResponseCallback(
+ long nativePlatformViewId,
+ int responseId,
+ ByteBuffer message,
+ int position
+ );
+ //------ End from FlutterNativeView ----
+
+ // TODO(mattcarroll): rename comments after refactor is done and their origin no longer matters (https://github.com/flutter/flutter/issues/25533)
+ //------ Start from Engine ---
+ // Called by native.
+ @SuppressWarnings("unused")
+ private void onPreEngineRestart() {
+ for (EngineLifecycleListener listener : engineLifecycleListeners) {
+ listener.onPreEngineRestart();
+ }
+ }
+ //------ End from Engine ---
+
+ private void ensureNotAttachedToNative() {
+ if (nativePlatformViewId != null) {
+ throw new RuntimeException("Cannot execute operation because FlutterJNI is attached to native.");
+ }
+ }
+
+ private void ensureAttachedToNative() {
+ if (nativePlatformViewId == null) {
+ throw new RuntimeException("Cannot execute operation because FlutterJNI is not attached to native.");
+ }
+ }
+}
diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
new file mode 100644
index 0000000000000..0403348b871b8
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
@@ -0,0 +1,15 @@
+// Copyright 2013 The Flutter 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.engine.dart;
+
+/**
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ */
+public interface PlatformMessageHandler {
+ void handlePlatformMessage(final String channel, byte[] message, final int replyId);
+
+ void handlePlatformMessageResponse(int replyId, byte[] reply);
+}
diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
new file mode 100644
index 0000000000000..6048cd4ab59aa
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
@@ -0,0 +1,268 @@
+// Copyright 2013 The Flutter 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.engine.renderer;
+
+import android.annotation.TargetApi;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicLong;
+
+import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.view.TextureRegistry;
+
+/**
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ *
+ * {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to create an
+ * interactive Flutter UI.
+ *
+ * {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls 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 AtomicLong nextTextureId = new AtomicLong(0L);
+ private RenderSurface renderSurface;
+
+ public FlutterRenderer(@NonNull FlutterJNI flutterJNI) {
+ this.flutterJNI = flutterJNI;
+ }
+
+ public void attachToRenderSurface(@NonNull RenderSurface renderSurface) {
+ // TODO(mattcarroll): determine desired behavior when attaching to an already attached renderer
+ if (this.renderSurface != null) {
+ detachFromRenderSurface();
+ }
+
+ this.renderSurface = renderSurface;
+ this.flutterJNI.setRenderSurface(renderSurface);
+ }
+
+ public void detachFromRenderSurface() {
+ // TODO(mattcarroll): determine desired behavior if we're asked to detach without first being attached
+ if (this.renderSurface != null) {
+ surfaceDestroyed();
+ this.renderSurface = null;
+ this.flutterJNI.setRenderSurface(null);
+ }
+ }
+
+ public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
+ flutterJNI.addOnFirstFrameRenderedListener(listener);
+ }
+
+ public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
+ flutterJNI.removeOnFirstFrameRenderedListener(listener);
+ }
+
+ //------ START TextureRegistry IMPLEMENTATION -----
+ // TODO(mattcarroll): detachFromGLContext requires API 16. Create solution 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;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // The callback relies on being executed on the UI thread (unsynchronised read of mNativeView
+ // and also the engine code check for platform thread in Shell::OnPlatformViewMarkTextureFrameAvailable),
+ // so we explicitly pass a Handler for the current thread.
+ this.surfaceTexture.setOnFrameAvailableListener(onFrameListener, new Handler());
+ } else {
+ // Android documentation states that the listener can be called on an arbitrary thread.
+ // But in practice, versions of Android that predate the newer API will call the listener
+ // on the thread where the SurfaceTexture was constructed.
+ this.surfaceTexture.setOnFrameAvailableListener(onFrameListener);
+ }
+ }
+
+ private SurfaceTexture.OnFrameAvailableListener onFrameListener = new SurfaceTexture.OnFrameAvailableListener() {
+ @Override
+ public void onFrameAvailable(SurfaceTexture texture) {
+ if (released) {
+ // Even though we make sure to unregister the callback before releasing, as of Android O
+ // SurfaceTexture has a data race when accessing the callback, so the callback may
+ // still be called by a stale reference after released==true and mNativeView==null.
+ return;
+ }
+ markTextureFrameAvailable(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): describe the native behavior that this invokes
+ public void surfaceCreated(Surface surface) {
+ flutterJNI.onSurfaceCreated(surface);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void surfaceChanged(int width, int height) {
+ flutterJNI.onSurfaceChanged(width, height);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void surfaceDestroyed() {
+ flutterJNI.onSurfaceDestroyed();
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ 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.setViewportMetrics(
+ devicePixelRatio,
+ physicalWidth,
+ physicalHeight,
+ physicalPaddingTop,
+ physicalPaddingRight,
+ physicalPaddingBottom,
+ physicalPaddingLeft,
+ physicalViewInsetTop,
+ physicalViewInsetRight,
+ physicalViewInsetBottom,
+ physicalViewInsetLeft
+ );
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public Bitmap getBitmap() {
+ return flutterJNI.getBitmap();
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void dispatchPointerDataPacket(ByteBuffer buffer, int position) {
+ flutterJNI.dispatchPointerDataPacket(buffer, position);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ private void registerTexture(long textureId, SurfaceTexture surfaceTexture) {
+ flutterJNI.registerTexture(textureId, surfaceTexture);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ private void markTextureFrameAvailable(long textureId) {
+ flutterJNI.markTextureFrameAvailable(textureId);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ private void unregisterTexture(long textureId) {
+ flutterJNI.unregisterTexture(textureId);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public boolean isSoftwareRenderingEnabled() {
+ return FlutterJNI.nativeGetIsSoftwareRenderingEnabled();
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void setAccessibilityFeatures(int flags) {
+ flutterJNI.setAccessibilityFeatures(flags);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void setSemanticsEnabled(boolean enabled) {
+ flutterJNI.setSemanticsEnabled(enabled);
+ }
+
+ // TODO(mattcarroll): describe the native behavior that this invokes
+ public void dispatchSemanticsAction(int id,
+ int action,
+ ByteBuffer args,
+ int argsPosition) {
+ flutterJNI.dispatchSemanticsAction(
+ id,
+ action,
+ args,
+ argsPosition
+ );
+ }
+
+ /**
+ * 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): describe what this callback is intended to do
+ void updateCustomAccessibilityActions(ByteBuffer buffer, String[] strings);
+
+ // TODO(mattcarroll): describe what this callback is intended 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, {@code #onFirstFrameRendered()} will
+ * never be called.
+ */
+ void onFirstFrameRendered();
+ }
+}
diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java b/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java
new file mode 100644
index 0000000000000..46d271b7f171b
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java
@@ -0,0 +1,20 @@
+// Copyright 2013 The Flutter 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.engine.renderer;
+
+/**
+ * Listener invoked after Flutter paints its first frame since being initialized.
+ *
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ */
+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();
+}
diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java
index 6d4a4e8bdc9e4..c18f259f51e77 100644
--- a/shell/platform/android/io/flutter/view/FlutterNativeView.java
+++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java
@@ -8,12 +8,16 @@
import android.content.Context;
import android.util.Log;
import io.flutter.app.FlutterPluginRegistry;
+import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener;
+import io.flutter.embedding.engine.renderer.FlutterRenderer.RenderSurface;
import io.flutter.plugin.common.*;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.HashMap;
import java.util.Map;
-import android.content.res.AssetManager;
+
+import io.flutter.embedding.engine.dart.PlatformMessageHandler;
public class FlutterNativeView implements BinaryMessenger {
private static final String TAG = "FlutterNativeView";
@@ -23,8 +27,8 @@ public class FlutterNativeView implements BinaryMessenger {
private final Map mPendingReplies = new HashMap<>();
private final FlutterPluginRegistry mPluginRegistry;
- private long mNativePlatformView;
private FlutterView mFlutterView;
+ private FlutterJNI mFlutterJNI;
private final Context mContext;
private boolean applicationIsRunning;
@@ -35,6 +39,10 @@ public FlutterNativeView(Context context) {
public FlutterNativeView(Context context, boolean isBackgroundView) {
mContext = context;
mPluginRegistry = new FlutterPluginRegistry(this, context);
+ mFlutterJNI = new FlutterJNI();
+ mFlutterJNI.setRenderSurface(new RenderSurfaceImpl());
+ mFlutterJNI.setPlatformMessageHandler(new PlatformMessageHandlerImpl());
+ mFlutterJNI.addEngineLifecycleListener(new EngineLifecycleListenerImpl());
attach(this, isBackgroundView);
assertAttached();
mMessageHandlers = new HashMap<>();
@@ -43,14 +51,13 @@ public FlutterNativeView(Context context, boolean isBackgroundView) {
public void detach() {
mPluginRegistry.detach();
mFlutterView = null;
- nativeDetach(mNativePlatformView);
+ mFlutterJNI.detachFromNativeButKeepNativeResources();
}
public void destroy() {
mPluginRegistry.destroy();
mFlutterView = null;
- nativeDestroy(mNativePlatformView);
- mNativePlatformView = 0;
+ mFlutterJNI.detachFromNativeAndReleaseResources();
applicationIsRunning = false;
}
@@ -64,11 +71,7 @@ public void attachViewAndActivity(FlutterView flutterView, Activity activity) {
}
public boolean isAttached() {
- return mNativePlatformView != 0;
- }
-
- public long get() {
- return mNativePlatformView;
+ return mFlutterJNI.isAttached();
}
public void assertAttached() {
@@ -110,8 +113,12 @@ private void runFromBundleInternal(String[] bundlePaths, String entrypoint,
if (applicationIsRunning)
throw new AssertionError(
"This Flutter engine instance is already running an application");
- nativeRunBundleAndSnapshotFromLibrary(mNativePlatformView, bundlePaths,
- entrypoint, libraryPath, mContext.getResources().getAssets());
+ mFlutterJNI.runBundleAndSnapshotFromLibrary(
+ bundlePaths,
+ entrypoint,
+ libraryPath,
+ mContext.getResources().getAssets()
+ );
applicationIsRunning = true;
}
@@ -121,7 +128,7 @@ public boolean isApplicationRunning() {
}
public static String getObservatoryUri() {
- return nativeGetObservatoryUri();
+ return FlutterJNI.nativeGetObservatoryUri();
}
@Override
@@ -142,10 +149,14 @@ public void send(String channel, ByteBuffer message, BinaryReply callback) {
mPendingReplies.put(replyId, callback);
}
if (message == null) {
- nativeDispatchEmptyPlatformMessage(mNativePlatformView, channel, replyId);
+ mFlutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
} else {
- nativeDispatchPlatformMessage(
- mNativePlatformView, channel, message, message.position(), replyId);
+ mFlutterJNI.dispatchPlatformMessage(
+ channel,
+ message,
+ message.position(),
+ replyId
+ );
}
}
@@ -158,115 +169,99 @@ public void setMessageHandler(String channel, BinaryMessageHandler handler) {
}
}
+ /*package*/ FlutterJNI getFlutterJNI() {
+ return mFlutterJNI;
+ }
+
private void attach(FlutterNativeView view, boolean isBackgroundView) {
- mNativePlatformView = nativeAttach(view, isBackgroundView);
+ mFlutterJNI.attachToNative(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());
+ private final class PlatformMessageHandlerImpl implements PlatformMessageHandler {
+ // Called by native to send us a platform message.
+ public 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 ot a detached view, channel=" + channel);
+ return;
+ }
+ if (done.getAndSet(true)) {
+ throw new IllegalStateException("Reply already submitted");
+ }
+ if (reply == null) {
+ mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
+ } else {
+ mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
+ }
}
- }
- });
- } catch (Exception ex) {
- Log.e(TAG, "Uncaught exception in binary message listener", ex);
- nativeInvokePlatformMessageEmptyResponseCallback(mNativePlatformView, replyId);
+ });
+ } catch (Exception exception) {
+ Log.e(TAG, "Uncaught exception in binary message listener", exception);
+ mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
+ }
+ return;
}
- return;
+ mFlutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
}
- 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 respond to a platform message that we sent.
+ public 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);
- }
+ private final class RenderSurfaceImpl implements RenderSurface {
+ // Called by native to update the semantics/accessibility tree.
+ public 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 update the custom accessibility actions.
+ public 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 first Flutter frame rendered.
+ public void onFirstFrameRendered() {
+ if (mFlutterView == null) {
+ return;
+ }
+ mFlutterView.onFirstFrame();
+ }
}
- // Called by native to notify when the engine is restarted (hot restart).
- @SuppressWarnings("unused")
- private void onPreEngineRestart() {
- if (mFlutterView != null) {
- mFlutterView.resetAccessibilityTree();
+ private final class EngineLifecycleListenerImpl implements EngineLifecycleListener {
+ // Called by native to notify when the engine is restarted (cold reload).
+ @SuppressWarnings("unused")
+ public void onPreEngineRestart() {
+ if (mFlutterView != null) {
+ mFlutterView.resetAccessibilityTree();
+ }
+ if (mPluginRegistry == null) {
+ return;
+ }
+ mPluginRegistry.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[] bundlePaths,
- 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/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java
index 82799421a9e37..81954d2dd5803 100644
--- a/shell/platform/android/io/flutter/view/FlutterView.java
+++ b/shell/platform/android/io/flutter/view/FlutterView.java
@@ -108,38 +108,38 @@ public FlutterView(Context context, AttributeSet attrs) {
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;
}
+ mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().nativeGetIsSoftwareRenderingEnabled();
+ mAnimationScaleObserver = new AnimationScaleObserver(new Handler());
+ mMetrics = new ViewportMetrics();
+ mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
+ setFocusable(true);
+ setFocusableInTouchMode(true);
+
mNativeView.attachViewAndActivity(this, activity);
mSurfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
assertAttached();
- nativeSurfaceCreated(mNativeView.get(), holder.getSurface());
+ mNativeView.getFlutterJNI().onSurfaceCreated(holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
assertAttached();
- nativeSurfaceChanged(mNativeView.get(), width, height);
+ mNativeView.getFlutterJNI().onSurfaceChanged(width, height);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
assertAttached();
- nativeSurfaceDestroyed(mNativeView.get());
+ mNativeView.getFlutterJNI().onSurfaceDestroyed();
}
};
getHolder().addCallback(mSurfaceCallback);
@@ -558,7 +558,7 @@ public boolean onTouchEvent(MotionEvent event) {
}
assert packet.position() % (kPointerDataFieldCount * kBytePerField) == 0;
- nativeDispatchPointerDataPacket(mNativeView.get(), packet, packet.position());
+ mNativeView.getFlutterJNI().dispatchPointerDataPacket(packet, packet.position());
return true;
}
@@ -760,45 +760,13 @@ public void runFromBundle(String bundlePath, String defaultPath, String entrypoi
*/
public Bitmap getBitmap() {
assertAttached();
- return nativeGetBitmap(mNativeView.get());
+ return mNativeView.getFlutterJNI().getBitmap();
}
- private static native void nativeSurfaceCreated(long nativePlatformViewAndroid, Surface surface);
-
- 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);
-
- private static native void nativeDispatchSemanticsAction(long nativePlatformViewAndroid, int id, int action,
- ByteBuffer args, int argsPosition);
-
- private static native void nativeSetSemanticsEnabled(long nativePlatformViewAndroid, boolean enabled);
-
- 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,
+ mNativeView.getFlutterJNI().setViewportMetrics(mMetrics.devicePixelRatio, mMetrics.physicalWidth,
mMetrics.physicalHeight, mMetrics.physicalPaddingTop, mMetrics.physicalPaddingRight,
mMetrics.physicalPaddingBottom, mMetrics.physicalPaddingLeft, mMetrics.physicalViewInsetTop,
mMetrics.physicalViewInsetRight, mMetrics.physicalViewInsetBottom, mMetrics.physicalViewInsetLeft);
@@ -861,7 +829,7 @@ protected void dispatchSemanticsAction(int id, AccessibilityBridge.Action action
encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args);
position = encodedArgs.position();
}
- nativeDispatchSemanticsAction(mNativeView.get(), id, action.value, encodedArgs, position);
+ mNativeView.getFlutterJNI().dispatchSemanticsAction(id, action.value, encodedArgs, position);
}
@Override
@@ -904,7 +872,7 @@ private void updateAccessibilityFeatures() {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
}
}
- nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
+ mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
}
@Override
@@ -934,7 +902,7 @@ public void onAccessibilityStateChanged(boolean enabled) {
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.setAccessibilityEnabled(false);
}
- nativeSetSemanticsEnabled(mNativeView.get(), false);
+ mNativeView.getFlutterJNI().setSemanticsEnabled(false);
}
resetWillNotDraw();
}
@@ -973,7 +941,7 @@ public void onChange(boolean selfChange, Uri uri) {
} else {
mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value;
}
- nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
+ mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
}
}
@@ -984,14 +952,14 @@ public void onTouchExplorationStateChanged(boolean enabled) {
mTouchExplorationEnabled = true;
ensureAccessibilityEnabled();
mAccessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
- nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
+ mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
} else {
mTouchExplorationEnabled = false;
if (mAccessibilityNodeProvider != null) {
mAccessibilityNodeProvider.handleTouchExplorationExit();
}
mAccessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value;
- nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags);
+ mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags);
}
resetWillNotDraw();
}
@@ -1016,7 +984,7 @@ void ensureAccessibilityEnabled() {
if (mAccessibilityNodeProvider == null) {
mAccessibilityNodeProvider = new AccessibilityBridge(this);
}
- nativeSetSemanticsEnabled(mNativeView.get(), true);
+ mNativeView.getFlutterJNI().setSemanticsEnabled(true);
mAccessibilityNodeProvider.setAccessibilityEnabled(true);
}
@@ -1074,7 +1042,7 @@ public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() {
surfaceTexture.detachFromGLContext();
final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(),
surfaceTexture);
- nativeRegisterTexture(mNativeView.get(), entry.id(), surfaceTexture);
+ mNativeView.getFlutterJNI().registerTexture(entry.id(), surfaceTexture);
return entry;
}
@@ -1109,7 +1077,7 @@ public void onFrameAvailable(SurfaceTexture texture) {
// still be called by a stale reference after released==true and mNativeView==null.
return;
}
- nativeMarkTextureFrameAvailable(mNativeView.get(), SurfaceTextureRegistryEntry.this.id);
+ mNativeView.getFlutterJNI().markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id);
}
};
@@ -1129,7 +1097,7 @@ public void release() {
return;
}
released = true;
- nativeUnregisterTexture(mNativeView.get(), id);
+ mNativeView.getFlutterJNI().unregisterTexture(id);
// Otherwise onFrameAvailableListener might be called after mNativeView==null
// (https://github.com/flutter/flutter/issues/20951). See also the check in onFrameAvailable.
surfaceTexture.setOnFrameAvailableListener(null);
diff --git a/shell/platform/android/platform_view_android_jni.cc b/shell/platform/android/platform_view_android_jni.cc
index 69e40dd9e1d32..ccc8998cab882 100644
--- a/shell/platform/android/platform_view_android_jni.cc
+++ b/shell/platform/android/platform_view_android_jni.cc
@@ -46,9 +46,9 @@ bool CheckException(JNIEnv* env) {
static fml::jni::ScopedJavaGlobalRef* g_flutter_callback_info_class =
nullptr;
-static fml::jni::ScopedJavaGlobalRef* g_flutter_view_class = nullptr;
-static fml::jni::ScopedJavaGlobalRef* g_flutter_native_view_class =
- nullptr;
+
+static fml::jni::ScopedJavaGlobalRef* g_flutter_jni_class = nullptr;
+
static fml::jni::ScopedJavaGlobalRef* g_surface_texture_class = nullptr;
// Called By Native
@@ -146,11 +146,11 @@ void SurfaceTextureDetachFromGLContext(JNIEnv* env, jobject obj) {
// Called By Java
-static jlong Attach(JNIEnv* env,
- jclass clazz,
- jobject flutterView,
- jboolean is_background_view) {
- fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterView);
+static jlong AttachJNI(JNIEnv* env,
+ jclass clazz,
+ jobject flutterJNI,
+ jboolean is_background_view) {
+ 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()) {
@@ -160,11 +160,12 @@ static jlong Attach(JNIEnv* env,
}
}
-static void Detach(JNIEnv* env, jobject jcaller, jlong shell_holder) {
+// TODO(mattcarroll): delete this method here and in FlutterJNI.java
+static void DetachJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) {
// Nothing to do.
}
-static void Destroy(JNIEnv* env, jobject jcaller, jlong shell_holder) {
+static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) {
delete ANDROID_SHELL_HOLDER;
}
@@ -537,52 +538,23 @@ 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;
- }
-
- g_flutter_view_class = new fml::jni::ScopedJavaGlobalRef(
- env, env->FindClass("io/flutter/view/FlutterView"));
- if (g_flutter_view_class->is_null()) {
- 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()) {
- return false;
- }
-
- static const JNINativeMethod native_view_methods[] = {
+bool RegisterApi(JNIEnv* env) {
+ static const JNINativeMethod flutter_jni_methods[] = {
+ // Start of methods from FlutterNativeView
{
.name = "nativeAttach",
- .signature = "(Lio/flutter/view/FlutterNativeView;Z)J",
- .fnPtr = reinterpret_cast(&shell::Attach),
+ .signature = "(Lio/flutter/embedding/engine/FlutterJNI;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::Destroy),
+ .fnPtr = reinterpret_cast(&shell::DestroyJNI),
},
{
.name = "nativeRunBundleAndSnapshotFromLibrary",
@@ -591,11 +563,6 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
.fnPtr =
reinterpret_cast(&shell::RunBundleAndSnapshotFromLibrary),
},
- {
- .name = "nativeDetach",
- .signature = "(J)V",
- .fnPtr = reinterpret_cast(&shell::Detach),
- },
{
.name = "nativeGetObservatoryUri",
.signature = "()Ljava/lang/String;",
@@ -624,9 +591,13 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
.fnPtr = reinterpret_cast(
&shell::InvokePlatformMessageEmptyResponseCallback),
},
- };
- static const JNINativeMethod view_methods[] = {
+ // Start of methods from FlutterView
+ {
+ .name = "nativeGetBitmap",
+ .signature = "(J)Landroid/graphics/Bitmap;",
+ .fnPtr = reinterpret_cast(&shell::GetBitmap),
+ },
{
.name = "nativeSurfaceCreated",
.signature = "(JLandroid/view/Surface;)V",
@@ -647,11 +618,6 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
.signature = "(JFIIIIIIIIII)V",
.fnPtr = reinterpret_cast(&shell::SetViewportMetrics),
},
- {
- .name = "nativeGetBitmap",
- .signature = "(J)Landroid/graphics/Bitmap;",
- .fnPtr = reinterpret_cast(&shell::GetBitmap),
- },
{
.name = "nativeDispatchPointerDataPacket",
.signature = "(JLjava/nio/ByteBuffer;I)V",
@@ -694,74 +660,114 @@ 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) {
- 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) {
+ 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_native_view_class->obj(),
- "handlePlatformMessage", "(Ljava/lang/String;[BI)V");
+ env->GetMethodID(g_flutter_jni_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_native_view_class->obj(),
- "handlePlatformMessageResponse", "(I[B)V");
+ g_handle_platform_message_response_method = env->GetMethodID(
+ g_flutter_jni_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_native_view_class->obj(), "updateSemantics",
+ env->GetMethodID(g_flutter_jni_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_native_view_class->obj(), "updateCustomAccessibilityActions",
+ g_flutter_jni_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_native_view_class->obj(),
- "onFirstFrame", "()V");
+ g_on_first_frame_method =
+ env->GetMethodID(g_flutter_jni_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_native_view_class->obj(), "onPreEngineRestart", "()V");
+ g_on_engine_restart_method =
+ env->GetMethodID(g_flutter_jni_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) {
+ 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;
+ }
+
+ g_flutter_jni_class = new fml::jni::ScopedJavaGlobalRef(
+ env, env->FindClass("io/flutter/embedding/engine/FlutterJNI"));
+ if (g_flutter_jni_class->is_null()) {
+ FML_LOG(ERROR) << "Failed to find FlutterJNI Class.";
+ 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 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;
}
@@ -769,6 +775,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;
}
@@ -776,6 +783,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;
}
@@ -783,6 +791,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;
}
@@ -790,10 +799,11 @@ 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;
+ return RegisterApi(env);
}
} // namespace shell