| // 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.Context; |
| import android.content.res.AssetManager; |
| import android.graphics.Bitmap; |
| import android.graphics.ColorSpace; |
| import android.graphics.ImageDecoder; |
| import android.graphics.SurfaceTexture; |
| import android.os.Build; |
| import android.os.Looper; |
| import android.util.DisplayMetrics; |
| import android.util.Size; |
| import android.util.TypedValue; |
| import android.view.Surface; |
| import android.view.SurfaceHolder; |
| import androidx.annotation.Keep; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.UiThread; |
| import androidx.annotation.VisibleForTesting; |
| import io.flutter.Log; |
| import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; |
| import io.flutter.embedding.engine.dart.PlatformMessageHandler; |
| import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager; |
| import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; |
| import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; |
| import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; |
| import io.flutter.embedding.engine.systemchannels.SettingsChannel; |
| import io.flutter.plugin.common.StandardMessageCodec; |
| import io.flutter.plugin.localization.LocalizationPlugin; |
| import io.flutter.plugin.platform.PlatformViewsController; |
| import io.flutter.util.Preconditions; |
| import io.flutter.view.AccessibilityBridge; |
| import io.flutter.view.FlutterCallbackInformation; |
| import io.flutter.view.TextureRegistry; |
| import java.io.IOException; |
| import java.lang.ref.WeakReference; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.concurrent.CopyOnWriteArraySet; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| /** |
| * Interface between Flutter embedding's Java code and Flutter engine's C/C++ code. |
| * |
| * <p>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. |
| * |
| * <p>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}. |
| * |
| * <p>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()}, 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. |
| * |
| * <p>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: |
| * |
| * <pre>{@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(); |
| * }</pre> |
| * |
| * <p>To receive callbacks for certain events that occur on the native side, register listeners: |
| * |
| * <ol> |
| * <li>{@link #addEngineLifecycleListener(FlutterEngine.EngineLifecycleListener)} |
| * <li>{@link #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)} |
| * </ol> |
| * |
| * To facilitate platform messages between Java and Dart running in Flutter, register a handler: |
| * |
| * <p>{@link #setPlatformMessageHandler(PlatformMessageHandler)} |
| * |
| * <p>To invoke a native method that is not associated with a platform view, invoke it statically: |
| * |
| * <p>{@code bool enabled = FlutterJNI.getIsSoftwareRenderingEnabled(); } |
| */ |
| @Keep |
| public class FlutterJNI { |
| private static final String TAG = "FlutterJNI"; |
| // This serializes the invocation of platform message responses and the |
| // attachment and detachment of the shell holder. This ensures that we don't |
| // detach FlutterJNI on the platform thread while a background thread invokes |
| // a message response. Typically accessing the shell holder happens on the |
| // platform thread and doesn't require locking. |
| private ReentrantReadWriteLock shellHolderLock = new ReentrantReadWriteLock(); |
| |
| // Prefer using the FlutterJNI.Factory so it's easier to test. |
| public FlutterJNI() { |
| // We cache the main looper so that we can ensure calls are made on the main thread |
| // without consistently paying the synchronization cost of getMainLooper(). |
| mainLooper = Looper.getMainLooper(); |
| } |
| |
| /** |
| * A factory for creating {@code FlutterJNI} instances. Useful for FlutterJNI injections during |
| * tests. |
| */ |
| public static class Factory { |
| /** @return a {@link FlutterJNI} instance. */ |
| public FlutterJNI provideFlutterJNI() { |
| return new FlutterJNI(); |
| } |
| } |
| |
| // BEGIN Methods related to loading for FlutterLoader. |
| /** |
| * Loads the libflutter.so C++ library. |
| * |
| * <p>This must be called before any other native methods, and can be overridden by tests to avoid |
| * loading native libraries. |
| * |
| * <p>This method should only be called once across all FlutterJNI instances. |
| */ |
| public void loadLibrary() { |
| if (FlutterJNI.loadLibraryCalled) { |
| Log.w(TAG, "FlutterJNI.loadLibrary called more than once"); |
| } |
| |
| System.loadLibrary("flutter"); |
| FlutterJNI.loadLibraryCalled = true; |
| } |
| |
| private static boolean loadLibraryCalled = false; |
| |
| private static native void nativePrefetchDefaultFontManager(); |
| |
| /** |
| * Prefetch the default font manager provided by txt::GetDefaultFontManager() which is a |
| * process-wide singleton owned by Skia. Note that, the first call to txt::GetDefaultFontManager() |
| * will take noticeable time, but later calls will return a reference to the preexisting font |
| * manager. |
| * |
| * <p>This method should only be called once across all FlutterJNI instances. |
| */ |
| public void prefetchDefaultFontManager() { |
| if (FlutterJNI.prefetchDefaultFontManagerCalled) { |
| Log.w(TAG, "FlutterJNI.prefetchDefaultFontManager called more than once"); |
| } |
| |
| FlutterJNI.nativePrefetchDefaultFontManager(); |
| FlutterJNI.prefetchDefaultFontManagerCalled = true; |
| } |
| |
| private static boolean prefetchDefaultFontManagerCalled = false; |
| |
| private static native void nativeInit( |
| @NonNull Context context, |
| @NonNull String[] args, |
| @Nullable String bundlePath, |
| @NonNull String appStoragePath, |
| @NonNull String engineCachesPath, |
| long initTimeMillis); |
| |
| /** |
| * Perform one time initialization of the Dart VM and Flutter engine. |
| * |
| * <p>This method must be called only once. Calling more than once will cause an exception. |
| * |
| * @param context The application context. |
| * @param args Arguments to the Dart VM/Flutter engine. |
| * @param bundlePath For JIT runtimes, the path to the Dart kernel file for the application. |
| * @param appStoragePath The path to the application data directory. |
| * @param engineCachesPath The path to the application cache directory. |
| * @param initTimeMillis The time, in milliseconds, taken for initialization. |
| */ |
| public void init( |
| @NonNull Context context, |
| @NonNull String[] args, |
| @Nullable String bundlePath, |
| @NonNull String appStoragePath, |
| @NonNull String engineCachesPath, |
| long initTimeMillis) { |
| if (FlutterJNI.initCalled) { |
| Log.w(TAG, "FlutterJNI.init called more than once"); |
| } |
| |
| FlutterJNI.nativeInit( |
| context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); |
| FlutterJNI.initCalled = true; |
| } |
| |
| private static boolean initCalled = false; |
| // END methods related to FlutterLoader |
| |
| @Nullable private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate; |
| |
| /** |
| * This value is updated by the VsyncWaiter when it is initialized. |
| * |
| * <p>On API 17+, it is updated whenever the default display refresh rate changes. |
| * |
| * <p>It is defaulted to 60. |
| */ |
| private static float refreshRateFPS = 60.0f; |
| |
| private static float displayWidth = -1.0f; |
| private static float displayHeight = -1.0f; |
| private static float displayDensity = -1.0f; |
| |
| // This is set from native code via JNI. |
| @Nullable private static String vmServiceUri; |
| |
| private native boolean nativeGetIsSoftwareRenderingEnabled(); |
| |
| /** |
| * Checks launch settings for whether software rendering is requested. |
| * |
| * <p>The value is the same per program. |
| */ |
| @UiThread |
| public boolean getIsSoftwareRenderingEnabled() { |
| return nativeGetIsSoftwareRenderingEnabled(); |
| } |
| |
| /** |
| * VM Service URI for the VM instance. |
| * |
| * <p>Its value is set by the native engine once {@link #init(Context, String[], String, String, |
| * String, long)} is run. |
| */ |
| @Nullable |
| public static String getVMServiceUri() { |
| return vmServiceUri; |
| } |
| |
| /** |
| * VM Service URI for the VM instance. |
| * |
| * <p>Its value is set by the native engine once {@link #init(Context, String[], String, String, |
| * String, long)} is run. |
| * |
| * @deprecated replaced by {@link #getVMServiceUri()}. |
| */ |
| @Deprecated |
| @Nullable |
| public static String getObservatoryUri() { |
| return vmServiceUri; |
| } |
| |
| /** |
| * Notifies the engine about the refresh rate of the display when the API level is below 30. |
| * |
| * <p>For API 30 and above, this value is ignored. |
| * |
| * <p>Calling this method multiple times will update the refresh rate for the next vsync period. |
| * However, callers should avoid calling {@link android.view.Display#getRefreshRate} frequently, |
| * since it is expensive on some vendor implementations. |
| * |
| * @param refreshRateFPS The refresh rate in nanoseconds. |
| */ |
| public void setRefreshRateFPS(float refreshRateFPS) { |
| // This is ok because it only ever tracks the refresh rate of the main |
| // display. If we ever need to support the refresh rate of other displays |
| // on Android we will need to refactor this. Static lookup makes things a |
| // bit easier on the C++ side. |
| FlutterJNI.refreshRateFPS = refreshRateFPS; |
| updateRefreshRate(); |
| } |
| |
| public void updateDisplayMetrics(int displayId, float width, float height, float density) { |
| FlutterJNI.displayWidth = width; |
| FlutterJNI.displayHeight = height; |
| FlutterJNI.displayDensity = density; |
| if (!FlutterJNI.loadLibraryCalled) { |
| return; |
| } |
| nativeUpdateDisplayMetrics(nativeShellHolderId); |
| } |
| |
| private native void nativeUpdateDisplayMetrics(long nativeShellHolderId); |
| |
| public void updateRefreshRate() { |
| if (!FlutterJNI.loadLibraryCalled) { |
| return; |
| } |
| nativeUpdateRefreshRate(refreshRateFPS); |
| } |
| |
| private native void nativeUpdateRefreshRate(float refreshRateFPS); |
| |
| /** |
| * The Android vsync waiter implementation in C++ needs to know when a vsync signal arrives, which |
| * is obtained via Java API. The delegate set here is called on the C++ side when the engine is |
| * ready to wait for the next vsync signal. The delegate is expected to add a postFrameCallback to |
| * the {@link android.view.Choreographer}, and call {@link onVsync} to notify the engine. |
| * |
| * @param delegate The delegate that will call the engine back on the next vsync signal. |
| */ |
| public void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { |
| asyncWaitForVsyncDelegate = delegate; |
| } |
| |
| // TODO(mattcarroll): add javadocs |
| // Called by native. |
| private static void asyncWaitForVsync(final long cookie) { |
| if (asyncWaitForVsyncDelegate != null) { |
| asyncWaitForVsyncDelegate.asyncWaitForVsync(cookie); |
| } else { |
| throw new IllegalStateException( |
| "An AsyncWaitForVsyncDelegate must be registered with FlutterJNI before asyncWaitForVsync() is invoked."); |
| } |
| } |
| |
| private native void nativeOnVsync(long frameDelayNanos, long refreshPeriodNanos, long cookie); |
| |
| /** |
| * Notifies the engine that the Choreographer has signaled a vsync. |
| * |
| * @param frameDelayNanos The time in nanoseconds when the frame started being rendered, |
| * subtracted from the {@link System#nanoTime} timebase. |
| * @param refreshPeriodNanos The display refresh period in nanoseconds. |
| * @param cookie An opaque handle to the C++ VSyncWaiter object. |
| */ |
| public void onVsync(long frameDelayNanos, long refreshPeriodNanos, long cookie) { |
| nativeOnVsync(frameDelayNanos, refreshPeriodNanos, cookie); |
| } |
| |
| @NonNull |
| @Deprecated |
| public static native FlutterCallbackInformation nativeLookupCallbackInformation(long handle); |
| |
| // ----- Start FlutterTextUtils Methods ---- |
| private native boolean nativeFlutterTextUtilsIsEmoji(int codePoint); |
| |
| public boolean isCodePointEmoji(int codePoint) { |
| return nativeFlutterTextUtilsIsEmoji(codePoint); |
| } |
| |
| private native boolean nativeFlutterTextUtilsIsEmojiModifier(int codePoint); |
| |
| public boolean isCodePointEmojiModifier(int codePoint) { |
| return nativeFlutterTextUtilsIsEmojiModifier(codePoint); |
| } |
| |
| private native boolean nativeFlutterTextUtilsIsEmojiModifierBase(int codePoint); |
| |
| public boolean isCodePointEmojiModifierBase(int codePoint) { |
| return nativeFlutterTextUtilsIsEmojiModifierBase(codePoint); |
| } |
| |
| private native boolean nativeFlutterTextUtilsIsVariationSelector(int codePoint); |
| |
| public boolean isCodePointVariantSelector(int codePoint) { |
| return nativeFlutterTextUtilsIsVariationSelector(codePoint); |
| } |
| |
| private native boolean nativeFlutterTextUtilsIsRegionalIndicator(int codePoint); |
| |
| public boolean isCodePointRegionalIndicator(int codePoint) { |
| return nativeFlutterTextUtilsIsRegionalIndicator(codePoint); |
| } |
| |
| // ----- End Engine FlutterTextUtils Methods ---- |
| |
| // Below represents the stateful part of the FlutterJNI instances that aren't static per program. |
| // Conceptually, it represents a native shell instance. |
| |
| @Nullable private Long nativeShellHolderId; |
| @Nullable private AccessibilityDelegate accessibilityDelegate; |
| @Nullable private PlatformMessageHandler platformMessageHandler; |
| @Nullable private LocalizationPlugin localizationPlugin; |
| @Nullable private PlatformViewsController platformViewsController; |
| |
| @Nullable private DeferredComponentManager deferredComponentManager; |
| |
| @NonNull |
| private final Set<EngineLifecycleListener> engineLifecycleListeners = new CopyOnWriteArraySet<>(); |
| |
| @NonNull |
| private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = |
| new CopyOnWriteArraySet<>(); |
| |
| @NonNull private final Looper mainLooper; // cached to avoid synchronization on repeat access. |
| |
| // ------ Start Native Attach/Detach Support ---- |
| /** |
| * Returns true if this instance of {@code FlutterJNI} is connected to Flutter's native engine via |
| * a Java Native Interface (JNI). |
| */ |
| public boolean isAttached() { |
| return nativeShellHolderId != null; |
| } |
| |
| /** |
| * Attaches this {@code FlutterJNI} instance to Flutter's native engine, which allows for |
| * communication between Android code and Flutter's platform agnostic engine. |
| * |
| * <p>This method must not be invoked if {@code FlutterJNI} is already attached to native. |
| */ |
| @UiThread |
| public void attachToNative() { |
| ensureRunningOnMainThread(); |
| ensureNotAttachedToNative(); |
| shellHolderLock.writeLock().lock(); |
| try { |
| nativeShellHolderId = performNativeAttach(this); |
| } finally { |
| shellHolderLock.writeLock().unlock(); |
| } |
| } |
| |
| @VisibleForTesting |
| public long performNativeAttach(@NonNull FlutterJNI flutterJNI) { |
| return nativeAttach(flutterJNI); |
| } |
| |
| private native long nativeAttach(@NonNull FlutterJNI flutterJNI); |
| |
| /** |
| * Spawns a new FlutterJNI instance from the current instance. |
| * |
| * <p>This creates another native shell from the current shell. This causes the 2 shells to re-use |
| * some of the shared resources, reducing the total memory consumption versus creating a new |
| * FlutterJNI by calling its standard constructor. |
| * |
| * <p>This can only be called once the current FlutterJNI instance is attached by calling {@link |
| * #attachToNative()}. |
| * |
| * <p>Static methods that should be only called once such as {@link #init(Context, String[], |
| * String, String, String, long)} shouldn't be called again on the spawned FlutterJNI instance. |
| */ |
| @UiThread |
| @NonNull |
| public FlutterJNI spawn( |
| @Nullable String entrypointFunctionName, |
| @Nullable String pathToEntrypointFunction, |
| @Nullable String initialRoute, |
| @Nullable List<String> entrypointArgs) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| FlutterJNI spawnedJNI = |
| nativeSpawn( |
| nativeShellHolderId, |
| entrypointFunctionName, |
| pathToEntrypointFunction, |
| initialRoute, |
| entrypointArgs); |
| Preconditions.checkState( |
| spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0, |
| "Failed to spawn new JNI connected shell from existing shell."); |
| |
| return spawnedJNI; |
| } |
| |
| private native FlutterJNI nativeSpawn( |
| long nativeSpawningShellId, |
| @Nullable String entrypointFunctionName, |
| @Nullable String pathToEntrypointFunction, |
| @Nullable String initialRoute, |
| @Nullable List<String> entrypointArgs); |
| |
| /** |
| * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any |
| * further communication between Android code and Flutter's platform agnostic engine. |
| * |
| * <p>This method must not be invoked if {@code FlutterJNI} is not already attached to native. |
| * |
| * <p>Invoking this method will result in the release of all native-side resources that were set |
| * up during {@link #attachToNative()} or {@link #spawn(String, String, String, List)}, or |
| * accumulated thereafter. |
| * |
| * <p>It is permissible to re-attach this instance to native after detaching it from native. |
| */ |
| @UiThread |
| public void detachFromNativeAndReleaseResources() { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| shellHolderLock.writeLock().lock(); |
| try { |
| nativeDestroy(nativeShellHolderId); |
| nativeShellHolderId = null; |
| } finally { |
| shellHolderLock.writeLock().unlock(); |
| } |
| } |
| |
| private native void nativeDestroy(long nativeShellHolderId); |
| |
| private void ensureNotAttachedToNative() { |
| if (nativeShellHolderId != null) { |
| throw new RuntimeException( |
| "Cannot execute operation because FlutterJNI is attached to native."); |
| } |
| } |
| |
| private void ensureAttachedToNative() { |
| if (nativeShellHolderId == null) { |
| throw new RuntimeException( |
| "Cannot execute operation because FlutterJNI is not attached to native."); |
| } |
| } |
| // ------ End Native Attach/Detach Support ---- |
| |
| // ----- Start Render Surface Support ----- |
| /** |
| * Adds a {@link FlutterUiDisplayListener}, which receives a callback when Flutter's engine |
| * notifies {@code FlutterJNI} that Flutter is painting pixels to the {@link Surface} that was |
| * provided to Flutter. |
| */ |
| @UiThread |
| public void addIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) { |
| ensureRunningOnMainThread(); |
| flutterUiDisplayListeners.add(listener); |
| } |
| |
| /** |
| * Removes a {@link FlutterUiDisplayListener} that was added with {@link |
| * #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}. |
| */ |
| @UiThread |
| public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListener listener) { |
| ensureRunningOnMainThread(); |
| flutterUiDisplayListeners.remove(listener); |
| } |
| |
| public static native void nativeImageHeaderCallback( |
| long imageGeneratorPointer, int width, int height); |
| |
| /** |
| * Called by native as a fallback method of image decoding. There are other ways to decode images |
| * on lower API levels, they involve copying the native data _and_ do not support any additional |
| * formats, whereas ImageDecoder supports HEIF images. Unlike most other methods called from |
| * native, this method is expected to be called on a worker thread, since it only uses thread safe |
| * methods and may take multiple frames to complete. |
| */ |
| @SuppressWarnings("unused") |
| @VisibleForTesting |
| @Nullable |
| public static Bitmap decodeImage(@NonNull ByteBuffer buffer, long imageGeneratorAddress) { |
| if (Build.VERSION.SDK_INT >= 28) { |
| ImageDecoder.Source source = ImageDecoder.createSource(buffer); |
| try { |
| return ImageDecoder.decodeBitmap( |
| source, |
| (decoder, info, src) -> { |
| // i.e. ARGB_8888 |
| decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)); |
| // TODO(bdero): Switch to ALLOCATOR_HARDWARE for devices that have |
| // `AndroidBitmap_getHardwareBuffer` (API 30+) available once Skia supports |
| // `SkImage::MakeFromAHardwareBuffer` via dynamic lookups: |
| // https://skia-review.googlesource.com/c/skia/+/428960 |
| decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); |
| |
| Size size = info.getSize(); |
| nativeImageHeaderCallback(imageGeneratorAddress, size.getWidth(), size.getHeight()); |
| }); |
| } catch (IOException e) { |
| Log.e(TAG, "Failed to decode image", e); |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| // Called by native to notify first Flutter frame rendered. |
| @SuppressWarnings("unused") |
| @VisibleForTesting |
| @UiThread |
| public void onFirstFrame() { |
| ensureRunningOnMainThread(); |
| |
| for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) { |
| listener.onFlutterUiDisplayed(); |
| } |
| } |
| |
| // TODO(mattcarroll): get native to call this when rendering stops. |
| @VisibleForTesting |
| @UiThread |
| void onRenderingStopped() { |
| ensureRunningOnMainThread(); |
| |
| for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) { |
| listener.onFlutterUiNoLongerDisplayed(); |
| } |
| } |
| |
| /** |
| * Call this method when a {@link Surface} has been created onto which you would like Flutter to |
| * paint. |
| * |
| * <p>See {@link android.view.SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)} for an example |
| * of where this call might originate. |
| */ |
| @UiThread |
| public void onSurfaceCreated(@NonNull Surface surface) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSurfaceCreated(nativeShellHolderId, surface); |
| } |
| |
| private native void nativeSurfaceCreated(long nativeShellHolderId, @NonNull Surface surface); |
| |
| /** |
| * In hybrid composition, call this method when the {@link Surface} has changed. |
| * |
| * <p>In hybrid composition, the root surfaces changes from {@link |
| * android.view.SurfaceHolder#getSurface()} to {@link android.media.ImageReader#getSurface()} when |
| * a platform view is in the current frame. |
| */ |
| @UiThread |
| public void onSurfaceWindowChanged(@NonNull Surface surface) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSurfaceWindowChanged(nativeShellHolderId, surface); |
| } |
| |
| private native void nativeSurfaceWindowChanged( |
| long nativeShellHolderId, @NonNull Surface surface); |
| |
| /** |
| * Call this method when the {@link Surface} changes that was previously registered with {@link |
| * #onSurfaceCreated(Surface)}. |
| * |
| * <p>See {@link android.view.SurfaceHolder.Callback#surfaceChanged(SurfaceHolder, int, int, int)} |
| * for an example of where this call might originate. |
| */ |
| @UiThread |
| public void onSurfaceChanged(int width, int height) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSurfaceChanged(nativeShellHolderId, width, height); |
| } |
| |
| private native void nativeSurfaceChanged(long nativeShellHolderId, int width, int height); |
| |
| /** |
| * Call this method when the {@link Surface} is destroyed that was previously registered with |
| * {@link #onSurfaceCreated(Surface)}. |
| * |
| * <p>See {@link android.view.SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)} for an |
| * example of where this call might originate. |
| */ |
| @UiThread |
| public void onSurfaceDestroyed() { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| onRenderingStopped(); |
| nativeSurfaceDestroyed(nativeShellHolderId); |
| } |
| |
| private native void nativeSurfaceDestroyed(long nativeShellHolderId); |
| |
| /** |
| * Call this method to notify Flutter of the current device viewport metrics that are applies to |
| * the Flutter UI that is being rendered. |
| * |
| * <p>This method should be invoked with initial values upon attaching to native. Then, it should |
| * be invoked any time those metrics change while {@code FlutterJNI} is attached to native. |
| */ |
| @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, |
| int systemGestureInsetTop, |
| int systemGestureInsetRight, |
| int systemGestureInsetBottom, |
| int systemGestureInsetLeft, |
| int physicalTouchSlop, |
| int[] displayFeaturesBounds, |
| int[] displayFeaturesType, |
| int[] displayFeaturesState) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSetViewportMetrics( |
| nativeShellHolderId, |
| devicePixelRatio, |
| physicalWidth, |
| physicalHeight, |
| physicalPaddingTop, |
| physicalPaddingRight, |
| physicalPaddingBottom, |
| physicalPaddingLeft, |
| physicalViewInsetTop, |
| physicalViewInsetRight, |
| physicalViewInsetBottom, |
| physicalViewInsetLeft, |
| systemGestureInsetTop, |
| systemGestureInsetRight, |
| systemGestureInsetBottom, |
| systemGestureInsetLeft, |
| physicalTouchSlop, |
| displayFeaturesBounds, |
| displayFeaturesType, |
| displayFeaturesState); |
| } |
| |
| private native void nativeSetViewportMetrics( |
| long nativeShellHolderId, |
| float devicePixelRatio, |
| int physicalWidth, |
| int physicalHeight, |
| int physicalPaddingTop, |
| int physicalPaddingRight, |
| int physicalPaddingBottom, |
| int physicalPaddingLeft, |
| int physicalViewInsetTop, |
| int physicalViewInsetRight, |
| int physicalViewInsetBottom, |
| int physicalViewInsetLeft, |
| int systemGestureInsetTop, |
| int systemGestureInsetRight, |
| int systemGestureInsetBottom, |
| int systemGestureInsetLeft, |
| int physicalTouchSlop, |
| int[] displayFeaturesBounds, |
| int[] displayFeaturesType, |
| int[] displayFeaturesState); |
| |
| @UiThread |
| public void SetIsRenderingToImageView(boolean value) { |
| nativeSetIsRenderingToImageView(nativeShellHolderId, value); |
| } |
| |
| private native void nativeSetIsRenderingToImageView(long nativeShellHolderId, boolean value); |
| |
| // ----- End Render Surface Support ----- |
| |
| // ------ Start Touch Interaction Support --- |
| /** Sends a packet of pointer data to Flutter's engine. */ |
| @UiThread |
| public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeDispatchPointerDataPacket(nativeShellHolderId, buffer, position); |
| } |
| |
| private native void nativeDispatchPointerDataPacket( |
| long nativeShellHolderId, @NonNull ByteBuffer buffer, int position); |
| // ------ End Touch Interaction Support --- |
| |
| @UiThread |
| public void setPlatformViewsController(@NonNull PlatformViewsController platformViewsController) { |
| ensureRunningOnMainThread(); |
| this.platformViewsController = platformViewsController; |
| } |
| |
| // ------ Start Accessibility Support ----- |
| /** |
| * Sets the {@link AccessibilityDelegate} for the attached Flutter context. |
| * |
| * <p>The {@link AccessibilityDelegate} is responsible for maintaining an Android-side cache of |
| * Flutter's semantics tree and custom accessibility actions. This cache should be hooked up to |
| * Android's accessibility system. |
| * |
| * <p>See {@link AccessibilityBridge} for an example of an {@link AccessibilityDelegate} and the |
| * surrounding responsibilities. |
| */ |
| @UiThread |
| public void setAccessibilityDelegate(@Nullable AccessibilityDelegate accessibilityDelegate) { |
| ensureRunningOnMainThread(); |
| this.accessibilityDelegate = accessibilityDelegate; |
| } |
| |
| /** |
| * Invoked by native to send semantics tree updates from Flutter to Android. |
| * |
| * <p>The {@code buffer} and {@code strings} form a communication protocol that is implemented |
| * here: |
| * https://github.com/flutter/engine/blob/main/shell/platform/android/platform_view_android.cc#L207 |
| */ |
| @SuppressWarnings("unused") |
| @UiThread |
| private void updateSemantics( |
| @NonNull ByteBuffer buffer, |
| @NonNull String[] strings, |
| @NonNull ByteBuffer[] stringAttributeArgs) { |
| ensureRunningOnMainThread(); |
| if (accessibilityDelegate != null) { |
| accessibilityDelegate.updateSemantics(buffer, strings, stringAttributeArgs); |
| } |
| // TODO(mattcarroll): log dropped messages when in debug mode |
| // (https://github.com/flutter/flutter/issues/25391) |
| } |
| |
| /** |
| * Invoked by native to send new custom accessibility events from Flutter to Android. |
| * |
| * <p>The {@code buffer} and {@code strings} form a communication protocol that is implemented |
| * here: |
| * https://github.com/flutter/engine/blob/main/shell/platform/android/platform_view_android.cc#L207 |
| * |
| * <p>// TODO(cbracken): expand these docs to include more actionable information. |
| */ |
| @SuppressWarnings("unused") |
| @UiThread |
| private void updateCustomAccessibilityActions( |
| @NonNull ByteBuffer buffer, @NonNull String[] strings) { |
| ensureRunningOnMainThread(); |
| if (accessibilityDelegate != null) { |
| accessibilityDelegate.updateCustomAccessibilityActions(buffer, strings); |
| } |
| // TODO(mattcarroll): log dropped messages when in debug mode |
| // (https://github.com/flutter/flutter/issues/25391) |
| } |
| |
| /** Sends a semantics action to Flutter's engine, without any additional arguments. */ |
| public void dispatchSemanticsAction(int nodeId, @NonNull AccessibilityBridge.Action action) { |
| dispatchSemanticsAction(nodeId, action, null); |
| } |
| |
| /** Sends a semantics action to Flutter's engine, with additional arguments. */ |
| public void dispatchSemanticsAction( |
| int nodeId, @NonNull AccessibilityBridge.Action action, @Nullable Object args) { |
| ensureAttachedToNative(); |
| |
| ByteBuffer encodedArgs = null; |
| int position = 0; |
| if (args != null) { |
| encodedArgs = StandardMessageCodec.INSTANCE.encodeMessage(args); |
| position = encodedArgs.position(); |
| } |
| dispatchSemanticsAction(nodeId, action.value, encodedArgs, position); |
| } |
| |
| /** |
| * Sends a semantics action to Flutter's engine, given arguments that are already encoded for the |
| * engine. |
| * |
| * <p>To send a semantics action that has not already been encoded, see {@link |
| * #dispatchSemanticsAction(int, AccessibilityBridge.Action)} and {@link |
| * #dispatchSemanticsAction(int, AccessibilityBridge.Action, Object)}. |
| */ |
| @UiThread |
| public void dispatchSemanticsAction( |
| int nodeId, int action, @Nullable ByteBuffer args, int argsPosition) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeDispatchSemanticsAction(nativeShellHolderId, nodeId, action, args, argsPosition); |
| } |
| |
| private native void nativeDispatchSemanticsAction( |
| long nativeShellHolderId, |
| int nodeId, |
| int action, |
| @Nullable ByteBuffer args, |
| int argsPosition); |
| |
| /** |
| * Instructs Flutter to enable/disable its semantics tree, which is used by Flutter to support |
| * accessibility and related behaviors. |
| */ |
| @UiThread |
| public void setSemanticsEnabled(boolean enabled) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSetSemanticsEnabled(nativeShellHolderId, enabled); |
| } |
| |
| private native void nativeSetSemanticsEnabled(long nativeShellHolderId, boolean enabled); |
| |
| // TODO(mattcarroll): figure out what flags are supported and add javadoc about when/why/where to |
| // use this. |
| @UiThread |
| public void setAccessibilityFeatures(int flags) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeSetAccessibilityFeatures(nativeShellHolderId, flags); |
| } |
| |
| private native void nativeSetAccessibilityFeatures(long nativeShellHolderId, int flags); |
| // ------ End Accessibility Support ---- |
| |
| // ------ Start Texture Registration Support ----- |
| /** |
| * Gives control of a {@link SurfaceTexture} to Flutter so that Flutter can display that texture |
| * within Flutter's UI. |
| */ |
| @UiThread |
| public void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeRegisterTexture( |
| nativeShellHolderId, textureId, new WeakReference<SurfaceTextureWrapper>(textureWrapper)); |
| } |
| |
| private native void nativeRegisterTexture( |
| long nativeShellHolderId, |
| long textureId, |
| @NonNull WeakReference<SurfaceTextureWrapper> textureWrapper); |
| |
| /** |
| * Registers a ImageTexture with the given id. |
| * |
| * <p>REQUIRED: Callers should eventually unregisterTexture with the same id. |
| */ |
| @UiThread |
| public void registerImageTexture( |
| long textureId, @NonNull TextureRegistry.ImageConsumer imageTexture) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeRegisterImageTexture( |
| nativeShellHolderId, |
| textureId, |
| new WeakReference<TextureRegistry.ImageConsumer>(imageTexture)); |
| } |
| |
| private native void nativeRegisterImageTexture( |
| long nativeShellHolderId, |
| long textureId, |
| @NonNull WeakReference<TextureRegistry.ImageConsumer> imageTexture); |
| |
| /** |
| * Call this method to inform Flutter that a texture previously registered with {@link |
| * #registerTexture(long, SurfaceTextureWrapper)} has a new frame available. |
| * |
| * <p>Invoking this method instructs Flutter to update its presentation of the given texture so |
| * that the new frame is displayed. |
| */ |
| @UiThread |
| public void markTextureFrameAvailable(long textureId) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeMarkTextureFrameAvailable(nativeShellHolderId, textureId); |
| } |
| |
| private native void nativeMarkTextureFrameAvailable(long nativeShellHolderId, long textureId); |
| |
| /** Schedule the engine to draw a frame but does not invalidate the layout tree. */ |
| @UiThread |
| public void scheduleFrame() { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeScheduleFrame(nativeShellHolderId); |
| } |
| |
| private native void nativeScheduleFrame(long nativeShellHolderId); |
| |
| /** |
| * Unregisters a texture that was registered with {@link #registerTexture(long, |
| * SurfaceTextureWrapper)}. |
| */ |
| @UiThread |
| public void unregisterTexture(long textureId) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeUnregisterTexture(nativeShellHolderId, textureId); |
| } |
| |
| private native void nativeUnregisterTexture(long nativeShellHolderId, long textureId); |
| // ------ Start Texture Registration Support ----- |
| |
| // ------ Start Dart Execution Support ------- |
| /** |
| * Executes a Dart entrypoint. |
| * |
| * <p>This can only be done once per JNI attachment because a Dart isolate can only be entered |
| * once. |
| */ |
| @UiThread |
| public void runBundleAndSnapshotFromLibrary( |
| @NonNull String bundlePath, |
| @Nullable String entrypointFunctionName, |
| @Nullable String pathToEntrypointFunction, |
| @NonNull AssetManager assetManager, |
| @Nullable List<String> entrypointArgs) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeRunBundleAndSnapshotFromLibrary( |
| nativeShellHolderId, |
| bundlePath, |
| entrypointFunctionName, |
| pathToEntrypointFunction, |
| assetManager, |
| entrypointArgs); |
| } |
| |
| private native void nativeRunBundleAndSnapshotFromLibrary( |
| long nativeShellHolderId, |
| @NonNull String bundlePath, |
| @Nullable String entrypointFunctionName, |
| @Nullable String pathToEntrypointFunction, |
| @NonNull AssetManager manager, |
| @Nullable List<String> entrypointArgs); |
| // ------ End Dart Execution Support ------- |
| |
| // --------- Start Platform Message Support ------ |
| /** |
| * Sets the handler for all platform messages that come from the attached platform view to Java. |
| * |
| * <p>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: |
| * |
| * <ul> |
| * <li>{@link #dispatchPlatformMessage(String, ByteBuffer, int, int)} |
| * <li>{@link #dispatchEmptyPlatformMessage(String, int)} |
| * </ul> |
| * |
| * <p>{@code FlutterJNI} is also the recipient of all platform messages sent from its attached |
| * Flutter context. {@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: |
| * |
| * <p>{@code setPlatformMessageHandler(PlatformMessageHandler)} |
| * |
| * <p>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 |
| * io.flutter.view.FlutterNativeView} for an example implementation. |
| */ |
| @UiThread |
| public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) { |
| ensureRunningOnMainThread(); |
| this.platformMessageHandler = platformMessageHandler; |
| } |
| |
| private native void nativeCleanupMessageData(long messageData); |
| |
| /** |
| * Destroys the resources provided sent to `handlePlatformMessage`. |
| * |
| * <p>This can be called on any thread. |
| * |
| * @param messageData the argument sent to handlePlatformMessage. |
| */ |
| public void cleanupMessageData(long messageData) { |
| // This doesn't rely on being attached like other methods. |
| nativeCleanupMessageData(messageData); |
| } |
| |
| // Called by native on any thread. |
| // TODO(mattcarroll): determine if message is nonull or nullable |
| @SuppressWarnings("unused") |
| @VisibleForTesting |
| public void handlePlatformMessage( |
| @NonNull final String channel, |
| ByteBuffer message, |
| final int replyId, |
| final long messageData) { |
| if (platformMessageHandler != null) { |
| platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData); |
| } else { |
| nativeCleanupMessageData(messageData); |
| } |
| // 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. |
| // TODO(mattcarroll): determine if reply is nonull or nullable |
| @SuppressWarnings("unused") |
| private void handlePlatformMessageResponse(int replyId, ByteBuffer reply) { |
| if (platformMessageHandler != null) { |
| platformMessageHandler.handlePlatformMessageResponse(replyId, reply); |
| } |
| // TODO(mattcarroll): log dropped messages when in debug mode |
| // (https://github.com/flutter/flutter/issues/25391) |
| } |
| |
| /** |
| * Sends an empty reply (identified by {@code responseId}) from Android to Flutter over the given |
| * {@code channel}. |
| */ |
| @UiThread |
| public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId) { |
| ensureRunningOnMainThread(); |
| if (isAttached()) { |
| nativeDispatchEmptyPlatformMessage(nativeShellHolderId, channel, responseId); |
| } else { |
| Log.w( |
| TAG, |
| "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " |
| + channel |
| + ". Response ID: " |
| + responseId); |
| } |
| } |
| |
| // Send an empty platform message to Dart. |
| private native void nativeDispatchEmptyPlatformMessage( |
| long nativeShellHolderId, @NonNull String channel, int responseId); |
| |
| /** Sends a reply {@code message} from Android to Flutter over the given {@code channel}. */ |
| @UiThread |
| public void dispatchPlatformMessage( |
| @NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) { |
| ensureRunningOnMainThread(); |
| if (isAttached()) { |
| nativeDispatchPlatformMessage(nativeShellHolderId, channel, message, position, responseId); |
| } else { |
| Log.w( |
| TAG, |
| "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " |
| + channel |
| + ". Response ID: " |
| + responseId); |
| } |
| } |
| |
| // Send a data-carrying platform message to Dart. |
| private native void nativeDispatchPlatformMessage( |
| long nativeShellHolderId, |
| @NonNull String channel, |
| @Nullable ByteBuffer message, |
| int position, |
| int responseId); |
| |
| // TODO(mattcarroll): differentiate between channel responses and platform responses. |
| public void invokePlatformMessageEmptyResponseCallback(int responseId) { |
| // Called on any thread. |
| shellHolderLock.readLock().lock(); |
| try { |
| if (isAttached()) { |
| nativeInvokePlatformMessageEmptyResponseCallback(nativeShellHolderId, responseId); |
| } else { |
| Log.w( |
| TAG, |
| "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: " |
| + responseId); |
| } |
| } finally { |
| shellHolderLock.readLock().unlock(); |
| } |
| } |
| |
| // Send an empty response to a platform message received from Dart. |
| private native void nativeInvokePlatformMessageEmptyResponseCallback( |
| long nativeShellHolderId, int responseId); |
| |
| // TODO(mattcarroll): differentiate between channel responses and platform responses. |
| public void invokePlatformMessageResponseCallback( |
| int responseId, @NonNull ByteBuffer message, int position) { |
| // Called on any thread. |
| if (!message.isDirect()) { |
| throw new IllegalArgumentException("Expected a direct ByteBuffer."); |
| } |
| shellHolderLock.readLock().lock(); |
| try { |
| if (isAttached()) { |
| nativeInvokePlatformMessageResponseCallback( |
| nativeShellHolderId, responseId, message, position); |
| } else { |
| Log.w( |
| TAG, |
| "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: " |
| + responseId); |
| } |
| } finally { |
| shellHolderLock.readLock().unlock(); |
| } |
| } |
| |
| // Send a data-carrying response to a platform message received from Dart. |
| private native void nativeInvokePlatformMessageResponseCallback( |
| long nativeShellHolderId, int responseId, @Nullable ByteBuffer message, int position); |
| // ------- End Platform Message Support ---- |
| |
| // ----- Start Engine Lifecycle Support ---- |
| /** |
| * Adds the given {@code engineLifecycleListener} to be notified of Flutter engine lifecycle |
| * events, e.g., {@link EngineLifecycleListener#onPreEngineRestart()}. |
| */ |
| @UiThread |
| public void addEngineLifecycleListener(@NonNull EngineLifecycleListener engineLifecycleListener) { |
| ensureRunningOnMainThread(); |
| engineLifecycleListeners.add(engineLifecycleListener); |
| } |
| |
| /** |
| * Removes the given {@code engineLifecycleListener}, which was previously added using {@link |
| * #addIsDisplayingFlutterUiListener(FlutterUiDisplayListener)}. |
| */ |
| @UiThread |
| public void removeEngineLifecycleListener( |
| @NonNull EngineLifecycleListener engineLifecycleListener) { |
| ensureRunningOnMainThread(); |
| engineLifecycleListeners.remove(engineLifecycleListener); |
| } |
| |
| // Called by native. |
| @SuppressWarnings("unused") |
| private void onPreEngineRestart() { |
| for (EngineLifecycleListener listener : engineLifecycleListeners) { |
| listener.onPreEngineRestart(); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| @UiThread |
| public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to position an overlay surface"); |
| } |
| platformViewsController.onDisplayOverlaySurface(id, x, y, width, height); |
| } |
| |
| @SuppressWarnings("unused") |
| @UiThread |
| public void onBeginFrame() { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to begin the frame"); |
| } |
| platformViewsController.onBeginFrame(); |
| } |
| |
| @SuppressWarnings("unused") |
| @UiThread |
| public void onEndFrame() { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to end the frame"); |
| } |
| platformViewsController.onEndFrame(); |
| } |
| |
| @SuppressWarnings("unused") |
| @UiThread |
| public FlutterOverlaySurface createOverlaySurface() { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to position an overlay surface"); |
| } |
| return platformViewsController.createOverlaySurface(); |
| } |
| |
| @SuppressWarnings("unused") |
| @UiThread |
| public void destroyOverlaySurfaces() { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to destroy an overlay surface"); |
| } |
| platformViewsController.destroyOverlaySurfaces(); |
| } |
| // ----- End Engine Lifecycle Support ---- |
| |
| // ----- Start Localization Support ---- |
| |
| /** Sets the localization plugin that is used in various localization methods. */ |
| @UiThread |
| public void setLocalizationPlugin(@Nullable LocalizationPlugin localizationPlugin) { |
| ensureRunningOnMainThread(); |
| this.localizationPlugin = localizationPlugin; |
| } |
| |
| /** Invoked by native to obtain the results of Android's locale resolution algorithm. */ |
| @SuppressWarnings("unused") |
| @VisibleForTesting |
| public String[] computePlatformResolvedLocale(@NonNull String[] strings) { |
| if (localizationPlugin == null) { |
| return new String[0]; |
| } |
| List<Locale> supportedLocales = new ArrayList<Locale>(); |
| final int localeDataLength = 3; |
| for (int i = 0; i < strings.length; i += localeDataLength) { |
| String languageCode = strings[i + 0]; |
| String countryCode = strings[i + 1]; |
| String scriptCode = strings[i + 2]; |
| // Convert to Locales via LocaleBuilder if available (API 21+) to include scriptCode. |
| if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { |
| Locale.Builder localeBuilder = new Locale.Builder(); |
| if (!languageCode.isEmpty()) { |
| localeBuilder.setLanguage(languageCode); |
| } |
| if (!countryCode.isEmpty()) { |
| localeBuilder.setRegion(countryCode); |
| } |
| if (!scriptCode.isEmpty()) { |
| localeBuilder.setScript(scriptCode); |
| } |
| supportedLocales.add(localeBuilder.build()); |
| } else { |
| // Pre-API 21, we fall back on scriptCode-less locales. |
| supportedLocales.add(new Locale(languageCode, countryCode)); |
| } |
| } |
| |
| Locale result = localizationPlugin.resolveNativeLocale(supportedLocales); |
| |
| if (result == null) { |
| return new String[0]; |
| } |
| String[] output = new String[localeDataLength]; |
| output[0] = result.getLanguage(); |
| output[1] = result.getCountry(); |
| if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { |
| output[2] = result.getScript(); |
| } else { |
| output[2] = ""; |
| } |
| return output; |
| } |
| |
| // ----- End Localization Support ---- |
| @Nullable |
| public float getScaledFontSize(float fontSize, int configurationId) { |
| final DisplayMetrics metrics = SettingsChannel.getPastDisplayMetrics(configurationId); |
| if (metrics == null) { |
| Log.e( |
| TAG, |
| "getScaledFontSize called with configurationId " |
| + String.valueOf(configurationId) |
| + ", which can't be found."); |
| return -1f; |
| } |
| return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, fontSize, metrics) |
| / metrics.density; |
| } |
| |
| // ----- Start Deferred Components Support ---- |
| |
| /** Sets the deferred component manager that is used to download and install split features. */ |
| @UiThread |
| public void setDeferredComponentManager( |
| @Nullable DeferredComponentManager deferredComponentManager) { |
| ensureRunningOnMainThread(); |
| this.deferredComponentManager = deferredComponentManager; |
| if (deferredComponentManager != null) { |
| deferredComponentManager.setJNI(this); |
| } |
| } |
| |
| /** |
| * Called by dart to request that a Dart deferred library corresponding to loadingUnitId be |
| * downloaded (if necessary) and loaded into the dart vm. |
| * |
| * <p>This method delegates the task to DeferredComponentManager, which handles the download and |
| * loading of the dart library and any assets. |
| * |
| * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is |
| * automatically retrieved when loadLibrary() is called on a dart deferred library. |
| */ |
| @SuppressWarnings("unused") |
| @UiThread |
| public void requestDartDeferredLibrary(int loadingUnitId) { |
| if (deferredComponentManager != null) { |
| deferredComponentManager.installDeferredComponent(loadingUnitId, null); |
| } else { |
| // TODO(garyq): Add link to setup/instructions guide wiki. |
| Log.e( |
| TAG, |
| "No DeferredComponentManager found. Android setup must be completed before using split AOT deferred components."); |
| } |
| } |
| |
| /** |
| * Searches each of the provided paths for a valid Dart shared library .so file and resolves |
| * symbols to load into the dart VM. |
| * |
| * <p>Successful loading of the dart library completes the future returned by loadLibrary() that |
| * triggered the install/load process. |
| * |
| * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is |
| * automatically retrieved when loadLibrary() is called on a dart deferred library. This is |
| * used to identify which Dart deferred library the resolved correspond to. |
| * @param searchPaths An array of paths in which to look for valid dart shared libraries. This |
| * supports paths within zipped apks as long as the apks are not compressed using the |
| * `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be tried first to last and ends |
| * when a library is successfully found. When the found library is invalid, no additional |
| * paths will be attempted. |
| */ |
| @UiThread |
| public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeLoadDartDeferredLibrary(nativeShellHolderId, loadingUnitId, searchPaths); |
| } |
| |
| private native void nativeLoadDartDeferredLibrary( |
| long nativeShellHolderId, int loadingUnitId, @NonNull String[] searchPaths); |
| |
| /** |
| * Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager. |
| * |
| * <p>This may be used to update the engine AssetManager when a new deferred component is |
| * installed and a new Android AssetManager is created with access to new assets. |
| * |
| * @param assetManager An android AssetManager that is able to access the newly downloaded assets. |
| * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical |
| * value is `flutter_assets`. |
| */ |
| @UiThread |
| public void updateJavaAssetManager( |
| @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeUpdateJavaAssetManager(nativeShellHolderId, assetManager, assetBundlePath); |
| } |
| |
| private native void nativeUpdateJavaAssetManager( |
| long nativeShellHolderId, |
| @NonNull AssetManager assetManager, |
| @NonNull String assetBundlePath); |
| |
| /** |
| * Indicates that a failure was encountered during the Android portion of downloading a dynamic |
| * feature module and loading a dart deferred library, which is typically done by |
| * DeferredComponentManager. |
| * |
| * <p>This will inform dart that the future returned by loadLibrary() should complete with an |
| * error. |
| * |
| * @param loadingUnitId The loadingUnitId that corresponds to the dart deferred library that |
| * failed to install. |
| * @param error The error message to display. |
| * @param isTransient When isTransient is false, new attempts to install will automatically result |
| * in same error in Dart before the request is passed to Android. |
| */ |
| @SuppressWarnings("unused") |
| @UiThread |
| public void deferredComponentInstallFailure( |
| int loadingUnitId, @NonNull String error, boolean isTransient) { |
| ensureRunningOnMainThread(); |
| nativeDeferredComponentInstallFailure(loadingUnitId, error, isTransient); |
| } |
| |
| private native void nativeDeferredComponentInstallFailure( |
| int loadingUnitId, @NonNull String error, boolean isTransient); |
| |
| // ----- End Deferred Components Support ---- |
| |
| // @SuppressWarnings("unused") |
| @UiThread |
| public void onDisplayPlatformView( |
| int viewId, |
| int x, |
| int y, |
| int width, |
| int height, |
| int viewWidth, |
| int viewHeight, |
| FlutterMutatorsStack mutatorsStack) { |
| ensureRunningOnMainThread(); |
| if (platformViewsController == null) { |
| throw new RuntimeException( |
| "platformViewsController must be set before attempting to position a platform view"); |
| } |
| platformViewsController.onDisplayPlatformView( |
| viewId, x, y, width, height, viewWidth, viewHeight, mutatorsStack); |
| } |
| |
| // TODO(mattcarroll): determine if this is nonull or nullable |
| @UiThread |
| public Bitmap getBitmap() { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| return nativeGetBitmap(nativeShellHolderId); |
| } |
| |
| // TODO(mattcarroll): determine if this is nonull or nullable |
| private native Bitmap nativeGetBitmap(long nativeShellHolderId); |
| |
| /** |
| * Notifies the Dart VM of a low memory event, or that the application is in a state such that now |
| * is an appropriate time to free resources, such as going to the background. |
| * |
| * <p>This is distinct from sending a SystemChannel message about low memory, which only notifies |
| * the running Flutter application. |
| */ |
| @UiThread |
| public void notifyLowMemoryWarning() { |
| ensureRunningOnMainThread(); |
| ensureAttachedToNative(); |
| nativeNotifyLowMemoryWarning(nativeShellHolderId); |
| } |
| |
| private native void nativeNotifyLowMemoryWarning(long nativeShellHolderId); |
| |
| private void ensureRunningOnMainThread() { |
| if (Looper.myLooper() != mainLooper) { |
| throw new RuntimeException( |
| "Methods marked with @UiThread must be executed on the main thread. Current thread: " |
| + Thread.currentThread().getName()); |
| } |
| } |
| |
| /** |
| * Delegate responsible for creating and updating Android-side caches of Flutter's semantics tree |
| * and custom accessibility actions. |
| * |
| * <p>{@link AccessibilityBridge} is an example of an {@code AccessibilityDelegate}. |
| */ |
| public interface AccessibilityDelegate { |
| /** |
| * Sends new custom accessibility actions from Flutter to Android. |
| * |
| * <p>Implementers are expected to maintain an Android-side cache of custom accessibility |
| * actions. This method provides new actions to add to that cache. |
| */ |
| void updateCustomAccessibilityActions(@NonNull ByteBuffer buffer, @NonNull String[] strings); |
| |
| /** |
| * Sends new {@code SemanticsNode} information from Flutter to Android. |
| * |
| * <p>Implementers are expected to maintain an Android-side cache of Flutter's semantics tree. |
| * This method provides updates from Flutter for the Android-side semantics tree cache. |
| */ |
| void updateSemantics( |
| @NonNull ByteBuffer buffer, |
| @NonNull String[] strings, |
| @NonNull ByteBuffer[] stringAttributeArgs); |
| } |
| |
| public interface AsyncWaitForVsyncDelegate { |
| void asyncWaitForVsync(final long cookie); |
| } |
| } |