extrasKeys = extras.keySet();
+
+ for (String extrasKey : extrasKeys) {
+ FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagFromIntentKey(extrasKey);
+ if (flag != null) {
+ Log.w(
+ TAG,
+ "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/180686 for more information on this breaking change. To migrate, set "
+ + flag.commandLineArgument
+ + " on the command line or see https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for alternative methods.");
+ break;
+ }
+ }
+ }
+
/**
* Invoke this method from {@code Activity#onCreate(Bundle)} to create the content {@code View},
* or from {@code Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
@@ -1090,7 +1117,6 @@ import java.util.List;
@NonNull
Lifecycle getLifecycle();
- /** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */
@NonNull
FlutterShellArgs getFlutterShellArgs();
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
index cf57c07eccd..c8a66a02985 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
@@ -177,8 +177,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
* native library and start a Dart VM.
*
* 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.FlutterEngineFlags} for all available flags) 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.
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
index bcb5addfff6..458c73f3a5c 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java
@@ -59,6 +59,7 @@ import java.util.Set;
// Standard FlutterPlugin
@NonNull private final FlutterEngine flutterEngine;
+ @NonNull private final FlutterLoader flutterLoader;
@NonNull private final FlutterPlugin.FlutterPluginBinding pluginBinding;
// ActivityAware
@@ -100,6 +101,7 @@ import java.util.Set;
@NonNull FlutterLoader flutterLoader,
@Nullable FlutterEngineGroup group) {
this.flutterEngine = flutterEngine;
+ this.flutterLoader = flutterLoader;
pluginBinding =
new FlutterPlugin.FlutterPluginBinding(
appContext,
@@ -326,13 +328,31 @@ import java.util.Set;
private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) {
this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle);
+ final Intent intent = activity.getIntent();
- final boolean useSoftwareRendering =
- activity.getIntent() != null
- ? activity
- .getIntent()
- .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)
+ // TODO(camsim99): Remove ability to set this flag via Intents. See
+ // https://github.com/flutter/flutter/issues/180686.
+ boolean useSoftwareRendering =
+ intent != null
+ ? intent.getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)
: false;
+
+ // As part of https://github.com/flutter/flutter/issues/172553, the ability to set
+ // --enable-software-rendering via Intent is planned to be removed. Warn
+ // developers about the new method for doing so if this was attempted.
+ // TODO(camsim99): Remove this warning after a stable release has passed:
+ // https://github.com/flutter/flutter/issues/179274.
+ if (useSoftwareRendering) {
+ Log.w(
+ TAG,
+ "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/172553 for more information on this breaking change. To migrate, set the "
+ + FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey
+ + " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for more info.");
+ } else {
+ // Check manifest for software rendering configuration.
+ useSoftwareRendering = flutterLoader.getSofwareRenderingEnabledViaManifest();
+ }
+
flutterEngine.getPlatformViewsController().setSoftwareRendering(useSoftwareRendering);
// Activate the PlatformViewsController. This must happen before any plugins attempt
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java
new file mode 100644
index 00000000000..a78735efc59
--- /dev/null
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java
@@ -0,0 +1,421 @@
+// 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 androidx.annotation.VisibleForTesting;
+import java.util.*;
+
+/**
+ * Arguments that can be delivered to the Flutter shell on Android.
+ *
+ *
The term "shell" refers to the native code that adapts Flutter to different platforms.
+ * Flutter's Android Java code initializes a native "shell" and passes these arguments to that
+ * native shell when it is initialized. See {@link
+ * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])}
+ * for more information.
+ *
+ *
All of these flags map to a flag listed in shell/common/switches.cc, which contains the full
+ * list of flags that can be set across all platforms.
+ *
+ *
These flags can either be set via the manifest metadata in a Flutter component's
+ * AndroidManifest.xml or via the command line. See the inner {@code Flag} class for the
+ * specification of how to set each flag via the command line and manifest metadata.
+ *
+ *
If the same flag is provided both via command line arguments and via AndroidManifest.xml
+ * metadata, the command line value will take precedence at runtime.
+ */
+public final class FlutterEngineFlags {
+
+ private FlutterEngineFlags() {}
+
+ /** Represents a Flutter shell flag that can be set via manifest metadata or command line. */
+ public static class Flag {
+ /** The command line argument used to specify the flag. */
+ public final String commandLineArgument;
+
+ /**
+ * The metadata key name used to specify the flag in AndroidManifest.xml.
+ *
+ *
To specify a flag in a manifest, it should be prefixed with {@code
+ * io.flutter.embedding.android.}. This is enforced to avoid potential naming collisions with
+ * other metadata keys. The only exception are flags that have already been deprecated.
+ */
+ public final String metadataKey;
+
+ /** Whether this flag is allowed to be set in release mode. */
+ public final boolean allowedInRelease;
+
+ /**
+ * Creates a new Flutter shell flag that is not allowed in release mode with the default flag
+ * prefix.
+ */
+ private Flag(String commandLineArgument, String metaDataName) {
+ this(commandLineArgument, metaDataName, "io.flutter.embedding.android.", false);
+ }
+
+ /** Creates a new Flutter shell flag with the default flag prefix. */
+ private Flag(String commandLineArgument, String metaDataName, boolean allowedInRelease) {
+ this(commandLineArgument, metaDataName, "io.flutter.embedding.android.", allowedInRelease);
+ }
+
+ /**
+ * Creates a new Flutter shell flag.
+ *
+ *
{@param allowedInRelease} determines whether or not this flag is allowed in release mode.
+ * Whenever possible, it is recommended to NOT allow this flag in release mode. Many flags are
+ * designed for debugging purposes and if enabled in production, could expose sensitive
+ * application data or make the app vulnerable to malicious actors.
+ *
+ *
If creating a flag that will be allowed in release, please leave a comment in the Javadoc
+ * explaining why it should be allowed in release.
+ */
+ private Flag(
+ String commandLineArgument,
+ String metaDataName,
+ String flagPrefix,
+ boolean allowedInRelease) {
+ this.commandLineArgument = commandLineArgument;
+ this.metadataKey = flagPrefix + metaDataName;
+ this.allowedInRelease = allowedInRelease;
+ }
+
+ /** Returns true if this flag requires a value to be specified. */
+ public boolean hasValue() {
+ return commandLineArgument.endsWith("=");
+ }
+ }
+
+ // Manifest flags allowed in release mode:
+
+ /**
+ * Specifies the path to the AOT shared library containing compiled Dart code.
+ *
+ *
The AOT shared library that the engine uses will default to the library set by this flag,
+ * but will fall back to the libraries set internally by the embedding if the path specified by
+ * this argument is invalid.
+ *
+ *
Allowed in release to support the same AOT configuration regardless of build mode.
+ */
+ public static final Flag AOT_SHARED_LIBRARY_NAME =
+ new Flag("--aot-shared-library-name=", "AOTSharedLibraryName", true);
+
+ /**
+ * Deprecated flag that specifies the path to the AOT shared library containing compiled Dart
+ * code.
+ *
+ *
Please use {@link AOT_SHARED_LIBRARY_NAME} instead.
+ */
+ @Deprecated
+ public static final Flag DEPRECATED_AOT_SHARED_LIBRARY_NAME =
+ new Flag(
+ "--aot-shared-library-name=",
+ "aot-shared-library-name",
+ "io.flutter.embedding.engine.loader.FlutterLoader.",
+ true);
+
+ /**
+ * Sets the directory containing Flutter assets.
+ *
+ *
Allowed in release to specify custom asset locations in production.
+ */
+ public static final Flag FLUTTER_ASSETS_DIR =
+ new Flag("--flutter-assets-dir=", "FlutterAssetsDir", true);
+
+ /**
+ * The deprecated flag that sets the directory containing Flutter assets.
+ *
+ *
Please use {@link FLUTTER_ASSETS_DIR} infstead.
+ */
+ @Deprecated
+ public static final Flag DEPRECATED_FLUTTER_ASSETS_DIR =
+ new Flag(
+ "--flutter-assets-dir=",
+ "flutter-assets-dir",
+ "io.flutter.embedding.engine.loader.FlutterLoader.",
+ true);
+
+ /**
+ * Sets the old generation heap size for the Dart VM in megabytes.
+ *
+ *
Allowed in release for performance tuning.
+ */
+ public static final Flag OLD_GEN_HEAP_SIZE =
+ new Flag("--old-gen-heap-size=", "OldGenHeapSize", true);
+
+ /**
+ * Enables or disables the Impeller renderer.
+ *
+ *
Allowed in release to control which rendering backend is used in production.
+ */
+ private static final Flag TOGGLE_IMPELLER =
+ new Flag("--enable-impeller=", "ToggleImpeller", true);
+
+ /**
+ * Enables Impeller.
+ *
+ *
Allowed in release to control which rendering backend is used in production.
+ */
+ private static final Flag ENABLE_IMPELLER = new Flag("--enable-impeller", "EnableImpeller", true);
+ /**
+ * Specifies the backend to use for Impeller rendering.
+ *
+ *
Allowed in release to select a specific graphics backend for Impeller in production.
+ */
+ private static final Flag IMPELLER_BACKEND =
+ new Flag("--impeller-backend=", "ImpellerBackend", true);
+
+ /**
+ * Enables Dart profiling for use with DevTools.
+ *
+ *
Allowed in release mode for testing purposes.
+ */
+ private static final Flag ENABLE_DART_PROFILING =
+ new Flag("--enable-dart-profiling", "EnableDartProfiling", true);
+
+ /**
+ * Discards new profiler samples once the buffer is full. Only meaningful when set in conjunction
+ * with {@link ENABLE_DART_PROFILING}.
+ *
+ *
Allowed in release mode to allow the startup performance to be profiled by DevTools.
+ */
+ private static final Flag PROFILE_STARTUP = new Flag("--profile-startup", "ProfileStartup", true);
+
+ /**
+ * Measures startup time and switches to an endless trace buffer.
+ *
+ *
Allowed in release mode to allow the startup performance to be profiled by DevTools.
+ */
+ private static final Flag TRACE_STARTUP = new Flag("--trace-startup", "TraceStartup", true);
+
+ /**
+ * Sets whether the UI thread and platform thread should be merged.
+ *
+ *
Allowed in release mode for performance purposes.
+ */
+ private static final Flag MERGED_PLATFORM_UI_THREAD =
+ new Flag("--merged-platform-ui-thread", "MergedPlatformUIThread", true);
+
+ /**
+ * Specifies the path to the VM snapshot data file.
+ *
+ *
Allowed in release to support different snapshot configurations.
+ */
+ public static final Flag VM_SNAPSHOT_DATA =
+ new Flag("--vm-snapshot-data=", "VmSnapshotData", true);
+
+ /**
+ * Specifies the path to the isolate snapshot data file.
+ *
+ *
Allowed in release to support different snapshot configurations.
+ */
+ public static final Flag ISOLATE_SNAPSHOT_DATA =
+ new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true);
+
+ // Manifest flags NOT allowed in release mode:
+
+ /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */
+ private static final Flag SKIA_DETERMINISTIC_RENDERING =
+ new Flag("--skia-deterministic-rendering", "SkiaDeterministicRendering");
+
+ /** Use Skia software backend for rendering. */
+ public static final Flag ENABLE_SOFTWARE_RENDERING =
+ new Flag("--enable-software-rendering", "EnableSoftwareRendering");
+
+ /** Use the Ahem test font for font resolution. */
+ private static final Flag USE_TEST_FONTS = new Flag("--use-test-fonts", "UseTestFonts");
+
+ /** Sets the port for the Dart VM Service. */
+ private static final Flag VM_SERVICE_PORT = new Flag("--vm-service-port=", "VMServicePort");
+
+ /** Enables Vulkan validation layers if available. */
+ private static final Flag ENABLE_VULKAN_VALIDATION =
+ new Flag("--enable-vulkan-validation", "EnableVulkanValidation");
+
+ /** Fake flag used for integration testing of the Android embedding processing engine flags. */
+ @VisibleForTesting public static final Flag TEST_FLAG = new Flag("--test-flag", "TestFlag");
+
+ /**
+ * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's
+ * metadata in the application block in AndroidManifest.xml. Set it to true in to leave the Dart
+ * VM, set it to false to destroy VM.
+ *
+ *
If your want to let your app destroy the last shell and re-create shells more quickly, set
+ * it to true, otherwise if you want to clean up the memory of the leak VM, set it to false.
+ *
+ *
TODO(eggfly): Should it be set to false by default?
+ * https://github.com/flutter/flutter/issues/96843
+ */
+ public static final Flag LEAK_VM = new Flag("--leak-vm=", "LeakVM");
+
+ /** Pauses Dart code execution at launch until a debugger is attached. */
+ private static final Flag START_PAUSED = new Flag("--start-paused", "StartPaused");
+
+ /** Disables authentication codes for VM service communication. */
+ private static final Flag DISABLE_SERVICE_AUTH_CODES =
+ new Flag("--disable-service-auth-codes", "DisableServiceAuthCodes");
+
+ /** Enables an endless trace buffer for timeline events. */
+ private static final Flag ENDLESS_TRACE_BUFFER =
+ new Flag("--endless-trace-buffer", "EndlessTraceBuffer");
+
+ /** Enables tracing of Skia GPU calls. */
+ private static final Flag TRACE_SKIA = new Flag("--trace-skia", "TraceSkia");
+
+ /** Only traces specified Skia event categories. */
+ private static final Flag TRACE_SKIA_ALLOWLIST =
+ new Flag("--trace-skia-allowlist=", "TraceSkiaAllowList");
+
+ /** Traces to the system tracer on supported platforms. */
+ private static final Flag TRACE_SYSTRACE = new Flag("--trace-systrace", "TraceSystrace");
+
+ /** Writes timeline trace to a file in Perfetto format. */
+ private static final Flag TRACE_TO_FILE = new Flag("--trace-to-file=", "TraceToFile");
+
+ /** Collects and logs information about microtasks. */
+ private static final Flag PROFILE_MICROTASKS =
+ new Flag("--profile-microtasks", "ProfileMicrotasks");
+
+ /** Dumps SKP files that trigger shader compilations. */
+ private static final Flag DUMP_SKP_ON_SHADER_COMPILATION =
+ new Flag("--dump-skp-on-shader-compilation", "DumpSkpOnShaderCompilation");
+
+ /** Removes all persistent cache files for debugging. */
+ private static final Flag PURGE_PERSISTENT_CACHE =
+ new Flag("--purge-persistent-cache", "PurgePersistentCache");
+
+ /** Enables logging at all severity levels. */
+ private static final Flag VERBOSE_LOGGING = new Flag("--verbose-logging", "VerboseLogging");
+
+ /** Only cache the shader in SkSL instead of binary or GLSL. */
+ private static final Flag CACHE_SKSL = new Flag("--cache-sksl", "CacheSksl");
+
+ /**
+ * Passes additional flags to the Dart VM.
+ *
+ *
All flags provided with this argument are subject to filtering based on a list of allowed
+ * flags in shell/common/switches.cc. If any flag provided is not allowed, the process will
+ * immediately terminate.
+ *
+ *
Flags should be separated by a space, e.g. "--dart-flags=--flag-1 --flag-2=2".
+ */
+ private static final Flag DART_FLAGS = new Flag("--dart-flags=", "DartFlags");
+
+ // Deprecated flags:
+
+ /** Disables the merging of the UI and platform threads. */
+ @VisibleForTesting
+ public static final Flag DISABLE_MERGED_PLATFORM_UI_THREAD =
+ new Flag("--no-enable-merged-platform-ui-thread", "DisableMergedPlatformUIThread");
+
+ @VisibleForTesting
+ public static final List ALL_FLAGS =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ VM_SERVICE_PORT,
+ USE_TEST_FONTS,
+ ENABLE_SOFTWARE_RENDERING,
+ SKIA_DETERMINISTIC_RENDERING,
+ AOT_SHARED_LIBRARY_NAME,
+ FLUTTER_ASSETS_DIR,
+ ENABLE_IMPELLER,
+ IMPELLER_BACKEND,
+ ENABLE_VULKAN_VALIDATION,
+ START_PAUSED,
+ DISABLE_SERVICE_AUTH_CODES,
+ ENDLESS_TRACE_BUFFER,
+ ENABLE_DART_PROFILING,
+ PROFILE_STARTUP,
+ TRACE_SKIA,
+ TRACE_SKIA_ALLOWLIST,
+ TRACE_SYSTRACE,
+ TRACE_TO_FILE,
+ PROFILE_MICROTASKS,
+ DUMP_SKP_ON_SHADER_COMPILATION,
+ VERBOSE_LOGGING,
+ DART_FLAGS,
+ MERGED_PLATFORM_UI_THREAD,
+ DISABLE_MERGED_PLATFORM_UI_THREAD,
+ DEPRECATED_AOT_SHARED_LIBRARY_NAME,
+ DEPRECATED_FLUTTER_ASSETS_DIR,
+ TOGGLE_IMPELLER,
+ OLD_GEN_HEAP_SIZE,
+ VM_SNAPSHOT_DATA,
+ ISOLATE_SNAPSHOT_DATA,
+ CACHE_SKSL,
+ PURGE_PERSISTENT_CACHE,
+ TRACE_STARTUP,
+ LEAK_VM,
+ TEST_FLAG));
+
+ // Flags that have been turned off.
+ private static final List DISABLED_FLAGS =
+ Collections.unmodifiableList(Arrays.asList(DISABLE_MERGED_PLATFORM_UI_THREAD));
+
+ // Lookup map for current flags that replace deprecated ones.
+ private static final Map DEPRECATED_FLAGS_BY_REPLACEMENT =
+ new HashMap() {
+ {
+ put(DEPRECATED_AOT_SHARED_LIBRARY_NAME, AOT_SHARED_LIBRARY_NAME);
+ put(DEPRECATED_FLUTTER_ASSETS_DIR, FLUTTER_ASSETS_DIR);
+ }
+ };
+
+ // Lookup map for retrieving the Flag corresponding to a specific command line argument.
+ private static final Map FLAG_BY_COMMAND_LINE_ARG;
+
+ // Lookup map for retrieving the Flag corresponding to a specific metadata key.
+ private static final Map FLAG_BY_META_DATA_KEY;
+
+ static {
+ Map map = new HashMap(ALL_FLAGS.size());
+ Map metaMap = new HashMap(ALL_FLAGS.size());
+ for (Flag flag : ALL_FLAGS) {
+ map.put(flag.commandLineArgument, flag);
+ metaMap.put(flag.metadataKey, flag);
+ }
+ FLAG_BY_COMMAND_LINE_ARG = Collections.unmodifiableMap(map);
+ FLAG_BY_META_DATA_KEY = Collections.unmodifiableMap(metaMap);
+ }
+
+ /** Looks up a {@link Flag} by its commandLineArgument. */
+ public static Flag getFlagByCommandLineArgument(String arg) {
+ int equalsIndex = arg.indexOf('=');
+ Flag flag =
+ FLAG_BY_COMMAND_LINE_ARG.get(equalsIndex == -1 ? arg : arg.substring(0, equalsIndex + 1));
+ Flag replacementFlag = getReplacementFlagIfDeprecated(flag);
+ return replacementFlag != null ? replacementFlag : flag;
+ }
+
+ /**
+ * Looks up a {@link Flag} by its Intent key.
+ *
+ * Previously, the Intent keys were used to set Flutter shell arguments via Intent. The Intent
+ * keys match the command line argument without the "--" prefix and "=" suffix if the argument
+ * takes a value.
+ */
+ public static Flag getFlagFromIntentKey(String intentKey) {
+ for (Flag flag : ALL_FLAGS) {
+ String commandLineArg = flag.commandLineArgument;
+ String key = commandLineArg.startsWith("--") ? commandLineArg.substring(2) : commandLineArg;
+ if (key.endsWith("=")) {
+ key = key.substring(0, key.length() - 1);
+ }
+ if (key.equals(intentKey)) {
+ return flag;
+ }
+ }
+ return null;
+ }
+
+ /** Returns whether or not a flag is disabled and should raise an exception if used. */
+ public static boolean isDisabled(Flag flag) {
+ return DISABLED_FLAGS.contains(flag);
+ }
+
+ /** Returns the replacement flag of that given if it is deprecated. */
+ public static Flag getReplacementFlagIfDeprecated(Flag flag) {
+ return DEPRECATED_FLAGS_BY_REPLACEMENT.get(flag);
+ }
+}
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java
index 697ecc742d7..26058798bfb 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java
@@ -10,7 +10,10 @@ import androidx.annotation.NonNull;
import java.util.*;
/**
- * Arguments that can be delivered to the Flutter shell when it is created.
+ * DEPRECATED. Please see {@link FlutterEngineFlags} for the list of arguments to use or update if
+ * you are adding a new flag.
+ *
+ *
Arguments that can be delivered to the Flutter shell when it is created.
*
*
The term "shell" refers to the native code that adapts Flutter to different platforms.
* Flutter's Android Java code initializes a native "shell" and passes these arguments to that
@@ -18,7 +21,10 @@ import java.util.*;
* io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])}
* for more information.
*/
+// TODO(camsim99): Delete this class when support for setting engine shell arguments via Intent
+// is no longer supported. See https://github.com/flutter/flutter/issues/180686.
@SuppressWarnings({"WeakerAccess", "unused"})
+@Deprecated
public class FlutterShellArgs {
public static final String ARG_KEY_TRACE_STARTUP = "trace-startup";
public static final String ARG_TRACE_STARTUP = "--trace-startup";
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
index 24c50298358..412af23b691 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
@@ -10,24 +10,19 @@ import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import androidx.annotation.NonNull;
+import io.flutter.embedding.engine.FlutterEngineFlags;
import java.io.IOException;
import org.json.JSONArray;
import org.xmlpull.v1.XmlPullParserException;
/** Loads application information given a Context. */
public final class ApplicationInfoLoader {
- // XML Attribute keys supported in AndroidManifest.xml
- public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
- FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
- public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
- FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
- public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
- FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
- public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
- FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
+ // TODO(camsim99): Remove support for these flags:
+ // https://github.com/flutter/flutter/issues/179276.
+ // AndroidManifest.xml metadata keys for setting internal respective Flutter configuration values.
public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY =
- "io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY;
+ "io.flutter.automatically-register-plugins";
@NonNull
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
@@ -47,6 +42,20 @@ public final class ApplicationInfoLoader {
return metadata.getString(key, null);
}
+ private static String getStringWithFallback(Bundle metadata, String key, String fallbackKey) {
+ if (metadata == null) {
+ return null;
+ }
+
+ String metadataString = metadata.getString(key, null);
+
+ if (metadataString == null) {
+ metadataString = metadata.getString(fallbackKey);
+ }
+
+ return metadataString;
+ }
+
private static boolean getBoolean(Bundle metadata, String key, boolean defaultValue) {
if (metadata == null) {
return defaultValue;
@@ -146,11 +155,21 @@ public final class ApplicationInfoLoader {
@NonNull
public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
+
+ // TODO(camsim99): Remove support for DEPRECATED_AOT_SHARED_LIBRARY_NAME and
+ // DEPRECATED_FLUTTER_ASSETS_DIR
+ // when all usage of the deprecated names has been removed.
return new FlutterApplicationInfo(
- getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
- getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
- getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
- getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
+ getStringWithFallback(
+ appInfo.metaData,
+ FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME.metadataKey,
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey),
+ getString(appInfo.metaData, FlutterEngineFlags.VM_SNAPSHOT_DATA.metadataKey),
+ getString(appInfo.metaData, FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.metadataKey),
+ getStringWithFallback(
+ appInfo.metaData,
+ FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR.metadataKey,
+ FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey),
getNetworkPolicy(appInfo, applicationContext),
appInfo.nativeLibraryDir,
getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true));
diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
index 07df17b5d85..6ea5875ce67 100644
--- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
@@ -22,6 +22,7 @@ import androidx.annotation.VisibleForTesting;
import io.flutter.BuildConfig;
import io.flutter.FlutterInjector;
import io.flutter.Log;
+import io.flutter.embedding.engine.FlutterEngineFlags;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.HandlerCompat;
import io.flutter.util.PathUtils;
@@ -38,49 +39,10 @@ import java.util.concurrent.Future;
public class FlutterLoader {
private static final String TAG = "FlutterLoader";
- private static final String OLD_GEN_HEAP_SIZE_META_DATA_KEY =
- "io.flutter.embedding.android.OldGenHeapSize";
- private static final String ENABLE_IMPELLER_META_DATA_KEY =
- "io.flutter.embedding.android.EnableImpeller";
- private static final String ENABLE_VULKAN_VALIDATION_META_DATA_KEY =
- "io.flutter.embedding.android.EnableVulkanValidation";
- private static final String IMPELLER_BACKEND_META_DATA_KEY =
- "io.flutter.embedding.android.ImpellerBackend";
- private static final String IMPELLER_OPENGL_GPU_TRACING_DATA_KEY =
- "io.flutter.embedding.android.EnableOpenGLGPUTracing";
- private static final String IMPELLER_VULKAN_GPU_TRACING_DATA_KEY =
- "io.flutter.embedding.android.EnableVulkanGPUTracing";
- private static final String DISABLE_MERGED_PLATFORM_UI_THREAD_KEY =
- "io.flutter.embedding.android.DisableMergedPlatformUIThread";
- private static final String ENABLE_SURFACE_CONTROL =
- "io.flutter.embedding.android.EnableSurfaceControl";
- private static final String ENABLE_FLUTTER_GPU = "io.flutter.embedding.android.EnableFlutterGPU";
- private static final String IMPELLER_LAZY_SHADER_MODE =
- "io.flutter.embedding.android.ImpellerLazyShaderInitialization";
- private static final String IMPELLER_ANTIALIAS_LINES =
- "io.flutter.embedding.android.ImpellerAntialiasLines";
-
- /**
- * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's
- * meta-data in in AndroidManifest.xml. Set it to true in to leave the Dart VM,
- * set it to false to destroy VM.
- *
- *
If your want to let your app destroy the last shell and re-create shells more quickly, set
- * it to true, otherwise if you want to clean up the memory of the leak VM, set it to false.
- *
- *
TODO(eggfly): Should it be set to false by default?
- * https://github.com/flutter/flutter/issues/96843
- */
- private static final String LEAK_VM_META_DATA_KEY = "io.flutter.embedding.android.LeakVM";
-
- // Must match values in flutter::switches
- static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
- static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME = "aot-vmservice-shared-library-name";
- static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
- static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
- static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
- static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
- static final String AUTOMATICALLY_REGISTER_PLUGINS_KEY = "automatically-register-plugins";
+ // Flags to only be set internally by default. Match values in flutter::switches.
+ private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
+ private static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME =
+ "aot-vmservice-shared-library-name";
// Resource names used for components of the precompiled snapshot.
private static final String DEFAULT_LIBRARY = "libflutter.so";
@@ -89,8 +51,7 @@ public class FlutterLoader {
private static FlutterLoader instance;
- @VisibleForTesting
- static final String aotSharedLibraryNameFlag = "--" + AOT_SHARED_LIBRARY_NAME + "=";
+ private boolean enableSoftwareRendering = false;
/**
* Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link
@@ -295,6 +256,21 @@ public class FlutterLoader {
*/
public void ensureInitializationComplete(
@NonNull Context applicationContext, @Nullable String[] args) {
+ ensureInitializationComplete(applicationContext, args, BuildConfig.RELEASE);
+ }
+
+ /**
+ * Blocks until initialization of the native system has completed.
+ *
+ *
Calling this method multiple times has no effect.
+ *
+ * @param applicationContext The Android application context.
+ * @param args Flags sent to the Flutter runtime.
+ * @param isRelease Whether or not the Flutter component is running in release mode.
+ */
+ @VisibleForTesting
+ void ensureInitializationComplete(
+ @NonNull Context applicationContext, @Nullable String[] args, @NonNull boolean isRelease) {
if (initialized) {
return;
}
@@ -311,59 +287,174 @@ public class FlutterLoader {
InitResult result = initResultFuture.get();
List shellArgs = new ArrayList<>();
- shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
+ // Add engine flags for which defaults set internally take precedent.
+ shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
shellArgs.add(
"--icu-native-lib-path="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ DEFAULT_LIBRARY);
- if (args != null) {
- for (String arg : args) {
- // Perform security check for path containing application's compiled Dart code and
- // potentially user-provided compiled native code.
- if (arg.startsWith(aotSharedLibraryNameFlag)) {
- String safeAotSharedLibraryNameFlag =
- getSafeAotSharedLibraryNameFlag(applicationContext, arg);
- if (safeAotSharedLibraryNameFlag != null) {
- arg = safeAotSharedLibraryNameFlag;
- } else {
- // If the library path is not safe, we will skip adding this argument.
- Log.w(
- TAG,
- "Skipping unsafe AOT shared library name flag: "
- + arg
- + ". Please ensure that the library is vetted and placed in your application's internal storage.");
- continue;
- }
+ // Add engine flags provided by metadata in the application manifest. These settings will take
+ // precedent over any defaults set below, but will be overridden if additionally set by the
+ // command line.
+ ApplicationInfo applicationInfo =
+ applicationContext
+ .getPackageManager()
+ .getApplicationInfo(
+ applicationContext.getPackageName(), PackageManager.GET_META_DATA);
+ Bundle applicationMetaData = applicationInfo.metaData;
+ boolean oldGenHeapSizeSet = false;
+ boolean isLeakVMSet = false;
+
+ if (applicationMetaData != null) {
+ for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) {
+ String metadataKey = flag.metadataKey;
+ if (!applicationMetaData.containsKey(metadataKey)) {
+ continue;
+ }
+
+ // Check if flag is valid:
+
+ if (flag == FlutterEngineFlags.TEST_FLAG) {
+ Log.w(
+ TAG,
+ "For testing purposes only: test flag specified in the manifest was loaded by the FlutterLoader.");
+ continue;
+ } else if (FlutterEngineFlags.isDisabled(flag)) {
+ // Do not allow disabled flags.
+ throw new IllegalArgumentException(
+ metadataKey
+ + " is disabled and no longer allowed. Please remove this flag from your application manifest.");
+ } else if (FlutterEngineFlags.getReplacementFlagIfDeprecated(flag) != null) {
+ Log.w(
+ TAG,
+ "If you are trying to specify "
+ + metadataKey
+ + " in your application manifest, please make sure to use the new metadata key name: "
+ + FlutterEngineFlags.getReplacementFlagIfDeprecated(flag));
+ } else if (!flag.allowedInRelease && isRelease) {
+ // Manifest flag is not allowed in release builds.
+ Log.w(
+ TAG,
+ "Flag with metadata key "
+ + metadataKey
+ + " is not allowed in release builds and will be ignored if specified in the application manifest or via the command line.");
+ continue;
+ }
+
+ // Handle special cases for specific flags:
+
+ if (flag == FlutterEngineFlags.OLD_GEN_HEAP_SIZE) {
+ // Mark if old gen heap size is set to track whether or not to set default
+ // internally.
+ oldGenHeapSizeSet = true;
+ } else if (flag == FlutterEngineFlags.LEAK_VM) {
+ // Mark if leak VM is set to track whether or not to set default internally.
+ isLeakVMSet = true;
+ } else if (flag == FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING) {
+ // Enabling software rendering impacts platform views, so save this value
+ // so that the PlatformViewsController can be properly configured.
+ enableSoftwareRendering =
+ applicationMetaData.getBoolean(
+ FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey, false);
+ } else if (flag == FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME
+ || flag == FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME) {
+ // Perform security check for path containing application's compiled Dart
+ // code and potentially user-provided compiled native code.
+ String aotSharedLibraryPath = applicationMetaData.getString(metadataKey);
+ maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs);
+ continue;
+ }
+
+ // Add flag to shell args.
+ String arg = flag.commandLineArgument;
+ if (flag.hasValue()) {
+ Object valueObj = applicationMetaData.get(metadataKey);
+ String value = valueObj != null ? valueObj.toString() : null;
+ if (value == null) {
+ Log.e(
+ TAG,
+ "Flag with metadata key "
+ + metadataKey
+ + " requires a value, but no value was found. Please ensure that the value is a string.");
+ continue;
+ }
+ arg += value;
}
- // TODO(camsim99): This is a dangerous pattern that blindly allows potentially malicious
- // arguments to be used for engine initialization and should be fixed. See
- // https://github.com/flutter/flutter/issues/172553.
shellArgs.add(arg);
}
}
+ // Add any remaining engine flags provided by the command line. These settings will take
+ // precedent over any flag settings specified by application manifest
+ // metadata and any defaults set below.
+ if (args != null) {
+ for (String arg : args) {
+ FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagByCommandLineArgument(arg);
+ if (flag == null) {
+ // TODO(camsim99): Reject unknown flags specified on the command line:
+ // https://github.com/flutter/flutter/issues/182557.
+ shellArgs.add(arg);
+ continue;
+ } else if (flag.equals(FlutterEngineFlags.TEST_FLAG)) {
+ Log.w(
+ TAG,
+ "For testing purposes only: test flag specified on the command line was loaded by the FlutterLoader.");
+ continue;
+ } else if (flag.equals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME)
+ || flag.equals(FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME)) {
+ // Perform security check for path containing application's compiled Dart
+ // code and potentially user-provided compiled native code.
+ String aotSharedLibraryPath =
+ arg.substring(
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length());
+ maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs);
+ continue;
+ } else if (!flag.allowedInRelease && isRelease) {
+ // Flag is not allowed in release builds.
+ Log.w(
+ TAG,
+ "Command line argument "
+ + arg
+ + " is not allowed in release builds and will be ignored if specified in the application manifest or via the command line.");
+ continue;
+ }
+
+ shellArgs.add(arg);
+ }
+ }
+
+ // Add engine flags set by default internally. Some of these settings can be overridden
+ // by command line args or application manifest metadata.
+
String kernelPath = null;
if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
String snapshotAssetPath =
result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir;
kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
- shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData);
shellArgs.add(
- "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
+ FlutterEngineFlags.VM_SNAPSHOT_DATA.commandLineArgument
+ + flutterApplicationInfo.vmSnapshotData);
+ shellArgs.add(
+ FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.commandLineArgument
+ + flutterApplicationInfo.isolateSnapshotData);
} else {
- // Add default AOT shared library name arg.
- shellArgs.add(aotSharedLibraryNameFlag + flutterApplicationInfo.aotSharedLibraryName);
+ // Add default AOT shared library name arg. Note that if a different library
+ // is set in the manifest, that value will take precendence and the default
+ // libraries will be used as fallbacks in the order that they are added.
+ shellArgs.add(
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument
+ + flutterApplicationInfo.aotSharedLibraryName);
// Some devices cannot load the an AOT shared library based on the library name
// with no directory path. So, we provide a fully qualified path to the default library
// as a workaround for devices where that fails.
shellArgs.add(
- aotSharedLibraryNameFlag
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
@@ -384,23 +475,17 @@ public class FlutterLoader {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
- ApplicationInfo applicationInfo =
- applicationContext
- .getPackageManager()
- .getApplicationInfo(
- applicationContext.getPackageName(), PackageManager.GET_META_DATA);
- Bundle metaData = applicationInfo.metaData;
- int oldGenHeapSizeMegaBytes =
- metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0;
- if (oldGenHeapSizeMegaBytes == 0) {
- // default to half of total memory.
+ if (!oldGenHeapSizeSet) {
+ // Default to half of total memory.
ActivityManager activityManager =
(ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memInfo);
- oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
+ int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
+ shellArgs.add(
+ FlutterEngineFlags.OLD_GEN_HEAP_SIZE.commandLineArgument
+ + String.valueOf(oldGenHeapSizeMegaBytes));
}
- shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
@@ -412,49 +497,10 @@ public class FlutterLoader {
shellArgs.add("--prefetched-default-font-manager");
- if (metaData != null) {
- if (metaData.containsKey(ENABLE_IMPELLER_META_DATA_KEY)) {
- if (metaData.getBoolean(ENABLE_IMPELLER_META_DATA_KEY)) {
- shellArgs.add("--enable-impeller=true");
- } else {
- shellArgs.add("--enable-impeller=false");
- }
- }
- if (metaData.getBoolean(ENABLE_VULKAN_VALIDATION_META_DATA_KEY, false)) {
- shellArgs.add("--enable-vulkan-validation");
- }
- if (metaData.getBoolean(IMPELLER_OPENGL_GPU_TRACING_DATA_KEY, false)) {
- shellArgs.add("--enable-opengl-gpu-tracing");
- }
- if (metaData.getBoolean(IMPELLER_VULKAN_GPU_TRACING_DATA_KEY, false)) {
- shellArgs.add("--enable-vulkan-gpu-tracing");
- }
- if (metaData.getBoolean(DISABLE_MERGED_PLATFORM_UI_THREAD_KEY, false)) {
- throw new IllegalArgumentException(
- DISABLE_MERGED_PLATFORM_UI_THREAD_KEY + " is no longer allowed.");
- }
- if (metaData.getBoolean(ENABLE_FLUTTER_GPU, false)) {
- shellArgs.add("--enable-flutter-gpu");
- }
- if (metaData.getBoolean(ENABLE_SURFACE_CONTROL, false)) {
- shellArgs.add("--enable-surface-control");
- }
-
- String backend = metaData.getString(IMPELLER_BACKEND_META_DATA_KEY);
- if (backend != null) {
- shellArgs.add("--impeller-backend=" + backend);
- }
- if (metaData.getBoolean(IMPELLER_LAZY_SHADER_MODE)) {
- shellArgs.add("--impeller-lazy-shader-mode");
- }
- if (metaData.getBoolean(IMPELLER_ANTIALIAS_LINES)) {
- shellArgs.add("--impeller-antialias-lines");
- }
+ if (!isLeakVMSet) {
+ shellArgs.add(FlutterEngineFlags.LEAK_VM.commandLineArgument + "true");
}
- final String leakVM = isLeakVM(metaData) ? "true" : "false";
- shellArgs.add("--leak-vm=" + leakVM);
-
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
flutterJNI.init(
@@ -474,9 +520,56 @@ public class FlutterLoader {
}
/**
- * Returns the AOT shared library name flag with the canonical path to the library that the engine
- * will use to load application's Dart code if it lives within a path we consider safe, which is a
- * path within the application's internal storage. Otherwise, returns null.
+ * Adds the AOT shared library name argument to the shell args if the provided path is safe.
+ *
+ * If the path is safe, it will be added to the beginning of the arguments list of arguments.
+ * The earlier specified path takes precedence over any later specified paths for the AOT shared
+ * library name argument.
+ */
+ private void maybeAddAotSharedLibraryNameArg(
+ @NonNull Context applicationContext,
+ @NonNull String aotSharedLibraryPath,
+ @NonNull List shellArgs) {
+ String safeAotSharedLibraryName = null;
+ try {
+ safeAotSharedLibraryName =
+ getSafeAotSharedLibraryName(applicationContext, aotSharedLibraryPath);
+ } catch (IOException exception) {
+ Log.w(
+ TAG,
+ "Error while validating AOT shared library name flag: " + aotSharedLibraryPath,
+ exception);
+ }
+
+ if (safeAotSharedLibraryName != null) {
+ shellArgs.add(
+ 0,
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument
+ + safeAotSharedLibraryName);
+ } else {
+ // If the library path is not safe, we will skip adding this argument.
+ Log.w(
+ TAG,
+ "Skipping unsafe AOT shared library name flag: "
+ + aotSharedLibraryPath
+ + ". Please ensure that the library is vetted and placed in your application's internal storage.");
+ }
+ }
+
+ /**
+ * Returns whether software rendering is enabled.
+ *
+ * {@link #ensureInitializationComplete} must be called first in order to retrieve this value.
+ * Otherwise, this will return false.
+ */
+ public boolean getSofwareRenderingEnabledViaManifest() {
+ return enableSoftwareRendering;
+ }
+
+ /**
+ * Returns the canonical path to the AOT shared library that the engine will use to load the
+ * application's Dart code if it lives within a path we consider safe, which is a path within the
+ * application's internal storage. Otherwise, returns null.
*
*
If the library lives within the application's internal storage, this means that the
* application developer either explicitly placed the library there or set the Android Gradle
@@ -484,17 +577,9 @@ public class FlutterLoader {
* https://developer.android.com/build/releases/past-releases/agp-4-2-0-release-notes#compress-native-libs-dsl
* for more information.
*/
- private String getSafeAotSharedLibraryNameFlag(
- @NonNull Context applicationContext, @NonNull String aotSharedLibraryNameArg)
+ private String getSafeAotSharedLibraryName(
+ @NonNull Context applicationContext, @NonNull String aotSharedLibraryPath)
throws IOException {
- // Isolate AOT shared library path.
- if (!aotSharedLibraryNameArg.startsWith(aotSharedLibraryNameFlag)) {
- throw new IllegalArgumentException(
- "AOT shared library name flag was not specified correctly; please use --aot-shared-library-name=.");
- }
- String aotSharedLibraryPath =
- aotSharedLibraryNameArg.substring(aotSharedLibraryNameFlag.length());
-
// Canocalize path for safety analysis.
File aotSharedLibraryFile = getFileFromPath(aotSharedLibraryPath);
@@ -519,7 +604,7 @@ public class FlutterLoader {
boolean isSoFile = aotSharedLibraryPathCanonicalPath.endsWith(".so");
if (livesWithinInternalStorage && isSoFile) {
- return aotSharedLibraryNameFlag + aotSharedLibraryPathCanonicalPath;
+ return aotSharedLibraryPathCanonicalPath;
}
// If the library does not live within the application's internal storage, we will not use it.
Log.e(
@@ -535,14 +620,6 @@ public class FlutterLoader {
return new File(path);
}
- private static boolean isLeakVM(@Nullable Bundle metaData) {
- final boolean leakVMDefaultValue = true;
- if (metaData == null) {
- return leakVMDefaultValue;
- }
- return metaData.getBoolean(LEAK_VM_META_DATA_KEY, leakVMDefaultValue);
- }
-
/**
* Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background
* thread, then invoking {@code callback} on the {@code callbackHandler}.
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
index ca2e40d1b96..253ecf5005f 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
@@ -40,7 +40,6 @@ import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.FlutterEngineGroupCache;
-import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
@@ -89,7 +88,6 @@ public class FlutterActivityAndFragmentDelegateTest {
mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class);
when(mockHost.getContext()).thenReturn(ctx);
when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
- when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
when(mockHost.getDartEntrypointFunctionName()).thenReturn("main");
when(mockHost.getDartEntrypointArgs()).thenReturn(null);
when(mockHost.getAppBundlePath()).thenReturn("/fake/path");
@@ -106,7 +104,6 @@ public class FlutterActivityAndFragmentDelegateTest {
mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class);
when(mockHost2.getContext()).thenReturn(ctx);
when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class));
- when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {}));
when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main");
when(mockHost2.getDartEntrypointArgs()).thenReturn(null);
when(mockHost2.getAppBundlePath()).thenReturn("/fake/path");
@@ -471,8 +468,6 @@ public class FlutterActivityAndFragmentDelegateTest {
activity -> {
when(customMockHost.getActivity()).thenReturn(activity);
when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class));
- when(customMockHost.getFlutterShellArgs())
- .thenReturn(new FlutterShellArgs(new String[] {}));
when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main");
when(customMockHost.getAppBundlePath()).thenReturn("/fake/path");
when(customMockHost.getInitialRoute()).thenReturn("/");
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java
index a9ac78f4fec..001a4a17aae 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java
@@ -302,6 +302,9 @@ public class FlutterAndroidComponentTest {
@NonNull
@Override
+ // Annotation required because support for setting engine shell arguments via Intent will be
+ // removed; see https://github.com/flutter/flutter/issues/180686.
+ @SuppressWarnings("deprecation")
public FlutterShellArgs getFlutterShellArgs() {
return new FlutterShellArgs(new String[] {});
}
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
index 091ca42931f..311d66beba1 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java
@@ -11,14 +11,19 @@ import static org.mockito.Mockito.*;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.android.ExclusiveAppComponent;
+import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
+import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.plugin.platform.PlatformViewsController2;
@@ -85,6 +90,19 @@ public class FlutterEngineConnectionRegistryTest {
when(flutterEngine.getPlatformViewsControllerDelegator())
.thenReturn(platformViewsControllerDelegator);
+ PackageManager packageManager = mock(PackageManager.class);
+ String packageName = "io.flutter.test";
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.metaData = new Bundle();
+ when(context.getPackageName()).thenReturn(packageName);
+ when(context.getPackageManager()).thenReturn(packageManager);
+ try {
+ when(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA))
+ .thenReturn(applicationInfo);
+ } catch (PackageManager.NameNotFoundException e) {
+ fail("Mocking application info threw an exception");
+ }
+
FlutterLoader flutterLoader = mock(FlutterLoader.class);
ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class);
@@ -127,41 +145,31 @@ public class FlutterEngineConnectionRegistryTest {
}
@Test
- public void softwareRendering() {
+ public void attachToActivityConfiguresSoftwareRendering() {
Context context = mock(Context.class);
-
FlutterEngine flutterEngine = mock(FlutterEngine.class);
PlatformViewsController platformViewsController = mock(PlatformViewsController.class);
- PlatformViewsController2 platformViewsController2 = mock(PlatformViewsController2.class);
+ FlutterLoader flutterLoader = mock(FlutterLoader.class);
+ ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class);
+ Activity activity = mock(Activity.class);
+ Lifecycle lifecycle = mock(Lifecycle.class);
+
+ when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
PlatformViewsControllerDelegator platformViewsControllerDelegator =
mock(PlatformViewsControllerDelegator.class);
when(flutterEngine.getPlatformViewsControllerDelegator())
.thenReturn(platformViewsControllerDelegator);
- when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController);
- when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2);
-
- FlutterLoader flutterLoader = mock(FlutterLoader.class);
-
- ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class);
- Activity activity = mock(Activity.class);
- when(appComponent.getAppComponent()).thenReturn(activity);
-
- // Test attachToActivity with an Activity that has no Intent.
+ when(flutterEngine.getDartExecutor()).thenReturn(mock(DartExecutor.class));
+ when(flutterEngine.getRenderer()).thenReturn(mock(FlutterRenderer.class));
FlutterEngineConnectionRegistry registry =
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
- registry.attachToActivity(appComponent, mock(Lifecycle.class));
- verify(platformViewsController).setSoftwareRendering(false);
- Intent intent = mock(Intent.class);
- when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(false);
- when(activity.getIntent()).thenReturn(intent);
+ when(flutterLoader.getSofwareRenderingEnabledViaManifest()).thenReturn(true);
+ when(appComponent.getAppComponent()).thenReturn(activity);
+ when(activity.getIntent()).thenReturn(mock(Intent.class));
- registry.attachToActivity(appComponent, mock(Lifecycle.class));
- verify(platformViewsController, times(2)).setSoftwareRendering(false);
+ registry.attachToActivity(appComponent, lifecycle);
- when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(true);
-
- registry.attachToActivity(appComponent, mock(Lifecycle.class));
verify(platformViewsController).setSoftwareRendering(true);
}
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java
new file mode 100644
index 00000000000..f5e111165ef
--- /dev/null
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java
@@ -0,0 +1,111 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import org.junit.Test;
+
+public class FlutterEngineFlagsTest {
+
+ @Test
+ public void allFlags_containsAllFlags() {
+ // Count the number of declared flags in FlutterEngineFlags.
+ int declaredFlagsCount = 0;
+ for (Field field : FlutterEngineFlags.class.getDeclaredFields()) {
+ if (FlutterEngineFlags.Flag.class.isAssignableFrom(field.getType())
+ && Modifier.isStatic(field.getModifiers())
+ && Modifier.isFinal(field.getModifiers())) {
+ declaredFlagsCount++;
+ }
+ }
+
+ // Check that the number of declared flags matches the size of ALL_FLAGS.
+ assertEquals(
+ "If you are adding a new Flag to FlutterEngineFlags, please make sure it is added to ALL_FLAGS as well. Otherwise, the flag will be silently ignored when specified.",
+ declaredFlagsCount,
+ FlutterEngineFlags.ALL_FLAGS.size());
+ }
+
+ // Annotation required because support for setting engine shell arguments via Intent will be
+ // removed; see https://github.com/flutter/flutter/issues/180686.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void allFlags_haveExpectedMetaDataNamePrefix() {
+ String defaultPrefix = "io.flutter.embedding.android.";
+ for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) {
+ // Test all non-deprecated flags that should have the default prefix.
+ if (!flag.equals(FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME)
+ && !flag.equals(FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR)) {
+ assertTrue(
+ "Flag " + flag.commandLineArgument + " does not have the correct metadata key prefix.",
+ flag.metadataKey.startsWith(defaultPrefix));
+ }
+ }
+ }
+
+ @Test
+ public void getFlagByCommandLineArgument_returnsExpectedFlagWhenValidArgumentSpecified() {
+ FlutterEngineFlags.Flag flag =
+ FlutterEngineFlags.getFlagByCommandLineArgument("--flutter-assets-dir=");
+ assertEquals(FlutterEngineFlags.FLUTTER_ASSETS_DIR, flag);
+ }
+
+ @Test
+ public void getFlagByCommandLineArgument_returnsNullWhenInvalidArgumentSpecified() {
+ assertNull(FlutterEngineFlags.getFlagFromIntentKey("--non-existent-flag"));
+ }
+
+ @Test
+ public void getFlagFromIntentKey_returnsExpectedFlagWhenValidKeySpecified() {
+ // Test flag without value.
+ FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagFromIntentKey("old-gen-heap-size");
+ assertEquals(FlutterEngineFlags.OLD_GEN_HEAP_SIZE, flag);
+
+ // Test with flag.
+ flag = FlutterEngineFlags.getFlagFromIntentKey("vm-snapshot-data");
+ assertEquals(FlutterEngineFlags.VM_SNAPSHOT_DATA, flag);
+ }
+
+ @Test
+ public void getFlagFromIntentKey_returnsNullWhenInvalidKeySpecified() {
+ assertNull(FlutterEngineFlags.getFlagFromIntentKey("non-existent-flag"));
+ }
+
+ @Test
+ public void isDisabled_returnsTrueWhenFlagIsDisabled() {
+ assertTrue(FlutterEngineFlags.isDisabled(FlutterEngineFlags.DISABLE_MERGED_PLATFORM_UI_THREAD));
+ }
+
+ @Test
+ public void isDisabled_returnsFalseWhenFlagIsNotDisabled() {
+ assertFalse(FlutterEngineFlags.isDisabled(FlutterEngineFlags.VM_SNAPSHOT_DATA));
+ }
+
+ // Deprecated flags are tested in this test.
+ @SuppressWarnings("deprecation")
+ @Test
+ public void getReplacementFlagIfDeprecated_returnsExpectedFlag() {
+ assertEquals(
+ FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME,
+ FlutterEngineFlags.getReplacementFlagIfDeprecated(
+ FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME));
+ assertEquals(
+ FlutterEngineFlags.FLUTTER_ASSETS_DIR,
+ FlutterEngineFlags.getReplacementFlagIfDeprecated(
+ FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR));
+ }
+
+ @Test
+ public void getReplacementFlagIfDeprecated_returnsNullWhenFlagIsNotDeprecated() {
+ assertNull(
+ FlutterEngineFlags.getReplacementFlagIfDeprecated(FlutterEngineFlags.VM_SNAPSHOT_DATA));
+ }
+}
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java
index 0f05dc67f67..9d1be0e26d7 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java
@@ -18,6 +18,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class FlutterShellArgsTest {
@Test
+ @SuppressWarnings("deprecation")
public void itProcessesShellFlags() {
// Setup the test.
Intent intent = new Intent();
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
index e594ce80057..20c64ea281d 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java
@@ -23,8 +23,8 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import io.flutter.embedding.engine.FlutterEngineFlags;
import io.flutter.embedding.engine.FlutterJNI;
-import io.flutter.embedding.engine.loader.ApplicationInfoLoader;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -129,8 +129,8 @@ public class PlayStoreDeferredComponentManagerTest {
TestFlutterJNI jni = new TestFlutterJNI();
Bundle bundle = new Bundle();
- bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so");
- bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets");
+ bundle.putString(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey, "custom_name.so");
+ bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
Context spyContext = createSpyContext(bundle);
doReturn(null).when(spyContext).getAssets();
@@ -162,7 +162,7 @@ public class PlayStoreDeferredComponentManagerTest {
Bundle bundle = new Bundle();
bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so");
- bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets");
+ bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
Context spyContext = createSpyContext(bundle);
doReturn(null).when(spyContext).getAssets();
@@ -194,7 +194,7 @@ public class PlayStoreDeferredComponentManagerTest {
Bundle bundle = new Bundle();
bundle.putString(
PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so,3:,4:");
- bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets");
+ bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
Context spyContext = createSpyContext(bundle);
doReturn(null).when(spyContext).getAssets();
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java
index 4a5f6183305..387781718ba 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java
@@ -23,6 +23,7 @@ import android.content.res.XmlResourceParser;
import android.os.Bundle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import io.flutter.embedding.engine.FlutterEngineFlags;
import java.io.StringReader;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -70,10 +71,10 @@ public class ApplicationInfoLoaderTest {
@Test
public void itGeneratesCorrectApplicationInfoWithCustomValues() throws Exception {
Bundle bundle = new Bundle();
- bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "testaot");
- bundle.putString(ApplicationInfoLoader.PUBLIC_VM_SNAPSHOT_DATA_KEY, "testvmsnapshot");
- bundle.putString(ApplicationInfoLoader.PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "testisolatesnapshot");
- bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "testassets");
+ bundle.putString(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey, "testaot");
+ bundle.putString(FlutterEngineFlags.VM_SNAPSHOT_DATA.metadataKey, "testvmsnapshot");
+ bundle.putString(FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.metadataKey, "testisolatesnapshot");
+ bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "testassets");
Context context = generateMockContext(bundle, null);
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
assertNotNull(info);
diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
index eff98068a1e..66dad8c83ff 100644
--- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
+++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
@@ -8,6 +8,7 @@ import static android.os.Looper.getMainLooper;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -203,35 +204,6 @@ public class FlutterLoaderTest {
assertTrue(arguments.contains(leakVMArg));
}
- @Test
- public void itSetsTheLeakVMFromMetaData() {
- FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
- FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
- Bundle metaData = new Bundle();
- metaData.putBoolean("io.flutter.embedding.android.LeakVM", false);
- ctx.getApplicationInfo().metaData = metaData;
-
- FlutterLoader.Settings settings = new FlutterLoader.Settings();
- assertFalse(flutterLoader.initialized());
- flutterLoader.startInitialization(ctx, settings);
- flutterLoader.ensureInitializationComplete(ctx, null);
- shadowOf(getMainLooper()).idle();
-
- final String leakVMArg = "--leak-vm=false";
- ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
- verify(mockFlutterJNI, times(1))
- .init(
- eq(ctx),
- shellArgsCaptor.capture(),
- anyString(),
- anyString(),
- anyString(),
- anyLong(),
- anyInt());
- List arguments = Arrays.asList(shellArgsCaptor.getValue());
- assertTrue(arguments.contains(leakVMArg));
- }
-
@Test
public void itUsesCorrectExecutorService() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
@@ -294,22 +266,32 @@ public class FlutterLoaderTest {
}
@Test
- public void itSetsEnableImpellerFromMetaData() {
+ public void itSetsDeprecatedAotSharedLibraryNameIfPathIsInInternalStorage() throws IOException {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
- FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
- Bundle metaData = new Bundle();
- metaData.putBoolean("io.flutter.embedding.android.EnableImpeller", true);
- ctx.getApplicationInfo().metaData = metaData;
+ FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI));
+ Context mockApplicationContext = mock(Context.class);
+ File internalStorageDir = ctx.getFilesDir();
+ Path internalStorageDirAsPathObj = internalStorageDir.toPath();
- FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ ctx.getApplicationInfo().nativeLibraryDir =
+ Paths.get("some", "path", "doesnt", "matter").toString();
assertFalse(flutterLoader.initialized());
- flutterLoader.startInitialization(ctx, settings);
- flutterLoader.ensureInitializationComplete(ctx, null);
- shadowOf(getMainLooper()).idle();
+ flutterLoader.startInitialization(ctx);
+
+ // Test paths for library living within internal storage.
+ String librarySoFileName = "library.so";
+ Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName);
+
+ String path = testPath.toString();
+ Bundle metadata = new Bundle();
+ metadata.putString(
+ "io.flutter.embedding.engine.loader.FlutterLoader.aot-shared-library-name", path);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ flutterLoader.ensureInitializationComplete(ctx, null);
- final String enableImpellerArg = "--enable-impeller=true";
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
- verify(mockFlutterJNI, times(1))
+ verify(mockFlutterJNI)
.init(
eq(ctx),
shellArgsCaptor.capture(),
@@ -318,27 +300,51 @@ public class FlutterLoaderTest {
anyString(),
anyLong(),
anyInt());
- List arguments = Arrays.asList(shellArgsCaptor.getValue());
- assertTrue(arguments.contains(enableImpellerArg));
+
+ List actualArgs = Arrays.asList(shellArgsCaptor.getValue());
+
+ // This check works because the tests run in debug mode. If run in release (or JIT release)
+ // mode, actualArgs would contain the default arguments for AOT shared library name on top
+ // of aotSharedLibraryNameArg.
+ String canonicalTestPath = testPath.toFile().getCanonicalPath();
+ String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath;
+ assertTrue(
+ "Args sent to FlutterJni.init incorrectly did not include path " + path,
+ actualArgs.contains(canonicalAotSharedLibraryNameArg));
+
+ // Reset FlutterLoader and mockFlutterJNI to make more calls to
+ // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing.
+ flutterLoader.initialized = false;
+ clearInvocations(mockFlutterJNI);
}
@Test
- public void itSetsEnableFlutterGPUFromMetaData() {
+ public void itSetsAotSharedLibraryNameIfPathIsInInternalStorageInReleaseMode()
+ throws IOException {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
- FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
- Bundle metaData = new Bundle();
- metaData.putBoolean("io.flutter.embedding.android.EnableFlutterGPU", true);
- ctx.getApplicationInfo().metaData = metaData;
+ FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI));
+ Context mockApplicationContext = mock(Context.class);
+ File internalStorageDir = ctx.getFilesDir();
+ Path internalStorageDirAsPathObj = internalStorageDir.toPath();
- FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ ctx.getApplicationInfo().nativeLibraryDir =
+ Paths.get("some", "path", "doesnt", "matter").toString();
assertFalse(flutterLoader.initialized());
- flutterLoader.startInitialization(ctx, settings);
- flutterLoader.ensureInitializationComplete(ctx, null);
- shadowOf(getMainLooper()).idle();
+ flutterLoader.startInitialization(ctx);
+
+ // Test paths for library living within internal storage.
+ String librarySoFileName = "library.so";
+ Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName);
+
+ String path = testPath.toString();
+ Bundle metadata = new Bundle();
+ metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ flutterLoader.ensureInitializationComplete(ctx, null, true);
- final String enableImpellerArg = "--enable-flutter-gpu";
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
- verify(mockFlutterJNI, times(1))
+ verify(mockFlutterJNI)
.init(
eq(ctx),
shellArgsCaptor.capture(),
@@ -347,66 +353,22 @@ public class FlutterLoaderTest {
anyString(),
anyLong(),
anyInt());
- List arguments = Arrays.asList(shellArgsCaptor.getValue());
- assertTrue(arguments.contains(enableImpellerArg));
- }
- @Test
- public void itSetsEnableSurfaceControlFromMetaData() {
- FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
- FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
- Bundle metaData = new Bundle();
- metaData.putBoolean("io.flutter.embedding.android.EnableSurfaceControl", true);
- ctx.getApplicationInfo().metaData = metaData;
+ List actualArgs = Arrays.asList(shellArgsCaptor.getValue());
- FlutterLoader.Settings settings = new FlutterLoader.Settings();
- assertFalse(flutterLoader.initialized());
- flutterLoader.startInitialization(ctx, settings);
- flutterLoader.ensureInitializationComplete(ctx, null);
- shadowOf(getMainLooper()).idle();
+ // This check works because the tests run in debug mode. If run in release (or JIT release)
+ // mode, actualArgs would contain the default arguments for AOT shared library name on top
+ // of aotSharedLibraryNameArg.
+ String canonicalTestPath = testPath.toFile().getCanonicalPath();
+ String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath;
+ assertTrue(
+ "Args sent to FlutterJni.init incorrectly did not include path " + path,
+ actualArgs.contains(canonicalAotSharedLibraryNameArg));
- final String disabledControlArg = "--enable-surface-control";
- ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
- verify(mockFlutterJNI, times(1))
- .init(
- eq(ctx),
- shellArgsCaptor.capture(),
- anyString(),
- anyString(),
- anyString(),
- anyLong(),
- anyInt());
- List arguments = Arrays.asList(shellArgsCaptor.getValue());
- assertTrue(arguments.contains(disabledControlArg));
- }
-
- @Test
- public void itSetsShaderInitModeFromMetaData() {
- FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
- FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
- Bundle metaData = new Bundle();
- metaData.putBoolean("io.flutter.embedding.android.ImpellerLazyShaderInitialization", true);
- ctx.getApplicationInfo().metaData = metaData;
-
- FlutterLoader.Settings settings = new FlutterLoader.Settings();
- assertFalse(flutterLoader.initialized());
- flutterLoader.startInitialization(ctx, settings);
- flutterLoader.ensureInitializationComplete(ctx, null);
- shadowOf(getMainLooper()).idle();
-
- final String shaderModeArg = "--impeller-lazy-shader-mode";
- ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
- verify(mockFlutterJNI, times(1))
- .init(
- eq(ctx),
- shellArgsCaptor.capture(),
- anyString(),
- anyString(),
- anyString(),
- anyLong(),
- anyInt());
- List arguments = Arrays.asList(shellArgsCaptor.getValue());
- assertTrue(arguments.contains(shaderModeArg));
+ // Reset FlutterLoader and mockFlutterJNI to make more calls to
+ // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing.
+ flutterLoader.initialized = false;
+ clearInvocations(mockFlutterJNI);
}
@Test
@@ -446,9 +408,11 @@ public class FlutterLoaderTest {
for (Path testPath : pathsToTest) {
String path = testPath.toString();
- String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path;
- String[] args = {aotSharedLibraryNameArg};
- flutterLoader.ensureInitializationComplete(ctx, args);
+ Bundle metadata = new Bundle();
+ metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ flutterLoader.ensureInitializationComplete(ctx, null);
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@@ -467,8 +431,7 @@ public class FlutterLoaderTest {
// mode, actualArgs would contain the default arguments for AOT shared library name on top
// of aotSharedLibraryNameArg.
String canonicalTestPath = testPath.toFile().getCanonicalPath();
- String canonicalAotSharedLibraryNameArg =
- FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath;
+ String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath;
assertTrue(
"Args sent to FlutterJni.init incorrectly did not include path " + path,
actualArgs.contains(canonicalAotSharedLibraryNameArg));
@@ -523,9 +486,11 @@ public class FlutterLoaderTest {
for (Path testPath : pathsToTest) {
String path = testPath.toString();
- String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path;
- String[] args = {aotSharedLibraryNameArg};
- flutterLoader.ensureInitializationComplete(ctx, args);
+ Bundle metadata = new Bundle();
+ metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ flutterLoader.ensureInitializationComplete(ctx, null);
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@@ -544,8 +509,7 @@ public class FlutterLoaderTest {
// mode, actualArgs would contain the default arguments for AOT shared library name on top
// of aotSharedLibraryNameArg.
String canonicalTestPath = testPath.toFile().getCanonicalPath();
- String canonicalAotSharedLibraryNameArg =
- FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath;
+ String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included canonical path " + canonicalTestPath,
actualArgs.contains(canonicalAotSharedLibraryNameArg));
@@ -572,8 +536,11 @@ public class FlutterLoaderTest {
String invalidFilePath = "my\0file.so";
- String[] args = {FlutterLoader.aotSharedLibraryNameFlag + invalidFilePath};
- flutterLoader.ensureInitializationComplete(ctx, args);
+ Bundle metadata = new Bundle();
+ metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", invalidFilePath);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ flutterLoader.ensureInitializationComplete(ctx, null);
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@@ -592,7 +559,7 @@ public class FlutterLoaderTest {
// mode, actualArgs would contain the default arguments for AOT shared library name on top
// of aotSharedLibraryNameArg.
for (String arg : actualArgs) {
- if (arg.startsWith(FlutterLoader.aotSharedLibraryNameFlag)) {
+ if (arg.startsWith("--aot-shared-library-name=")) {
fail();
}
}
@@ -620,9 +587,11 @@ public class FlutterLoaderTest {
when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile);
doReturn(realSoFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath();
- String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getPath();
- String[] args = {symlinkArg};
- flutterLoader.ensureInitializationComplete(ctx, args);
+ Bundle metadata = new Bundle();
+ metadata.putString(
+ "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getPath());
+ ctx.getApplicationInfo().metaData = metadata;
+ flutterLoader.ensureInitializationComplete(ctx, null);
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@@ -638,12 +607,14 @@ public class FlutterLoaderTest {
List actualArgs = Arrays.asList(shellArgsCaptor.getValue());
String canonicalSymlinkCanonicalizedPath = realSoFile.getCanonicalPath();
+ String aotSharedLibraryNameFlag = "--aot-shared-library-name=";
+ String symlinkAotSharedLibraryNameArg = aotSharedLibraryNameFlag + spySymlinkFile.getPath();
String canonicalAotSharedLibraryNameArg =
- FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
+ aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included absolute symlink path: "
+ spySymlinkFile.getAbsolutePath(),
- actualArgs.contains(symlinkArg));
+ actualArgs.contains(symlinkAotSharedLibraryNameArg));
assertTrue(
"Args sent to FlutterJni.init incorrectly did not include canonicalized path of symlink: "
+ canonicalSymlinkCanonicalizedPath,
@@ -674,15 +645,17 @@ public class FlutterLoaderTest {
List unsafeFiles = Arrays.asList(nonSoFile, fileJustOutsideInternalStorage);
Files.deleteIfExists(spySymlinkFile.toPath());
- String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath();
- String[] args = {symlinkArg};
+ Bundle metadata = new Bundle();
+ metadata.putString(
+ "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getAbsolutePath());
+ ctx.getApplicationInfo().metaData = metadata;
for (File unsafeFile : unsafeFiles) {
// Simulate a symlink since some filesystems do not support symlinks.
when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile);
doReturn(unsafeFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath();
- flutterLoader.ensureInitializationComplete(ctx, args);
+ flutterLoader.ensureInitializationComplete(ctx, null);
ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@@ -698,8 +671,11 @@ public class FlutterLoaderTest {
List actualArgs = Arrays.asList(shellArgsCaptor.getValue());
String canonicalSymlinkCanonicalizedPath = unsafeFile.getCanonicalPath();
+ String aotSharedLibraryNameFlag = "--aot-shared-library-name=";
+ String symlinkAotSharedLibraryNameArg =
+ aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath();
String canonicalAotSharedLibraryNameArg =
- FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
+ aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included canonicalized path of symlink: "
+ canonicalSymlinkCanonicalizedPath,
@@ -707,7 +683,7 @@ public class FlutterLoaderTest {
assertFalse(
"Args sent to FlutterJni.init incorrectly included absolute path of symlink: "
+ spySymlinkFile.getAbsolutePath(),
- actualArgs.contains(symlinkArg));
+ actualArgs.contains(symlinkAotSharedLibraryNameArg));
// Clean up created files.
spySymlinkFile.delete();
@@ -719,4 +695,569 @@ public class FlutterLoaderTest {
clearInvocations(mockFlutterJNI);
}
}
-}
\ No newline at end of file
+
+ @Test
+ public void itSetsEnableSoftwareRenderingFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.EnableSoftwareRendering",
+ null,
+ "--enable-software-rendering");
+ }
+
+ @Test
+ public void getSofwareRenderingEnabledViaManifest_returnsExpectedValueWhenSetViaManifest() {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+ Bundle metadata = new Bundle();
+
+ metadata.putBoolean("io.flutter.embedding.android.EnableSoftwareRendering", true);
+
+ ctx.getApplicationInfo().metaData = metadata;
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+ flutterLoader.ensureInitializationComplete(ctx, null);
+ shadowOf(getMainLooper()).idle();
+
+ assertTrue(flutterLoader.getSofwareRenderingEnabledViaManifest());
+ }
+
+ @Test
+ public void itSetsSkiaDeterministicRenderingFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.SkiaDeterministicRendering",
+ null,
+ "--skia-deterministic-rendering");
+ }
+
+ @Test
+ public void itSetsFlutterAssetsDirFromMetadata() {
+ String expectedAssetsDir = "flutter_assets_dir";
+ // Test debug mode
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.FlutterAssetsDir",
+ expectedAssetsDir,
+ "--flutter-assets-dir=" + expectedAssetsDir);
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.FlutterAssetsDir",
+ expectedAssetsDir,
+ "--flutter-assets-dir=" + expectedAssetsDir);
+ }
+
+ @Test
+ public void itSetsDeprecatedFlutterAssetsDirFromMetadata() {
+ String expectedAssetsDir = "flutter_assets_dir";
+
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir",
+ expectedAssetsDir,
+ "--flutter-assets-dir=" + expectedAssetsDir);
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir",
+ expectedAssetsDir,
+ "--flutter-assets-dir=" + expectedAssetsDir);
+ }
+
+ @Test
+ public void itSetsOldGenHeapSizeFromMetadata() {
+ // Test old gen heap size can be set from metadata in debug mode.
+ int expectedOldGenHeapSize = 256;
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.OldGenHeapSize",
+ expectedOldGenHeapSize,
+ "--old-gen-heap-size=" + expectedOldGenHeapSize);
+
+ // Test old gen heap size can be set from metadta in release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.OldGenHeapSize",
+ expectedOldGenHeapSize,
+ "--old-gen-heap-size=" + expectedOldGenHeapSize);
+
+ // Test that default old gen heap size will not be included if it
+ // is configured via the manifest.
+ ActivityManager activityManager =
+ (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ activityManager.getMemoryInfo(memInfo);
+ int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
+ testFlagFromMetadataNotPresent(
+ "io.flutter.embedding.android.OldGenHeapSize",
+ expectedOldGenHeapSize,
+ "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
+ }
+
+ @Test
+ public void itSetsToggleImpellerFromMetadata() {
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.ToggleImpeller", true, "--enable-impeller=true");
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.ToggleImpeller", true, "--enable-impeller=true");
+ }
+
+ @Test
+ public void itSetsEnableImpellerFromMetadata() {
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.EnableImpeller", null, "--enable-impeller");
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.EnableImpeller", null, "--enable-impeller");
+ }
+
+ @Test
+ public void itSetsImpellerBackendFromMetadata() {
+ String expectedImpellerBackend = "Vulkan";
+
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.ImpellerBackend",
+ expectedImpellerBackend,
+ "--impeller-backend=" + expectedImpellerBackend);
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.ImpellerBackend",
+ expectedImpellerBackend,
+ "--impeller-backend=" + expectedImpellerBackend);
+ }
+
+ @Test
+ public void itSetsVmSnapshotDataFromMetadata() {
+ String expectedVmSnapshotData = "vm_snapshot_data";
+
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.VmSnapshotData",
+ expectedVmSnapshotData,
+ "--vm-snapshot-data=" + expectedVmSnapshotData);
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.VmSnapshotData",
+ expectedVmSnapshotData,
+ "--vm-snapshot-data=" + expectedVmSnapshotData);
+ }
+
+ @Test
+ public void itSetsIsolateSnapshotDataFromMetadata() {
+ String expectedIsolateSnapshotData = "isolate_snapshot_data";
+
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.IsolateSnapshotData",
+ expectedIsolateSnapshotData,
+ "--isolate-snapshot-data=" + expectedIsolateSnapshotData);
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.IsolateSnapshotData",
+ expectedIsolateSnapshotData,
+ "--isolate-snapshot-data=" + expectedIsolateSnapshotData);
+ }
+
+ @Test
+ public void itSetsUseTestFontsFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.UseTestFonts", null, "--use-test-fonts");
+ }
+
+ @Test
+ public void itSetsVmServicePortFromMetadata() {
+ int expectedVmServicePort = 12345;
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.VMServicePort",
+ expectedVmServicePort,
+ "--vm-service-port=" + expectedVmServicePort);
+ }
+
+ @Test
+ public void itSetsEnableVulkanValidationFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.EnableVulkanValidation", null, "--enable-vulkan-validation");
+ }
+
+ @Test
+ public void itSetsLeakVMFromMetadata() {
+ // Test that LeakVM can be set via manifest.
+ testFlagFromMetadataPresent("io.flutter.embedding.android.LeakVM", false, "--leak-vm=false");
+
+ // Test that default LeakVM will not be included if it is configured via the manifest.
+ testFlagFromMetadataNotPresent("io.flutter.embedding.android.LeakVM", false, "--leak-vm=true");
+ }
+
+ @Test
+ public void itSetsTraceStartupFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.TraceStartup", null, "--trace-startup");
+ }
+
+ @Test
+ public void itSetsStartPausedFromMetadata() {
+ testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", null, "--start-paused");
+ }
+
+ @Test
+ public void itSetsDisableServiceAuthCodesFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.DisableServiceAuthCodes",
+ null,
+ "--disable-service-auth-codes");
+ }
+
+ @Test
+ public void itSetsEndlessTraceBufferFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.EndlessTraceBuffer", null, "--endless-trace-buffer");
+ }
+
+ @Test
+ public void itSetsEnableDartProfilingFromMetadata() {
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling");
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling");
+ }
+
+ @Test
+ public void itSetsProfileStartupFromMetadata() {
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup");
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup");
+ }
+
+ @Test
+ public void itSetsMergedPlatformUiThread() {
+ // Test debug mode.
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread");
+
+ // Test release mode.
+ testFlagFromMetadataPresentInReleaseMode(
+ "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread");
+ }
+
+ @Test
+ public void itSetsTraceSkiaFromMetadata() {
+ testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", null, "--trace-skia");
+ }
+
+ @Test
+ public void itSetsTraceSkiaAllowlistFromMetadata() {
+ String expectedTraceSkiaAllowList = "allowed1,allowed2,allowed3";
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.TraceSkiaAllowList",
+ expectedTraceSkiaAllowList,
+ "--trace-skia-allowlist=" + expectedTraceSkiaAllowList);
+ }
+
+ @Test
+ public void itSetsTraceSystraceFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.TraceSystrace", null, "--trace-systrace");
+ }
+
+ @Test
+ public void itSetsTraceToFileFromMetadata() {
+ String expectedTraceToFilePath = "/path/to/trace/file";
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.TraceToFile",
+ expectedTraceToFilePath,
+ "--trace-to-file=" + expectedTraceToFilePath);
+ }
+
+ @Test
+ public void itSetsProfileMicrotasksFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.ProfileMicrotasks", null, "--profile-microtasks");
+ }
+
+ @Test
+ public void itSetsDumpSkpOnShaderCompilationFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.DumpSkpOnShaderCompilation",
+ null,
+ "--dump-skp-on-shader-compilation");
+ }
+
+ @Test
+ public void itSetsPurgePersistentCacheFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.PurgePersistentCache", null, "--purge-persistent-cache");
+ }
+
+ @Test
+ public void itSetsVerboseLoggingFromMetadata() {
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.VerboseLogging", null, "--verbose-logging");
+ }
+
+ @Test
+ public void itSetsDartFlagsFromMetadata() {
+ String expectedDartFlags = "--enable-asserts --enable-vm-service";
+ testFlagFromMetadataPresent(
+ "io.flutter.embedding.android.DartFlags",
+ expectedDartFlags,
+ "--dart-flags=" + expectedDartFlags);
+ }
+
+ @Test
+ public void itDoesNotSetTestFlagFromMetadata() {
+ testFlagFromMetadataNotPresent("io.flutter.embedding.android.TestFlag", null, "--test-flag");
+ }
+
+ @Test
+ public void itDoesNotSetDisableMergedPlatformUIThreadFromMetadata() {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+ Bundle metadata = new Bundle();
+
+ metadata.putBoolean("io.flutter.embedding.android.DisableMergedPlatformUIThread", true);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+
+ // Verify that an IllegalArgumentException is thrown when DisableMergedPlatformUIThread is set,
+ // as it is no longer supported.
+ Exception exception =
+ assertThrows(
+ RuntimeException.class, () -> flutterLoader.ensureInitializationComplete(ctx, null));
+ Throwable cause = exception.getCause();
+
+ assertNotNull(cause);
+ assertTrue(
+ "Expected cause to be IllegalArgumentException", cause instanceof IllegalArgumentException);
+ assertTrue(
+ cause
+ .getMessage()
+ .contains(
+ "io.flutter.embedding.android.DisableMergedPlatformUIThread is disabled and no longer allowed."));
+ }
+
+ @Test
+ public void itDoesSetRecognizedCommandLineArgument() {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+ Bundle metadata = new Bundle();
+
+ String[] recognizedArg = {"--enable-impeller=true"};
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+ flutterLoader.ensureInitializationComplete(ctx, recognizedArg);
+ shadowOf(getMainLooper()).idle();
+
+ ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
+ verify(mockFlutterJNI, times(1))
+ .init(
+ eq(ctx),
+ shellArgsCaptor.capture(),
+ anyString(),
+ anyString(),
+ anyString(),
+ anyLong(),
+ anyInt());
+ List arguments = Arrays.asList(shellArgsCaptor.getValue());
+
+ assertTrue(
+ "Recognized argument '"
+ + recognizedArg[0]
+ + "' was not found in the arguments passed to FlutterJNI.init",
+ arguments.contains(recognizedArg[0]));
+ }
+
+ @Test
+ public void ifFlagSetViaManifestAndCommandLineThenCommandLineTakesPrecedence() {
+ String expectedImpellerArgFromMetadata = "--enable-impeller=true";
+ String expectedImpellerArgFromCommandLine = "--enable-impeller=false";
+
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+ Bundle metadata = new Bundle();
+
+ // Place metadata key and value into the metadata bundle used to mock the manifest.
+ metadata.putBoolean("io.flutter.embedding.android.EnableImpeller", true);
+ ctx.getApplicationInfo().metaData = metadata;
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+ flutterLoader.ensureInitializationComplete(
+ ctx, new String[] {expectedImpellerArgFromCommandLine});
+ shadowOf(getMainLooper()).idle();
+
+ ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
+ verify(mockFlutterJNI, times(1))
+ .init(
+ eq(ctx),
+ shellArgsCaptor.capture(),
+ anyString(),
+ anyString(),
+ anyString(),
+ anyLong(),
+ anyInt());
+ List arguments = Arrays.asList(shellArgsCaptor.getValue());
+
+ // Verify that the command line argument takes precedence over the manifest metadata.
+ assertTrue(
+ arguments.indexOf(expectedImpellerArgFromMetadata)
+ < arguments.indexOf(expectedImpellerArgFromCommandLine));
+ }
+
+ @Test
+ public void ifAOTSharedLibraryNameSetViaManifestAndCommandLineThenCommandLineTakesPrecedence()
+ throws IOException {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI));
+ File internalStorageDir = ctx.getFilesDir();
+ Path internalStorageDirAsPathObj = internalStorageDir.toPath();
+
+ ctx.getApplicationInfo().nativeLibraryDir =
+ Paths.get("some", "path", "doesnt", "matter").toString();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx);
+
+ // Test paths for library living within internal storage.
+ Path pathWithDirectInternalStoragePath1 = internalStorageDirAsPathObj.resolve("library1.so");
+ Path pathWithDirectInternalStoragePath2 = internalStorageDirAsPathObj.resolve("library2.so");
+
+ String expectedAotSharedLibraryNameFromMetadata =
+ "--aot-shared-library-name="
+ + pathWithDirectInternalStoragePath1.toFile().getCanonicalPath();
+ String expectedAotSharedLibraryNameFromCommandLine =
+ "--aot-shared-library-name="
+ + pathWithDirectInternalStoragePath2.toFile().getCanonicalPath();
+
+ Bundle metadata = new Bundle();
+
+ // Place metadata key and value into the metadata bundle used to mock the manifest.
+ metadata.putString(
+ "io.flutter.embedding.android.AOTSharedLibraryName",
+ pathWithDirectInternalStoragePath1.toFile().getCanonicalPath());
+ ctx.getApplicationInfo().metaData = metadata;
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+ flutterLoader.ensureInitializationComplete(
+ ctx,
+ new String[] {expectedAotSharedLibraryNameFromCommandLine, "--enable-opengl-gpu-tracing"});
+ shadowOf(getMainLooper()).idle();
+
+ ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
+ verify(mockFlutterJNI, times(1))
+ .init(
+ eq(ctx),
+ shellArgsCaptor.capture(),
+ anyString(),
+ anyString(),
+ anyString(),
+ anyLong(),
+ anyInt());
+ List arguments = Arrays.asList(shellArgsCaptor.getValue());
+
+ // Verify that the command line argument takes precedence over the manifest metadata.
+ assertTrue(
+ arguments.indexOf(expectedAotSharedLibraryNameFromCommandLine)
+ < arguments.indexOf(expectedAotSharedLibraryNameFromMetadata));
+
+ // Verify other command line arguments are still passed through.
+ assertTrue(
+ "Expected argument --enable-opengl-gpu-tracing was not found in the arguments passed to FlutterJNI.init",
+ arguments.contains("--enable-opengl-gpu-tracing"));
+ }
+
+ private void testFlagFromMetadataPresentInReleaseMode(
+ String metadataKey, Object metadataValue, String expectedArg) {
+ testFlagFromMetadata(metadataKey, metadataValue, expectedArg, true, true);
+ }
+
+ private void testFlagFromMetadataNotPresent(
+ String metadataKey, Object metadataValue, String expectedArg) {
+ testFlagFromMetadata(metadataKey, metadataValue, expectedArg, false, false);
+ }
+
+ private void testFlagFromMetadataPresent(
+ String metadataKey, Object metadataValue, String expectedArg) {
+ testFlagFromMetadata(metadataKey, metadataValue, expectedArg, true, false);
+ }
+
+ // Test that specified shell argument can be set via manifest metadata as expected.
+ private void testFlagFromMetadata(
+ String metadataKey,
+ Object metadataValue,
+ String expectedArg,
+ boolean shouldBeSet,
+ boolean isReleaseMode) {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+ Bundle metadata = new Bundle();
+
+ // Place metadata key and value into the metadata bundle used to mock the manifest.
+ if (metadataValue == null) {
+ metadata.putString(metadataKey, null);
+ } else if (metadataValue instanceof Boolean) {
+ metadata.putBoolean(metadataKey, (Boolean) metadataValue);
+ } else if (metadataValue instanceof Integer) {
+ metadata.putInt(metadataKey, (Integer) metadataValue);
+ } else if (metadataValue instanceof String) {
+ metadata.putString(metadataKey, (String) metadataValue);
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported metadataValue type: " + metadataValue.getClass());
+ }
+
+ ctx.getApplicationInfo().metaData = metadata;
+
+ FlutterLoader.Settings settings = new FlutterLoader.Settings();
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(ctx, settings);
+ flutterLoader.ensureInitializationComplete(ctx, null, isReleaseMode);
+ shadowOf(getMainLooper()).idle();
+
+ ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
+ verify(mockFlutterJNI, times(1))
+ .init(
+ eq(ctx),
+ shellArgsCaptor.capture(),
+ anyString(),
+ anyString(),
+ anyString(),
+ anyLong(),
+ anyInt());
+ List arguments = Arrays.asList(shellArgsCaptor.getValue());
+
+ if (shouldBeSet) {
+ assertTrue(
+ "Expected argument '"
+ + expectedArg
+ + "' was not found in the arguments passed to FlutterJNI.init",
+ arguments.contains(expectedArg));
+ } else {
+ assertFalse(
+ "Unexpected argument '"
+ + expectedArg
+ + "' was found in the arguments passed to FlutterJNI.init",
+ arguments.contains(expectedArg));
+ }
+ }
+}
diff --git a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java
index 3c53005f354..73fd7e33fa0 100644
--- a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java
+++ b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java
@@ -15,11 +15,14 @@ import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
+import io.flutter.Log;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BasicMessageChannel.MessageHandler;
import io.flutter.plugin.common.BasicMessageChannel.Reply;
import io.flutter.plugin.common.StringCodec;
+import java.util.Arrays;
import java.util.ArrayList;
+import java.util.List;
public class MainActivity extends AppCompatActivity {
private static FlutterEngine flutterEngine;
@@ -31,34 +34,28 @@ public class MainActivity extends AppCompatActivity {
private static final String PING = "ping";
private BasicMessageChannel messageChannel;
- private String[] getArgsFromIntent(Intent intent) {
- // Before adding more entries to this list, consider that arbitrary
- // Android applications can generate intents with extra data and that
- // there are many security-sensitive args in the binary.
- ArrayList args = new ArrayList<>();
- if (intent.getBooleanExtra("trace-startup", false)) {
- args.add("--trace-startup");
+ // Previously, this example checked for certain flags set via Intent. Engine
+ // flags can no longer be set via Intent, so warn developers that Intent extras
+ // will be ignored and point to alternative methods for setting engine flags.
+ private void warnIfEngineFlagsSetViaIntent(Intent intent) {
+ List previouslySupportedFlagsViaIntent = Arrays.asList(
+ "trace-startup", "start-paused", "enable-dart-profiling");
+ for (String flag : previouslySupportedFlagsViaIntent) {
+ if (intent.hasExtra(flag)) {
+ Log.w("MainActivity", "Engine flags can no longer be set via Intent on Android. If you wish to set " + flag + ", see https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for alternative methods.");
+ break;
+ }
}
- if (intent.getBooleanExtra("start-paused", false)) {
- args.add("--start-paused");
- }
- if (intent.getBooleanExtra("enable-dart-profiling", false)) {
- args.add("--enable-dart-profiling");
- }
- if (!args.isEmpty()) {
- String[] argsArray = new String[args.size()];
- return args.toArray(argsArray);
- }
- return null;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String[] args = getArgsFromIntent(getIntent());
+ warnIfEngineFlagsSetViaIntent(getIntent());
+
if (flutterEngine == null) {
- flutterEngine = new FlutterEngine(this, args);
+ flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 612388310ff..90d012a3c3c 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -674,7 +674,11 @@ class AndroidDevice extends Device {
if (debuggingOptions.debuggingEnabled) ...[
if (debuggingOptions.buildInfo.isDebug) ...[
...['--ez', 'enable-checked-mode', 'true'],
- ...['--ez', 'verify-entry-points', 'true'],
+ ...[
+ '--ez',
+ 'verify-entry-points',
+ 'true',
+ ], // TODO(camsim99): check if this is even used
],
if (debuggingOptions.startPaused) ...['--ez', 'start-paused', 'true'],
if (debuggingOptions.disableServiceAuthCodes) ...[