// 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.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.PluginRegistry;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverControlSurface;
import io.flutter.embedding.engine.plugins.contentprovider.ContentProviderControlSurface;
import io.flutter.embedding.engine.plugins.service.ServiceControlSurface;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
import io.flutter.embedding.engine.systemchannels.RestorationChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.util.ViewUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A single Flutter execution environment.
 *
 * <p>The {@code FlutterEngine} is the container through which Dart code can be run in an Android
 * application.
 *
 * <p>Dart code in a {@code FlutterEngine} can execute in the background, or it can be render to the
 * screen by using the accompanying {@link FlutterRenderer} and Dart code using the Flutter
 * framework on the Dart side. 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.
 *
 * <p>Multiple {@code FlutterEngine}s may exist, execute Dart code, and render UIs within a single
 * Android app. For better memory performance characteristics, construct multiple {@code
 * FlutterEngine}s via {@link io.flutter.embedding.engine.FlutterEngineGroup} rather than via {@code
 * FlutterEngine}'s constructor directly.
 *
 * <p>To start running Dart and/or Flutter within this {@code FlutterEngine}, get a reference to
 * this engine's {@link DartExecutor} and then use {@link
 * DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}. The {@link
 * DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)} method must not be invoked twice
 * on the same {@code FlutterEngine}.
 *
 * <p>To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a
 * {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using a {@link
 * io.flutter.embedding.android.FlutterView} as a {@link RenderSurface}.
 *
 * <p>Instatiating the first {@code FlutterEngine} per process will also load the Flutter engine's
 * native library and start the Dart VM. Subsequent {@code FlutterEngine}s will run on the same VM
 * instance but will have their own Dart <a
 * href="https://api.dartlang.org/stable/dart-isolate/Isolate-class.html">Isolate</a> when the
 * {@link DartExecutor} is run. Each Isolate is a self-contained Dart environment and cannot
 * communicate with each other except via Isolate ports.
 */
public class FlutterEngine implements ViewUtils.DisplayUpdater {
  private static final String TAG = "FlutterEngine";

  @NonNull private final FlutterJNI flutterJNI;
  @NonNull private final FlutterRenderer renderer;
  @NonNull private final DartExecutor dartExecutor;
  @NonNull private final FlutterEngineConnectionRegistry pluginRegistry;
  @NonNull private final LocalizationPlugin localizationPlugin;

  // System channels.
  @NonNull private final AccessibilityChannel accessibilityChannel;
  @NonNull private final DeferredComponentChannel deferredComponentChannel;
  @NonNull private final LifecycleChannel lifecycleChannel;
  @NonNull private final LocalizationChannel localizationChannel;
  @NonNull private final MouseCursorChannel mouseCursorChannel;
  @NonNull private final NavigationChannel navigationChannel;
  @NonNull private final RestorationChannel restorationChannel;
  @NonNull private final PlatformChannel platformChannel;
  @NonNull private final SettingsChannel settingsChannel;
  @NonNull private final SpellCheckChannel spellCheckChannel;
  @NonNull private final SystemChannel systemChannel;
  @NonNull private final TextInputChannel textInputChannel;

  // Platform Views.
  @NonNull private final PlatformViewsController platformViewsController;

  // Engine Lifecycle.
  @NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();

  @NonNull
  private final EngineLifecycleListener engineLifecycleListener =
      new EngineLifecycleListener() {
        @SuppressWarnings("unused")
        public void onPreEngineRestart() {
          Log.v(TAG, "onPreEngineRestart()");
          for (EngineLifecycleListener lifecycleListener : engineLifecycleListeners) {
            lifecycleListener.onPreEngineRestart();
          }

          platformViewsController.onPreEngineRestart();
          restorationChannel.clearData();
        }

        @Override
        public void onEngineWillDestroy() {
          // This inner implementation doesn't do anything since FlutterEngine sent this
          // notification in the first place. It's meant for external listeners.
        }
      };

  /**
   * Constructs a new {@code FlutterEngine}.
   *
   * <p>A new {@code FlutterEngine} does not execute any Dart code automatically. See {@link
   * #getDartExecutor()} and {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}
   * to begin executing Dart code within this {@code FlutterEngine}.
   *
   * <p>A new {@code FlutterEngine} will not display any UI until a {@link RenderSurface} is
   * registered. See {@link #getRenderer()} and {@link
   * FlutterRenderer#startRenderingToSurface(Surface, boolean)}.
   *
   * <p>A new {@code FlutterEngine} automatically attaches all plugins. See {@link #getPlugins()}.
   *
   * <p>A new {@code FlutterEngine} does come with all default system channels attached.
   *
   * <p>The first {@code FlutterEngine} instance constructed per process will also load the Flutter
   * native library and start a Dart VM.
   *
   * <p>In order to pass Dart VM initialization arguments (see {@link
   * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
   * initialization arguments by calling {@link
   * io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link
   * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context,
   * String[])} before constructing the engine.
   */
  public FlutterEngine(@NonNull Context context) {
    this(context, null);
  }

  /**
   * Same as {@link #FlutterEngine(Context)} with added support for passing Dart VM arguments.
   *
   * <p>If the Dart VM has already started, the given arguments will have no effect.
   */
  public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) {
    this(context, /* flutterLoader */ null, /* flutterJNI */ null, dartVmArgs, true);
  }

  /**
   * Same as {@link #FlutterEngine(Context)} with added support for passing Dart VM arguments and
   * avoiding automatic plugin registration.
   *
   * <p>If the Dart VM has already started, the given arguments will have no effect.
   */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins) {
    this(
        context,
        /* flutterLoader */ null,
        /* flutterJNI */ null,
        dartVmArgs,
        automaticallyRegisterPlugins);
  }

  /**
   * Same as {@link #FlutterEngine(Context, String[], boolean)} with added support for configuring
   * whether the engine will receive restoration data.
   *
   * <p>The {@code waitForRestorationData} flag controls whether the engine delays responding to
   * requests from the framework for restoration data until that data has been provided to the
   * engine via {@code RestorationChannel.setRestorationData(byte[] data)}. If the flag is false,
   * the framework may temporarily initialize itself to default values before the restoration data
   * has been made available to the engine. Setting {@code waitForRestorationData} to true avoids
   * this extra work by delaying initialization until the data is available.
   *
   * <p>When {@code waitForRestorationData} is set, {@code
   * RestorationChannel.setRestorationData(byte[] data)} must be called at a later point in time. If
   * it later turns out that no restoration data is available to restore the framework from, that
   * method must still be called with null as an argument to indicate "no data".
   *
   * <p>If the framework never requests the restoration data, this flag has no effect.
   */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins,
      boolean waitForRestorationData) {
    this(
        context,
        /* flutterLoader */ null,
        /* flutterJNI */ null,
        new PlatformViewsController(),
        dartVmArgs,
        automaticallyRegisterPlugins,
        waitForRestorationData);
  }

  /**
   * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)} but with
   * no Dart VM flags and automatically registers plugins.
   *
   * <p>{@code flutterJNI} should be a new instance that has never been attached to an engine
   * before.
   */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI) {
    this(context, flutterLoader, flutterJNI, null, true);
  }

  /**
   * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI)}, plus Dart VM flags in
   * {@code dartVmArgs}, and control over whether plugins are automatically registered with this
   * {@code FlutterEngine} in {@code automaticallyRegisterPlugins}. If plugins are automatically
   * registered, then they are registered during the execution of this constructor.
   */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins) {
    this(
        context,
        flutterLoader,
        flutterJNI,
        new PlatformViewsController(),
        dartVmArgs,
        automaticallyRegisterPlugins);
  }

  /**
   * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean)}, plus the
   * ability to provide a custom {@code PlatformViewsController}.
   */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @NonNull PlatformViewsController platformViewsController,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins) {
    this(
        context,
        flutterLoader,
        flutterJNI,
        platformViewsController,
        dartVmArgs,
        automaticallyRegisterPlugins,
        false);
  }

  /** Fully configurable {@code FlutterEngine} constructor. */
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @NonNull PlatformViewsController platformViewsController,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins,
      boolean waitForRestorationData) {
    this(
        context,
        flutterLoader,
        flutterJNI,
        platformViewsController,
        dartVmArgs,
        automaticallyRegisterPlugins,
        waitForRestorationData,
        null);
  }

  @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
  public FlutterEngine(
      @NonNull Context context,
      @Nullable FlutterLoader flutterLoader,
      @NonNull FlutterJNI flutterJNI,
      @NonNull PlatformViewsController platformViewsController,
      @Nullable String[] dartVmArgs,
      boolean automaticallyRegisterPlugins,
      boolean waitForRestorationData,
      @Nullable FlutterEngineGroup group) {
    AssetManager assetManager;
    try {
      assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
    } catch (NameNotFoundException e) {
      assetManager = context.getAssets();
    }

    FlutterInjector injector = FlutterInjector.instance();

    if (flutterJNI == null) {
      flutterJNI = injector.getFlutterJNIFactory().provideFlutterJNI();
    }
    this.flutterJNI = flutterJNI;

    this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
    this.dartExecutor.onAttachedToJNI();

    DeferredComponentManager deferredComponentManager =
        FlutterInjector.instance().deferredComponentManager();

    accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
    deferredComponentChannel = new DeferredComponentChannel(dartExecutor);
    lifecycleChannel = new LifecycleChannel(dartExecutor);
    localizationChannel = new LocalizationChannel(dartExecutor);
    mouseCursorChannel = new MouseCursorChannel(dartExecutor);
    navigationChannel = new NavigationChannel(dartExecutor);
    platformChannel = new PlatformChannel(dartExecutor);
    restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
    settingsChannel = new SettingsChannel(dartExecutor);
    spellCheckChannel = new SpellCheckChannel(dartExecutor);
    systemChannel = new SystemChannel(dartExecutor);
    textInputChannel = new TextInputChannel(dartExecutor);

    if (deferredComponentManager != null) {
      deferredComponentManager.setDeferredComponentChannel(deferredComponentChannel);
    }

    this.localizationPlugin = new LocalizationPlugin(context, localizationChannel);

    if (flutterLoader == null) {
      flutterLoader = injector.flutterLoader();
    }

    if (!flutterJNI.isAttached()) {
      flutterLoader.startInitialization(context.getApplicationContext());
      flutterLoader.ensureInitializationComplete(context, dartVmArgs);
    }

    flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
    flutterJNI.setPlatformViewsController(platformViewsController);
    flutterJNI.setLocalizationPlugin(localizationPlugin);
    flutterJNI.setDeferredComponentManager(injector.deferredComponentManager());

    // It should typically be a fresh, unattached JNI. But on a spawned engine, the JNI instance
    // is already attached to a native shell. In that case, the Java FlutterEngine is created around
    // an existing shell.
    if (!flutterJNI.isAttached()) {
      attachToJni();
    }

    // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
    // possible.
    this.renderer = new FlutterRenderer(flutterJNI);

    this.platformViewsController = platformViewsController;
    this.platformViewsController.onAttachedToJNI();

    this.pluginRegistry =
        new FlutterEngineConnectionRegistry(
            context.getApplicationContext(), this, flutterLoader, group);

    localizationPlugin.sendLocalesToFlutter(context.getResources().getConfiguration());

    // Only automatically register plugins if both constructor parameter and
    // loaded AndroidManifest config turn this feature on.
    if (automaticallyRegisterPlugins && flutterLoader.automaticallyRegisterPlugins()) {
      GeneratedPluginRegister.registerGeneratedPlugins(this);
    }

    ViewUtils.calculateMaximumDisplayMetrics(context, this);
  }

  private void attachToJni() {
    Log.v(TAG, "Attaching to JNI.");
    flutterJNI.attachToNative();

    if (!isAttachedToJni()) {
      throw new RuntimeException("FlutterEngine failed to attach to its native Object reference.");
    }
  }

  @SuppressWarnings("BooleanMethodIsAlwaysInverted")
  private boolean isAttachedToJni() {
    return flutterJNI.isAttached();
  }

  /**
   * Create a second {@link io.flutter.embedding.engine.FlutterEngine} based on this current one by
   * sharing as much resources together as possible to minimize startup latency and memory cost.
   *
   * @param context is a Context used to create the {@link
   *     io.flutter.embedding.engine.FlutterEngine}. Could be the same Context as the current engine
   *     or a different one. Generally, only an application Context is needed for the {@link
   *     io.flutter.embedding.engine.FlutterEngine} and its dependencies.
   * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It
   *     doesn't need to be the same entrypoint as the current engine but must be built in the same
   *     AOT or snapshot.
   * @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
   *     null, it will default to the "/" route.
   * @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
   * @return a new {@link io.flutter.embedding.engine.FlutterEngine}.
   */
  @NonNull
  /*package*/ FlutterEngine spawn(
      @NonNull Context context,
      @NonNull DartEntrypoint dartEntrypoint,
      @Nullable String initialRoute,
      @Nullable List<String> dartEntrypointArgs,
      @Nullable PlatformViewsController platformViewsController,
      boolean automaticallyRegisterPlugins,
      boolean waitForRestorationData) {
    if (!isAttachedToJni()) {
      throw new IllegalStateException(
          "Spawn can only be called on a fully constructed FlutterEngine");
    }

    FlutterJNI newFlutterJNI =
        flutterJNI.spawn(
            dartEntrypoint.dartEntrypointFunctionName,
            dartEntrypoint.dartEntrypointLibrary,
            initialRoute,
            dartEntrypointArgs);
    return new FlutterEngine(
        context, // Context.
        null, // FlutterLoader. A null value passed here causes the constructor to get it from the
        // FlutterInjector.
        newFlutterJNI, // FlutterJNI.
        platformViewsController, // PlatformViewsController.
        null, // String[]. The Dart VM has already started, this arguments will have no effect.
        automaticallyRegisterPlugins, // boolean.
        waitForRestorationData); // boolean
  }

  /**
   * Cleans up all components within this {@code FlutterEngine} and destroys the associated Dart
   * Isolate. All state held by the Dart Isolate, such as the Flutter Elements tree, is lost.
   *
   * <p>This {@code FlutterEngine} instance should be discarded after invoking this method.
   */
  public void destroy() {
    Log.v(TAG, "Destroying.");
    for (EngineLifecycleListener listener : engineLifecycleListeners) {
      listener.onEngineWillDestroy();
    }
    // The order that these things are destroyed is important.
    pluginRegistry.destroy();
    platformViewsController.onDetachedFromJNI();
    dartExecutor.onDetachedFromJNI();
    flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
    flutterJNI.setDeferredComponentManager(null);
    flutterJNI.detachFromNativeAndReleaseResources();
    if (FlutterInjector.instance().deferredComponentManager() != null) {
      FlutterInjector.instance().deferredComponentManager().destroy();
      deferredComponentChannel.setDeferredComponentManager(null);
    }
  }

  /**
   * Adds a {@code listener} to be notified of Flutter engine lifecycle events, e.g., {@code
   * onPreEngineStart()}.
   */
  public void addEngineLifecycleListener(@NonNull EngineLifecycleListener listener) {
    engineLifecycleListeners.add(listener);
  }

  /**
   * Removes a {@code listener} that was previously added with {@link
   * #addEngineLifecycleListener(EngineLifecycleListener)}.
   */
  public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener listener) {
    engineLifecycleListeners.remove(listener);
  }

  /**
   * The Dart execution context associated with this {@code FlutterEngine}.
   *
   * <p>The {@link DartExecutor} can be used to start executing Dart code from a given entrypoint.
   * See {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}.
   *
   * <p>Use the {@link DartExecutor} to connect any desired message channels and method channels to
   * facilitate communication between Android and Dart/Flutter.
   */
  @NonNull
  public DartExecutor getDartExecutor() {
    return dartExecutor;
  }

  /**
   * The rendering system associated with this {@code FlutterEngine}.
   *
   * <p>To render a Flutter UI that is produced by this {@code FlutterEngine}'s Dart code, attach a
   * {@link RenderSurface} to this {@link FlutterRenderer}.
   */
  @NonNull
  public FlutterRenderer getRenderer() {
    return renderer;
  }

  /** System channel that sends accessibility requests and events from Flutter to Android. */
  @NonNull
  public AccessibilityChannel getAccessibilityChannel() {
    return accessibilityChannel;
  }

  /** System channel that sends Android lifecycle events to Flutter. */
  @NonNull
  public LifecycleChannel getLifecycleChannel() {
    return lifecycleChannel;
  }

  /** System channel that sends locale data from Android to Flutter. */
  @NonNull
  public LocalizationChannel getLocalizationChannel() {
    return localizationChannel;
  }

  /** System channel that sends Flutter navigation commands from Android to Flutter. */
  @NonNull
  public NavigationChannel getNavigationChannel() {
    return navigationChannel;
  }

  /**
   * System channel that sends platform-oriented requests and information to Flutter, e.g., requests
   * to play sounds, requests for haptics, system chrome settings, etc.
   */
  @NonNull
  public PlatformChannel getPlatformChannel() {
    return platformChannel;
  }

  /**
   * System channel to exchange restoration data between framework and engine.
   *
   * <p>The engine can obtain the current restoration data from the framework via this channel to
   * store it on disk and - when the app is relaunched - provide the stored data back to the
   * framework to recreate the original state of the app.
   */
  @NonNull
  public RestorationChannel getRestorationChannel() {
    return restorationChannel;
  }

  /**
   * System channel that sends platform/user settings from Android to Flutter, e.g., time format,
   * scale factor, etc.
   */
  @NonNull
  public SettingsChannel getSettingsChannel() {
    return settingsChannel;
  }

  /** System channel that allows manual installation and state querying of deferred components. */
  @NonNull
  public DeferredComponentChannel getDeferredComponentChannel() {
    return deferredComponentChannel;
  }

  /** System channel that sends memory pressure warnings from Android to Flutter. */
  @NonNull
  public SystemChannel getSystemChannel() {
    return systemChannel;
  }

  /** System channel that sends and receives text input requests and state. */
  @NonNull
  public MouseCursorChannel getMouseCursorChannel() {
    return mouseCursorChannel;
  }

  /** System channel that sends and receives text input requests and state. */
  @NonNull
  public TextInputChannel getTextInputChannel() {
    return textInputChannel;
  }

  /** System channel that sends and receives spell check requests and results. */
  @NonNull
  public SpellCheckChannel getSpellCheckChannel() {
    return spellCheckChannel;
  }

  /**
   * Plugin registry, which registers plugins that want to be applied to this {@code FlutterEngine}.
   */
  @NonNull
  public PluginRegistry getPlugins() {
    return pluginRegistry;
  }

  /** The LocalizationPlugin this FlutterEngine created. */
  @NonNull
  public LocalizationPlugin getLocalizationPlugin() {
    return localizationPlugin;
  }

  /**
   * {@code PlatformViewsController}, which controls all platform views running within this {@code
   * FlutterEngine}.
   */
  @NonNull
  public PlatformViewsController getPlatformViewsController() {
    return platformViewsController;
  }

  @NonNull
  public ActivityControlSurface getActivityControlSurface() {
    return pluginRegistry;
  }

  @NonNull
  public ServiceControlSurface getServiceControlSurface() {
    return pluginRegistry;
  }

  @NonNull
  public BroadcastReceiverControlSurface getBroadcastReceiverControlSurface() {
    return pluginRegistry;
  }

  @NonNull
  public ContentProviderControlSurface getContentProviderControlSurface() {
    return pluginRegistry;
  }

  /** Lifecycle callbacks for Flutter engine lifecycle events. */
  public interface EngineLifecycleListener {
    /** Lifecycle callback invoked before a hot restart of the Flutter engine. */
    void onPreEngineRestart();
    /**
     * Lifecycle callback invoked before the Flutter engine is destroyed.
     *
     * <p>For the duration of the call, the Flutter engine is still valid.
     */
    void onEngineWillDestroy();
  }

  @Override
  public void updateDisplayMetrics(float width, float height, float density) {
    flutterJNI.updateDisplayMetrics(0 /* display ID */, width, height, density);
  }
}
