Merge ff053d38bf785a36c316c7b8fecfe2feee621cd7 into 06df71c51446e96939c6a615b7c34ce9123806ba

This commit is contained in:
Camille Simon 2026-02-19 16:47:10 +01:00 committed by GitHub
commit 091c812adb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1783 additions and 362 deletions

View File

@ -0,0 +1,190 @@
# Setting Flutter Android engine flags
You can set flags for the Flutter engine on Android in two different ways:
- From the command line when launching an app with the Flutter tool
- Via `AndroidManifest.xml` metadata (static, per-build configuration)
All flags available on Android can be set via the command line **and** via
manifest metadata. See `src/flutter/shell/common/switches.cc` for
the list of all supported flags, and see
`src/flutter/shell/platform/android/io/flutter/embedding/engine/`
`FlutterEngineFlags.java` for the list of flags that can be set for the
Android shell.
## When to use manifest metadata versus the command line
Use the manifest when:
- You want a fixed, reproducible baseline of engine flags
for your app across all launches. This is ideal for CI and for enforcing a
consistent configuration for your app.
- You want to vary flags by build mode or product flavor
via manifest merging. For example, place metadata in
`src/debug/AndroidManifest.xml`, `src/profile/AndroidManifest.xml`, and
`src/release/AndroidManifest.xml` (or per-flavor manifests) to tailor flags
per variant.
Use the command line when:
- You want to quickly experiment with a flag for a single run of your app.
- You need to override a flag that is already set in the manifest temporarily for debugging
or testing purposes.
**Note: If a flag is specified both on the command line and in the manifest,
the command-line value takes precedence at runtime.**
See below for details on using each method.
## How to set engine flags from the command line
When you run a standalone Flutter app with the Flutter tool, engine flags
can be passed directly and are forwarded to the Android engine. Examples:
```bash
flutter run --trace-startup \
--enable-software-rendering \
--dart-flags="--enable-asserts"
```
Notes:
- Flags that take values use the `--flag=value` form (with `=`). The Flutter
tool forwards them in that form to the Android embedding.
## How to set engine flags in the manifest
All manifest metadata keys must be prefixed with the package name
`io.flutter.embedding.android` and are suffixed with the metadata name for the
related command line flag as determined in
`src/flutter/shell/platform/android/io/flutter/embedding/engine/`
`FlutterEngineFlags.java`. For example, the `--impeller-lazy-shader-mode=`
command line flag corresponds to the metadata key
`io.flutter.embedding.android.ImpellerLazyShaderInitialization`.
For flags that take values, set the numeric, string, or boolean value (without
the leading `--flag=` prefix).
### Examples
Set the `--trace-to-file=` flag to `some_file.txt`:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application ...>
<meta-data
android:name="io.flutter.embedding.android.TraceToFile"
android:value="some_file.txt"/>
...
</application>
</manifest>
```
Set the `--enable-flutter-gpu` flag:
```xml
<meta-data
android:name="io.flutter.embedding.android.EnableFlutterGPU"
/>
```
## Release-mode restrictions
- Some flags are not allowed in release mode. The Android embedding enforces
this policy (see `src/flutter/shell/platform/android/io/flutter/
embedding/engine/FlutterEngineFlags`, which marks allowed flags
with `allowedInRelease`). If a disallowed flag is set in release, it will
be ignored.
- If you need different behavior in release vs debug/profile mode, configure it
via variant-specific manifests or product flavors.
## How to set engine flags dynamically
As of the writing of this document, setting Flutter shell arguments via an
Android `Intent` is no longer supported. If you need per-launch or
runtime-controlled flags in an add-to-app integration, you may do so
programatically before engine initialization.
To do that, supply engine arguments directly to a `FlutterEngine` with the
desired flags from the earliest point you can control in your
application. For example, if you are writing an add-to-app app that launches
a `FlutterActivity` or `FlutterFragment`, then you can cache a
`FlutterEngine` that is initialized with your desired
engine flags:
```kotlin
// Your native Android application
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// Initialize the Flutter engine with desired flags
val args = arrayOf(
"--trace-startup",
"--trace-to-file=some_file.txt",
"--enable-software-rendering"
)
val flutterEngine = FlutterEngine(this, args)
// Start executing Dart code in the FlutterEngine
flutterEngine.dartExecutor.executeDartEntrypoint(
DartEntrypoint.createDefault()
)
// Store the engine in the cache for later use
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
}
}
```
Then, your `Activity` can launch a `FlutterActivity` or `FlutterFragment`
with that cached `FlutterEngine`:
```kotlin
// Start a FlutterActivity using the cached engine...
val intent = FlutterActivity.withCachedEngine("my_engine_id").build(this)
startActivity(intent)
// Or launch a FlutterFragment using the cached engine
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").build()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, flutterFragment, TAG_FLUTTER_FRAGMENT)
.commit()
```
For a normal Flutter Android app, you can create and initialize a `FlutterEngine`
with your desired flags the same as in the example above, then override
`provideFlutterEngine` in your app's `FlutterActivity` to provide the
configured `FlutterEngine`. For example:
```kotlin
// Your Flutter Android application
class MyApplication : FlutterApplication() {
override fun onCreate() {
super.onCreate()
val args = arrayOf(
"--trace-startup",
"--trace-to-file=some_file.txt",
"--enable-software-rendering"
)
val flutterEngine = FlutterEngine(this, args)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
.getInstance()
.put(MY_ENGINE_ID, flutterEngine)
}
}
// Your Flutter Android Activity
class MainActivity: FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return FlutterEngineCache
.getInstance()
.get(MyApplication.MY_ENGINE_ID)
}
}
```

View File

@ -250,6 +250,7 @@ android_java_sources = [
"io/flutter/embedding/engine/FlutterEngine.java",
"io/flutter/embedding/engine/FlutterEngineCache.java",
"io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java",
"io/flutter/embedding/engine/FlutterEngineFlags.java",
"io/flutter/embedding/engine/FlutterEngineGroup.java",
"io/flutter/embedding/engine/FlutterEngineGroupCache.java",
"io/flutter/embedding/engine/FlutterJNI.java",

View File

@ -29,6 +29,7 @@ import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterEngineFlags;
import io.flutter.embedding.engine.FlutterEngineGroup;
import io.flutter.embedding.engine.FlutterEngineGroupCache;
import io.flutter.embedding.engine.FlutterShellArgs;
@ -38,6 +39,7 @@ import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.plugin.view.SensitiveContentPlugin;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* Delegate that implements all Flutter logic that is the same between a {@link FlutterActivity} and
@ -331,6 +333,7 @@ import java.util.List;
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
warnIfEngineFlagsSetViaIntent(host.getActivity().getIntent());
FlutterEngineGroup group =
engineGroup == null
? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray())
@ -344,6 +347,30 @@ import java.util.List;
isFlutterEngineFromHost = false;
}
// As part of https://github.com/flutter/flutter/issues/180686, the ability
// to set engine flags via Intent extras is planned to be removed, so warn
// developers that engine shell arguments set that way will be ignored.
private void warnIfEngineFlagsSetViaIntent(@NonNull Intent intent) {
if (intent.getExtras() == null) {
return;
}
Bundle extras = intent.getExtras();
Set<String> 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();

View File

@ -177,8 +177,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
* native library and start a Dart VM.
*
* <p>In order to pass Dart VM initialization arguments (see {@link
* io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
* initialization arguments by calling {@link
* io.flutter.embedding.engine.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.

View File

@ -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

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>{@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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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}.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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<Flag> 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<Flag> DISABLED_FLAGS =
Collections.unmodifiableList(Arrays.asList(DISABLE_MERGED_PLATFORM_UI_THREAD));
// Lookup map for current flags that replace deprecated ones.
private static final Map<Flag, Flag> DEPRECATED_FLAGS_BY_REPLACEMENT =
new HashMap<Flag, Flag>() {
{
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<String, Flag> FLAG_BY_COMMAND_LINE_ARG;
// Lookup map for retrieving the Flag corresponding to a specific metadata key.
private static final Map<String, Flag> FLAG_BY_META_DATA_KEY;
static {
Map<String, Flag> map = new HashMap<String, Flag>(ALL_FLAGS.size());
Map<String, Flag> metaMap = new HashMap<String, Flag>(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.
*
* <p>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);
}
}

View File

@ -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.
*
* <p>Arguments that can be delivered to the Flutter shell when it is created.
*
* <p>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";

View File

@ -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));

View File

@ -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 <application /> in AndroidManifest.xml. Set it to true in to leave the Dart VM,
* set it to false to destroy VM.
*
* <p>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.
*
* <p>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.
*
* <p>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<String> 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.
*
* <p>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<String> 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.
*
* <p>{@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.
*
* <p>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=<path>.");
}
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}.

View File

@ -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("/");

View File

@ -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[] {});
}

View File

@ -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<Activity> 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);
}

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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);

View File

@ -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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> 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<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(enableImpellerArg));
List<String> 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<String[]> 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<String> 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<String> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> 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<String[]> 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<String[]> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -638,12 +607,14 @@ public class FlutterLoaderTest {
List<String> 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<File> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -698,8 +671,11 @@ public class FlutterLoaderTest {
List<String> 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);
}
}
}
@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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
anyString(),
anyString(),
anyString(),
anyLong(),
anyInt());
List<String> 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));
}
}
}

View File

@ -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<String> 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<String> 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<String> 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()
);

View File

@ -674,7 +674,11 @@ class AndroidDevice extends Device {
if (debuggingOptions.debuggingEnabled) ...<String>[
if (debuggingOptions.buildInfo.isDebug) ...<String>[
...<String>['--ez', 'enable-checked-mode', 'true'],
...<String>['--ez', 'verify-entry-points', 'true'],
...<String>[
'--ez',
'verify-entry-points',
'true',
], // TODO(camsim99): check if this is even used
],
if (debuggingOptions.startPaused) ...<String>['--ez', 'start-paused', 'true'],
if (debuggingOptions.disableServiceAuthCodes) ...<String>[