From 6b85ed3d93cf8c1e0de5c1fa551cd2b84b93b1dc Mon Sep 17 00:00:00 2001 From: matthew-carroll Date: Fri, 21 Dec 2018 01:07:04 -0500 Subject: [PATCH] Android embedding refactor PR1: JNI Extraction to FlutterJNI.java (#7098) Android embedding refactor: JNI Extraction to FlutterJNI.java --- ci/licenses_golden/licenses_flutter | 5 + shell/platform/android/.gitignore | 3 + shell/platform/android/BUILD.gn | 5 + .../embedding/engine/FlutterEngine.java | 42 ++ .../flutter/embedding/engine/FlutterJNI.java | 526 ++++++++++++++++++ .../engine/dart/PlatformMessageHandler.java | 15 + .../engine/renderer/FlutterRenderer.java | 268 +++++++++ .../OnFirstFrameRenderedListener.java | 20 + .../io/flutter/view/FlutterNativeView.java | 215 ++++--- .../android/io/flutter/view/FlutterView.java | 78 +-- .../android/platform_view_android_jni.cc | 202 +++---- 11 files changed, 1118 insertions(+), 261 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java create mode 100644 shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java create mode 100644 shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java create mode 100644 shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java create mode 100644 shell/platform/android/io/flutter/embedding/engine/renderer/OnFirstFrameRenderedListener.java 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 493353be20551..c7dd1aaee6920 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: + * + *
    + *
  1. {@link #addEngineLifecycleListener(EngineLifecycleListener)}
  2. + *
  3. {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}
  4. + *
+ * + * 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 43a6a611fdaeb..09b751053a10f 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); @@ -559,7 +559,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; } @@ -761,45 +761,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); @@ -862,7 +830,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 @@ -905,7 +873,7 @@ private void updateAccessibilityFeatures() { mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; } } - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); + mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags); } @Override @@ -935,7 +903,7 @@ public void onAccessibilityStateChanged(boolean enabled) { if (mAccessibilityNodeProvider != null) { mAccessibilityNodeProvider.setAccessibilityEnabled(false); } - nativeSetSemanticsEnabled(mNativeView.get(), false); + mNativeView.getFlutterJNI().setSemanticsEnabled(false); } resetWillNotDraw(); } @@ -974,7 +942,7 @@ public void onChange(boolean selfChange, Uri uri) { } else { mAccessibilityFeatureFlags &= ~AccessibilityFeature.DISABLE_ANIMATIONS.value; } - nativeSetAccessibilityFeatures(mNativeView.get(), mAccessibilityFeatureFlags); + mNativeView.getFlutterJNI().setAccessibilityFeatures(mAccessibilityFeatureFlags); } } @@ -985,14 +953,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(); } @@ -1017,7 +985,7 @@ void ensureAccessibilityEnabled() { if (mAccessibilityNodeProvider == null) { mAccessibilityNodeProvider = new AccessibilityBridge(this); } - nativeSetSemanticsEnabled(mNativeView.get(), true); + mNativeView.getFlutterJNI().setSemanticsEnabled(true); mAccessibilityNodeProvider.setAccessibilityEnabled(true); } @@ -1075,7 +1043,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; } @@ -1110,7 +1078,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); } }; @@ -1130,7 +1098,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