Revert "[Android] Add mechanism for setting Android engine flags via … (#182388)

Reverts https://github.com/flutter/flutter/pull/181632 due to internal
performance regressions of startup time in the order of 100ms.

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
Camille Simon 2026-02-17 07:39:30 -08:00 committed by GitHub
parent 9fa7f81be0
commit 9292bb3a66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 583 additions and 2033 deletions

View File

@ -1,190 +0,0 @@
# 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/`
`FlutterShellArgs.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/`
`FlutterShellArgs.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 `--old-gen-heap-size=` flag to 322 MB:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application ...>
<meta-data
android:name="io.flutter.embedding.android.OldGenHeapSize"
android:value="322"/>
...
</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/FlutterShellArgs`, 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",
"--old-gen-heap-size=256",
"--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",
"--old-gen-heap-size=256",
"--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

@ -255,7 +255,6 @@ android_java_sources = [
"io/flutter/embedding/engine/FlutterJNI.java",
"io/flutter/embedding/engine/FlutterOverlaySurface.java",
"io/flutter/embedding/engine/FlutterShellArgs.java",
"io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java",
"io/flutter/embedding/engine/dart/DartExecutor.java",
"io/flutter/embedding/engine/dart/DartMessenger.java",
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",

View File

@ -49,7 +49,7 @@ import androidx.lifecycle.LifecycleRegistry;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgsIntentUtils;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.plugin.platform.PlatformPlugin;
@ -1042,8 +1042,8 @@ public class FlutterActivity extends Activity
*/
@NonNull
@Override
public String[] getFlutterShellArgs() {
return FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(getIntent());
public FlutterShellArgs getFlutterShellArgs() {
return FlutterShellArgs.fromIntent(getIntent());
}
/**

View File

@ -38,7 +38,6 @@ 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
@ -332,12 +331,9 @@ import java.util.Set;
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
warnIfEngineFlagsSetViaIntent(host.getActivity().getIntent());
String[] flutterShellArgs =
host.getFlutterShellArgs() == null ? new String[0] : host.getFlutterShellArgs();
FlutterEngineGroup group =
engineGroup == null
? new FlutterEngineGroup(host.getContext(), flutterShellArgs)
? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray())
: engineGroup;
flutterEngine =
group.createAndRunEngine(
@ -348,30 +344,6 @@ import java.util.Set;
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) {
FlutterShellArgs.Flag flag = FlutterShellArgs.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/Android-Flutter-Shell-Arguments.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)}.
@ -1118,8 +1090,9 @@ import java.util.Set;
@NonNull
Lifecycle getLifecycle();
/** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */
@NonNull
String[] getFlutterShellArgs();
FlutterShellArgs getFlutterShellArgs();
/**
* Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to

View File

@ -27,6 +27,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.Lifecycle;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.plugin.platform.PlatformPlugin;
import io.flutter.plugin.view.SensitiveContentPlugin;
@ -255,7 +256,7 @@ public class FlutterFragment extends Fragment
private String initialRoute = "/";
private boolean handleDeeplinking = false;
private String appBundlePath = null;
private String[] shellArgs = null;
private FlutterShellArgs shellArgs = null;
private RenderMode renderMode = RenderMode.surface;
private TransparencyMode transparencyMode = TransparencyMode.transparent;
private boolean shouldAttachEngineToActivity = true;
@ -331,7 +332,7 @@ public class FlutterFragment extends Fragment
/** Any special configuration arguments for the Flutter engine */
@NonNull
public NewEngineFragmentBuilder flutterShellArgs(@NonNull String[] shellArgs) {
public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) {
this.shellArgs = shellArgs;
return this;
}
@ -458,8 +459,9 @@ public class FlutterFragment extends Fragment
dartEntrypointArgs != null ? new ArrayList(dartEntrypointArgs) : null);
// TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of
// conflating.
args.putStringArray(
ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs == null ? new String[0] : shellArgs);
if (null != shellArgs) {
args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray());
}
args.putString(
ARG_FLUTTERVIEW_RENDER_MODE,
renderMode != null ? renderMode.name() : RenderMode.surface.name());
@ -1351,9 +1353,10 @@ public class FlutterFragment extends Fragment
*/
@Override
@NonNull
public String[] getFlutterShellArgs() {
public FlutterShellArgs getFlutterShellArgs() {
String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS);
return flutterShellArgsArray == null ? new String[0] : flutterShellArgsArray;
return new FlutterShellArgs(
flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {});
}
/**

View File

@ -43,7 +43,7 @@ import androidx.fragment.app.FragmentManager;
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgsIntentUtils;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister;
import io.flutter.plugin.platform.PlatformPlugin;
import java.util.ArrayList;
@ -591,7 +591,7 @@ public class FlutterFragmentActivity extends FragmentActivity
.dartEntrypointArgs(getDartEntrypointArgs())
.initialRoute(getInitialRoute())
.appBundlePath(getAppBundlePath())
.flutterShellArgs(FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(getIntent()))
.flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
.handleDeeplinking(shouldHandleDeeplinking())
.renderMode(renderMode)
.transparencyMode(transparencyMode)

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} for all available flags) when creating the VM,
* manually set the initialization arguments by calling {@link
* io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the
* initialization arguments by calling {@link
* io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link
* io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context,
* String[])} before constructing the engine.

View File

@ -59,7 +59,6 @@ import java.util.Set;
// Standard FlutterPlugin
@NonNull private final FlutterEngine flutterEngine;
@NonNull private final FlutterLoader flutterLoader;
@NonNull private final FlutterPlugin.FlutterPluginBinding pluginBinding;
// ActivityAware
@ -101,7 +100,6 @@ import java.util.Set;
@NonNull FlutterLoader flutterLoader,
@Nullable FlutterEngineGroup group) {
this.flutterEngine = flutterEngine;
this.flutterLoader = flutterLoader;
pluginBinding =
new FlutterPlugin.FlutterPluginBinding(
appContext,
@ -328,32 +326,13 @@ import java.util.Set;
private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) {
this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle);
final Intent intent = activity.getIntent();
// TODO(camsim99): Remove ability to set this flag via Intents. See
// https://github.com/flutter/flutter/issues/180686.
boolean useSoftwareRendering =
intent != null
? intent.getBooleanExtra(
FlutterShellArgsIntentUtils.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)
final boolean useSoftwareRendering =
activity.getIntent() != null
? activity
.getIntent()
.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 "
+ FlutterShellArgs.ENABLE_SOFTWARE_RENDERING.metadataKey
+ " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.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

@ -4,434 +4,207 @@
package io.flutter.embedding.engine;
import androidx.annotation.VisibleForTesting;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import java.util.*;
/**
* Arguments that can be delivered to the Flutter shell on Android.
* 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
* 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 FlutterShellArgs {
@SuppressWarnings({"WeakerAccess", "unused"})
public class FlutterShellArgs {
public static final String ARG_KEY_TRACE_STARTUP = "trace-startup";
public static final String ARG_TRACE_STARTUP = "--trace-startup";
public static final String ARG_KEY_START_PAUSED = "start-paused";
public static final String ARG_START_PAUSED = "--start-paused";
public static final String ARG_KEY_DISABLE_SERVICE_AUTH_CODES = "disable-service-auth-codes";
public static final String ARG_DISABLE_SERVICE_AUTH_CODES = "--disable-service-auth-codes";
public static final String ARG_KEY_ENDLESS_TRACE_BUFFER = "endless-trace-buffer";
public static final String ARG_ENDLESS_TRACE_BUFFER = "--endless-trace-buffer";
public static final String ARG_KEY_USE_TEST_FONTS = "use-test-fonts";
public static final String ARG_USE_TEST_FONTS = "--use-test-fonts";
public static final String ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling";
public static final String ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling";
public static final String ARG_KEY_PROFILE_STARTUP = "profile-startup";
public static final String ARG_PROFILE_STARTUP = "--profile-startup";
public static final String ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering";
public static final String ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering";
public static final String ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering";
public static final String ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering";
public static final String ARG_KEY_TRACE_SKIA = "trace-skia";
public static final String ARG_TRACE_SKIA = "--trace-skia";
public static final String ARG_KEY_TRACE_SKIA_ALLOWLIST = "trace-skia-allowlist";
public static final String ARG_TRACE_SKIA_ALLOWLIST = "--trace-skia-allowlist=";
public static final String ARG_KEY_TRACE_SYSTRACE = "trace-systrace";
public static final String ARG_TRACE_SYSTRACE = "--trace-systrace";
public static final String ARG_KEY_TRACE_TO_FILE = "trace-to-file";
public static final String ARG_TRACE_TO_FILE = "--trace-to-file";
public static final String ARG_KEY_PROFILE_MICROTASKS = "profile-microtasks";
public static final String ARG_PROFILE_MICROTASKS = "--profile-microtasks";
public static final String ARG_KEY_TOGGLE_IMPELLER = "enable-impeller";
public static final String ARG_ENABLE_IMPELLER = "--enable-impeller=true";
public static final String ARG_DISABLE_IMPELLER = "--enable-impeller=false";
public static final String ARG_KEY_ENABLE_VULKAN_VALIDATION = "enable-vulkan-validation";
public static final String ARG_ENABLE_VULKAN_VALIDATION = "--enable-vulkan-validation";
public static final String ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION =
"dump-skp-on-shader-compilation";
public static final String ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION =
"--dump-skp-on-shader-compilation";
public static final String ARG_KEY_CACHE_SKSL = "cache-sksl";
public static final String ARG_CACHE_SKSL = "--cache-sksl";
public static final String ARG_KEY_PURGE_PERSISTENT_CACHE = "purge-persistent-cache";
public static final String ARG_PURGE_PERSISTENT_CACHE = "--purge-persistent-cache";
public static final String ARG_KEY_VERBOSE_LOGGING = "verbose-logging";
public static final String ARG_VERBOSE_LOGGING = "--verbose-logging";
public static final String ARG_KEY_VM_SERVICE_PORT = "vm-service-port";
public static final String ARG_VM_SERVICE_PORT = "--vm-service-port=";
public static final String ARG_KEY_DART_FLAGS = "dart-flags";
public static final String ARG_DART_FLAGS = "--dart-flags";
private FlutterShellArgs() {}
@NonNull
public static FlutterShellArgs fromIntent(@NonNull 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<>();
/** 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);
if (intent.getBooleanExtra(ARG_KEY_TRACE_STARTUP, false)) {
args.add(ARG_TRACE_STARTUP);
}
/** 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);
if (intent.getBooleanExtra(ARG_KEY_START_PAUSED, false)) {
args.add(ARG_START_PAUSED);
}
/**
* 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;
int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0);
if (vmServicePort > 0) {
args.add(ARG_VM_SERVICE_PORT + vmServicePort);
}
/** Returns true if this flag requires a value to be specified. */
public boolean hasValue() {
return commandLineArgument.endsWith("=");
if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) {
args.add(ARG_DISABLE_SERVICE_AUTH_CODES);
}
}
// 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>This is 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>This is 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 DEPRECATED_FLUTTER_ASSETS_DIR} instead.
*/
@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>This is 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>This is 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>This is 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 Android SurfaceControl for rendering.
*
* <p>This is allowed in release to opt-in to this rendering feature in production.
*/
private static final Flag ENABLE_SURFACE_CONTROL =
new Flag("--enable-surface-control", "EnableSurfaceControl", true);
/**
* Enables the Flutter GPU backend.
*
* <p>This is allowed in release for developers to use the Flutter GPU backend in production.
*/
private static final Flag ENABLE_FLUTTER_GPU =
new Flag("--enable-flutter-gpu", "EnableFlutterGPU", true);
/**
* Enables lazy initialization of Impeller shaders.
*
* <p>This is allowed in release for performance tuning of the Impeller backend.
*/
private static final Flag IMPELLER_LAZY_SHADER_MODE =
new Flag("--impeller-lazy-shader-mode=", "ImpellerLazyShaderInitialization", true);
/**
* Enables antialiasing for lines in Impeller.
*
* <p>This is allowed in release to control rendering quality in production.
*/
private static final Flag IMPELLER_ANTIALIAS_LINES =
new Flag("--impeller-antialias-lines", "ImpellerAntialiasLines", true);
/**
* Specifies the path to the VM snapshot data file.
*
* <p>This is 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>This is 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");
/** Enables GPU tracing for OpenGL. */
private static final Flag ENABLE_OPENGL_GPU_TRACING =
new Flag("--enable-opengl-gpu-tracing", "EnableOpenGLGPUTracing");
/** Enables GPU tracing for Vulkan. */
private static final Flag ENABLE_VULKAN_GPU_TRACING =
new Flag("--enable-vulkan-gpu-tracing", "EnableVulkanGPUTracing");
/**
* 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");
/** Measures startup time and switches to an endless trace buffer. */
private static final Flag TRACE_STARTUP = new Flag("--trace-startup", "TraceStartup");
/** 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 Dart profiling for use with DevTools. */
private static final Flag ENABLE_DART_PROFILING =
new Flag("--enable-dart-profiling", "EnableDartProfiling");
/** Discards new profiler samples once the buffer is full. */
private static final Flag PROFILE_STARTUP = new Flag("--profile-startup", "ProfileStartup");
/** 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");
/**
* 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,
OLD_GEN_HEAP_SIZE,
ENABLE_IMPELLER,
IMPELLER_BACKEND,
ENABLE_SURFACE_CONTROL,
ENABLE_FLUTTER_GPU,
IMPELLER_LAZY_SHADER_MODE,
IMPELLER_ANTIALIAS_LINES,
VM_SNAPSHOT_DATA,
ISOLATE_SNAPSHOT_DATA,
ENABLE_VULKAN_VALIDATION,
ENABLE_OPENGL_GPU_TRACING,
ENABLE_VULKAN_GPU_TRACING,
LEAK_VM,
TRACE_STARTUP,
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,
PURGE_PERSISTENT_CACHE,
VERBOSE_LOGGING,
DART_FLAGS,
DISABLE_MERGED_PLATFORM_UI_THREAD,
DEPRECATED_AOT_SHARED_LIBRARY_NAME,
DEPRECATED_FLUTTER_ASSETS_DIR));
// 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);
if (intent.getBooleanExtra(ARG_KEY_ENDLESS_TRACE_BUFFER, false)) {
args.add(ARG_ENDLESS_TRACE_BUFFER);
}
FLAG_BY_COMMAND_LINE_ARG = Collections.unmodifiableMap(map);
FLAG_BY_META_DATA_KEY = Collections.unmodifiableMap(metaMap);
}
/** Looks up a {@link Flag} by its metadataKey. */
public static Flag getFlagByMetadataKey(String key) {
Flag flag = FLAG_BY_META_DATA_KEY.get(key);
Flag replacementFlag = getReplacementFlagIfDeprecated(flag);
return replacementFlag != null ? replacementFlag : flag;
}
/** 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;
if (intent.getBooleanExtra(ARG_KEY_USE_TEST_FONTS, false)) {
args.add(ARG_USE_TEST_FONTS);
}
if (intent.getBooleanExtra(ARG_KEY_ENABLE_DART_PROFILING, false)) {
args.add(ARG_ENABLE_DART_PROFILING);
}
if (intent.getBooleanExtra(ARG_KEY_PROFILE_STARTUP, false)) {
args.add(ARG_PROFILE_STARTUP);
}
if (intent.getBooleanExtra(ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)) {
args.add(ARG_ENABLE_SOFTWARE_RENDERING);
}
if (intent.getBooleanExtra(ARG_KEY_SKIA_DETERMINISTIC_RENDERING, false)) {
args.add(ARG_SKIA_DETERMINISTIC_RENDERING);
}
if (intent.getBooleanExtra(ARG_KEY_TRACE_SKIA, false)) {
args.add(ARG_TRACE_SKIA);
}
String traceSkiaAllowlist = intent.getStringExtra(ARG_KEY_TRACE_SKIA_ALLOWLIST);
if (traceSkiaAllowlist != null) {
args.add(ARG_TRACE_SKIA_ALLOWLIST + traceSkiaAllowlist);
}
if (intent.getBooleanExtra(ARG_KEY_TRACE_SYSTRACE, false)) {
args.add(ARG_TRACE_SYSTRACE);
}
if (intent.hasExtra(ARG_KEY_TRACE_TO_FILE)) {
args.add(ARG_TRACE_TO_FILE + "=" + intent.getStringExtra(ARG_KEY_TRACE_TO_FILE));
}
if (intent.hasExtra(ARG_KEY_PROFILE_MICROTASKS)) {
args.add(ARG_PROFILE_MICROTASKS);
}
if (intent.hasExtra(ARG_KEY_TOGGLE_IMPELLER)) {
if (intent.getBooleanExtra(ARG_KEY_TOGGLE_IMPELLER, false)) {
args.add(ARG_ENABLE_IMPELLER);
} else {
args.add(ARG_DISABLE_IMPELLER);
}
}
return null;
if (intent.getBooleanExtra(ARG_KEY_ENABLE_VULKAN_VALIDATION, false)) {
args.add(ARG_ENABLE_VULKAN_VALIDATION);
}
if (intent.getBooleanExtra(ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, false)) {
args.add(ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION);
}
if (intent.getBooleanExtra(ARG_KEY_CACHE_SKSL, false)) {
args.add(ARG_CACHE_SKSL);
}
if (intent.getBooleanExtra(ARG_KEY_PURGE_PERSISTENT_CACHE, false)) {
args.add(ARG_PURGE_PERSISTENT_CACHE);
}
if (intent.getBooleanExtra(ARG_KEY_VERBOSE_LOGGING, false)) {
args.add(ARG_VERBOSE_LOGGING);
}
// NOTE: 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.
if (intent.hasExtra(ARG_KEY_DART_FLAGS)) {
args.add(ARG_DART_FLAGS + "=" + intent.getStringExtra(ARG_KEY_DART_FLAGS));
}
return new FlutterShellArgs(args);
}
/** 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);
@NonNull private Set<String> args;
/**
* Creates a set of Flutter shell arguments from a given {@code String[]} array. The given
* arguments are automatically de-duplicated.
*/
public FlutterShellArgs(@NonNull String[] args) {
this.args = new HashSet<>(Arrays.asList(args));
}
/** Returns the replacement flag of that given if it is deprecated. */
public static Flag getReplacementFlagIfDeprecated(Flag flag) {
return DEPRECATED_FLAGS_BY_REPLACEMENT.get(flag);
/**
* Creates a set of Flutter shell arguments from a given {@code List<String>}. The given arguments
* are automatically de-duplicated.
*/
public FlutterShellArgs(@NonNull List<String> args) {
this.args = new HashSet<>(args);
}
/** Creates a set of Flutter shell arguments from a given {@code Set<String>}. */
public FlutterShellArgs(@NonNull Set<String> args) {
this.args = new HashSet<>(args);
}
/**
* Adds the given {@code arg} to this set of arguments.
*
* @param arg argument to add
*/
public void add(@NonNull String arg) {
args.add(arg);
}
/**
* Removes the given {@code arg} from this set of arguments.
*
* @param arg argument to remove
*/
public void remove(@NonNull String arg) {
args.remove(arg);
}
/**
* Returns a new {@code String[]} array which contains each of the arguments within this {@code
* FlutterShellArgs}.
*
* @return array of arguments
*/
@NonNull
public String[] toArray() {
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
}

View File

@ -1,169 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.engine;
import android.content.Intent;
import androidx.annotation.NonNull;
import java.util.*;
/**
* Arguments that can be delivered to the Flutter shell on Android as Intent extras.
*
* <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 are preferably set via the manifest metadata in a Flutter component's
* AndroidManifest.xml or via the command line for security purposes as Intent extras may expose
* sensitive information to malicious actors. See {@link FlutterShellArgs} for the specification of
* how to set each flag via the command line and manifest metadata.
*/
// 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.
public final class FlutterShellArgsIntentUtils {
private FlutterShellArgsIntentUtils() {}
public static final String ARG_KEY_TRACE_STARTUP = "trace-startup";
public static final String ARG_TRACE_STARTUP = "--trace-startup";
public static final String ARG_KEY_START_PAUSED = "start-paused";
public static final String ARG_START_PAUSED = "--start-paused";
public static final String ARG_KEY_DISABLE_SERVICE_AUTH_CODES = "disable-service-auth-codes";
public static final String ARG_DISABLE_SERVICE_AUTH_CODES = "--disable-service-auth-codes";
public static final String ARG_KEY_ENDLESS_TRACE_BUFFER = "endless-trace-buffer";
public static final String ARG_ENDLESS_TRACE_BUFFER = "--endless-trace-buffer";
public static final String ARG_KEY_USE_TEST_FONTS = "use-test-fonts";
public static final String ARG_USE_TEST_FONTS = "--use-test-fonts";
public static final String ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling";
public static final String ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling";
public static final String ARG_KEY_PROFILE_STARTUP = "profile-startup";
public static final String ARG_PROFILE_STARTUP = "--profile-startup";
public static final String ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering";
public static final String ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering";
public static final String ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering";
public static final String ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering";
public static final String ARG_KEY_TRACE_SKIA = "trace-skia";
public static final String ARG_TRACE_SKIA = "--trace-skia";
public static final String ARG_KEY_TRACE_SKIA_ALLOWLIST = "trace-skia-allowlist";
public static final String ARG_TRACE_SKIA_ALLOWLIST = "--trace-skia-allowlist=";
public static final String ARG_KEY_TRACE_SYSTRACE = "trace-systrace";
public static final String ARG_TRACE_SYSTRACE = "--trace-systrace";
public static final String ARG_KEY_TRACE_TO_FILE = "trace-to-file";
public static final String ARG_TRACE_TO_FILE = "--trace-to-file";
public static final String ARG_KEY_PROFILE_MICROTASKS = "profile-microtasks";
public static final String ARG_PROFILE_MICROTASKS = "--profile-microtasks";
public static final String ARG_KEY_TOGGLE_IMPELLER = "enable-impeller";
public static final String ARG_ENABLE_IMPELLER = "--enable-impeller=true";
public static final String ARG_DISABLE_IMPELLER = "--enable-impeller=false";
public static final String ARG_KEY_ENABLE_VULKAN_VALIDATION = "enable-vulkan-validation";
public static final String ARG_ENABLE_VULKAN_VALIDATION = "--enable-vulkan-validation";
public static final String ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION =
"dump-skp-on-shader-compilation";
public static final String ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION =
"--dump-skp-on-shader-compilation";
public static final String ARG_KEY_CACHE_SKSL = "cache-sksl";
public static final String ARG_CACHE_SKSL = "--cache-sksl";
public static final String ARG_KEY_PURGE_PERSISTENT_CACHE = "purge-persistent-cache";
public static final String ARG_PURGE_PERSISTENT_CACHE = "--purge-persistent-cache";
public static final String ARG_KEY_VERBOSE_LOGGING = "verbose-logging";
public static final String ARG_VERBOSE_LOGGING = "--verbose-logging";
public static final String ARG_KEY_VM_SERVICE_PORT = "vm-service-port";
public static final String ARG_VM_SERVICE_PORT = "--vm-service-port=";
public static final String ARG_KEY_DART_FLAGS = "dart-flags";
public static final String ARG_DART_FLAGS = "--dart-flags";
@NonNull
public static String[] getFlutterShellCommandLineArgs(@NonNull 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(ARG_KEY_TRACE_STARTUP, false)) {
args.add(ARG_TRACE_STARTUP);
}
if (intent.getBooleanExtra(ARG_KEY_START_PAUSED, false)) {
args.add(ARG_START_PAUSED);
}
int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0);
if (vmServicePort > 0) {
args.add(ARG_VM_SERVICE_PORT + vmServicePort);
}
if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) {
args.add(ARG_DISABLE_SERVICE_AUTH_CODES);
}
if (intent.getBooleanExtra(ARG_KEY_ENDLESS_TRACE_BUFFER, false)) {
args.add(ARG_ENDLESS_TRACE_BUFFER);
}
if (intent.getBooleanExtra(ARG_KEY_USE_TEST_FONTS, false)) {
args.add(ARG_USE_TEST_FONTS);
}
if (intent.getBooleanExtra(ARG_KEY_ENABLE_DART_PROFILING, false)) {
args.add(ARG_ENABLE_DART_PROFILING);
}
if (intent.getBooleanExtra(ARG_KEY_PROFILE_STARTUP, false)) {
args.add(ARG_PROFILE_STARTUP);
}
if (intent.getBooleanExtra(ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)) {
args.add(ARG_ENABLE_SOFTWARE_RENDERING);
}
if (intent.getBooleanExtra(ARG_KEY_SKIA_DETERMINISTIC_RENDERING, false)) {
args.add(ARG_SKIA_DETERMINISTIC_RENDERING);
}
if (intent.getBooleanExtra(ARG_KEY_TRACE_SKIA, false)) {
args.add(ARG_TRACE_SKIA);
}
String traceSkiaAllowlist = intent.getStringExtra(ARG_KEY_TRACE_SKIA_ALLOWLIST);
if (traceSkiaAllowlist != null) {
args.add(ARG_TRACE_SKIA_ALLOWLIST + traceSkiaAllowlist);
}
if (intent.getBooleanExtra(ARG_KEY_TRACE_SYSTRACE, false)) {
args.add(ARG_TRACE_SYSTRACE);
}
if (intent.hasExtra(ARG_KEY_TRACE_TO_FILE)) {
args.add(ARG_TRACE_TO_FILE + "=" + intent.getStringExtra(ARG_KEY_TRACE_TO_FILE));
}
if (intent.hasExtra(ARG_KEY_PROFILE_MICROTASKS)) {
args.add(ARG_PROFILE_MICROTASKS);
}
if (intent.hasExtra(ARG_KEY_TOGGLE_IMPELLER)) {
if (intent.getBooleanExtra(ARG_KEY_TOGGLE_IMPELLER, false)) {
args.add(ARG_ENABLE_IMPELLER);
} else {
args.add(ARG_DISABLE_IMPELLER);
}
}
if (intent.getBooleanExtra(ARG_KEY_ENABLE_VULKAN_VALIDATION, false)) {
args.add(ARG_ENABLE_VULKAN_VALIDATION);
}
if (intent.getBooleanExtra(ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, false)) {
args.add(ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION);
}
if (intent.getBooleanExtra(ARG_KEY_CACHE_SKSL, false)) {
args.add(ARG_CACHE_SKSL);
}
if (intent.getBooleanExtra(ARG_KEY_PURGE_PERSISTENT_CACHE, false)) {
args.add(ARG_PURGE_PERSISTENT_CACHE);
}
if (intent.getBooleanExtra(ARG_KEY_VERBOSE_LOGGING, false)) {
args.add(ARG_VERBOSE_LOGGING);
}
// 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.
if (intent.hasExtra(ARG_KEY_DART_FLAGS)) {
args.add(ARG_DART_FLAGS + "=" + intent.getStringExtra(ARG_KEY_DART_FLAGS));
}
String[] argsArray = new String[args.size()];
return args.toArray(argsArray);
}
}

View File

@ -10,19 +10,24 @@ import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import androidx.annotation.NonNull;
import io.flutter.embedding.engine.FlutterShellArgs;
import java.io.IOException;
import org.json.JSONArray;
import org.xmlpull.v1.XmlPullParserException;
/** Loads application information given a Context. */
public final class ApplicationInfoLoader {
// 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.
// 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;
public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY =
"io.flutter.automatically-register-plugins";
"io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY;
@NonNull
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
@ -42,20 +47,6 @@ 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;
@ -155,21 +146,11 @@ 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(
getStringWithFallback(
appInfo.metaData,
FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME.metadataKey,
FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey),
getString(appInfo.metaData, FlutterShellArgs.VM_SNAPSHOT_DATA.metadataKey),
getString(appInfo.metaData, FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.metadataKey),
getStringWithFallback(
appInfo.metaData,
FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR.metadataKey,
FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey),
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),
getNetworkPolicy(appInfo, applicationContext),
appInfo.nativeLibraryDir,
getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true));

View File

@ -23,7 +23,6 @@ import io.flutter.BuildConfig;
import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.util.HandlerCompat;
import io.flutter.util.PathUtils;
import io.flutter.util.TraceSection;
@ -34,19 +33,54 @@ import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
/** Finds Flutter resources in an application APK and also loads Flutter's native library. */
public class FlutterLoader {
private static final String TAG = "FlutterLoader";
// 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";
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";
// Flag set for generating GeneratedPluginRegistrant.java.
private static final String FLUTTER_EMBEDDING_KEY = "flutterEmbedding";
/**
* 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";
// Resource names used for components of the precompiled snapshot.
private static final String DEFAULT_LIBRARY = "libflutter.so";
@ -55,7 +89,8 @@ public class FlutterLoader {
private static FlutterLoader instance;
private boolean enableSoftwareRendering = false;
@VisibleForTesting
static final String aotSharedLibraryNameFlag = "--" + AOT_SHARED_LIBRARY_NAME + "=";
/**
* Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link
@ -260,21 +295,6 @@ 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;
}
@ -291,183 +311,59 @@ public class FlutterLoader {
InitResult result = initResultFuture.get();
List<String> shellArgs = new ArrayList<>();
// 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);
// Add AOT shared library name flag if set via the command line. This flag,
// unlike others, gives precedence to the first occurrence found in shellArgs,
// so we must add it here before adding flags from the manifest.
if (args != null) {
for (String arg : args) {
if (arg.startsWith(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument)) {
// Perform security check for path containing application's compiled Dart
// code and potentially user-provided compiled native code.
String aotSharedLibraryPath =
arg.substring(
FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length());
maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs);
break;
}
}
}
// 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;
final AtomicBoolean oldGenHeapSizeSet = new AtomicBoolean(false);
final AtomicBoolean isLeakVMSet = new AtomicBoolean(false);
if (applicationMetaData != null) {
applicationMetaData.keySet().stream()
.filter(metadataKey -> !metadataKey.equals(FLUTTER_EMBEDDING_KEY))
.forEach(
metadataKey -> {
FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByMetadataKey(metadataKey);
if (flag == null) {
// Manifest flag was not recognized.
Log.w(
TAG,
"Flag with metadata key "
+ metadataKey
+ " is not recognized. Please ensure that the flag is defined in the FlutterShellArgs.");
return;
} else if (FlutterShellArgs.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 (FlutterShellArgs.getReplacementFlagIfDeprecated(flag) != null) {
Log.w(
TAG,
"If you are trying to specify "
+ flag.metadataKey
+ " in your application manifest, please make sure to use the new metadata key name: "
+ FlutterShellArgs.getReplacementFlagIfDeprecated(flag).metadataKey);
} 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.");
return;
}
// Handle special cases for specific flags.
if (flag == FlutterShellArgs.OLD_GEN_HEAP_SIZE) {
// Mark if old gen heap size is set to track whether or not to set default
// internally.
oldGenHeapSizeSet.set(true);
} else if (flag == FlutterShellArgs.LEAK_VM) {
// Mark if leak VM is set to track whether or not to set default internally.
isLeakVMSet.set(true);
} else if (flag == FlutterShellArgs.ENABLE_SOFTWARE_RENDERING) {
// Enabling software rendering impacts platform views, so save this value
// so that the PlatformViewsController can be properly configured.
enableSoftwareRendering =
applicationMetaData.getBoolean(
FlutterShellArgs.ENABLE_SOFTWARE_RENDERING.metadataKey, false);
} else if (flag == FlutterShellArgs.AOT_SHARED_LIBRARY_NAME
|| flag == FlutterShellArgs.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);
return;
}
// 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.w(
TAG,
"Flag with metadata key "
+ metadataKey
+ " requires a value, but no value was found. Please ensure that the value is a string.");
return;
}
arg += value;
}
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) {
FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByCommandLineArgument(arg);
if (flag == null) {
// Command line flag was not recognized.
Log.w(
TAG,
"Command line argument "
+ arg
+ "is not recognized. Please ensure that the flag is defined in the FlutterShellArgs.");
continue;
} else if (flag.equals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME)) {
// This flag has already been handled.
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;
// 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;
}
}
// 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 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(
FlutterShellArgs.VM_SNAPSHOT_DATA.commandLineArgument
+ flutterApplicationInfo.vmSnapshotData);
shellArgs.add(
FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.commandLineArgument
+ flutterApplicationInfo.isolateSnapshotData);
"--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData);
} else {
// 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(
FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument
+ flutterApplicationInfo.aotSharedLibraryName);
// Add default AOT shared library name arg.
shellArgs.add(aotSharedLibraryNameFlag + 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(
FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument
aotSharedLibraryNameFlag
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName);
@ -488,17 +384,23 @@ public class FlutterLoader {
shellArgs.add("--log-tag=" + settings.getLogTag());
}
if (!oldGenHeapSizeSet.get()) {
// Default to half of total memory.
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.
ActivityManager activityManager =
(ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memInfo);
int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
shellArgs.add(
FlutterShellArgs.OLD_GEN_HEAP_SIZE.commandLineArgument
+ String.valueOf(oldGenHeapSizeMegaBytes));
oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
}
shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
@ -510,10 +412,49 @@ public class FlutterLoader {
shellArgs.add("--prefetched-default-font-manager");
if (!isLeakVMSet.get()) {
shellArgs.add(FlutterShellArgs.LEAK_VM.commandLineArgument + "true");
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");
}
}
final String leakVM = isLeakVM(metaData) ? "true" : "false";
shellArgs.add("--leak-vm=" + leakVM);
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
flutterJNI.init(
@ -532,49 +473,10 @@ public class FlutterLoader {
}
}
/** Adds the AOT shared library name argument to the shell args if the provided path is safe. */
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(
FlutterShellArgs.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.
* 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.
*
* <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
@ -582,9 +484,17 @@ 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 getSafeAotSharedLibraryName(
@NonNull Context applicationContext, @NonNull String aotSharedLibraryPath)
private String getSafeAotSharedLibraryNameFlag(
@NonNull Context applicationContext, @NonNull String aotSharedLibraryNameArg)
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);
@ -609,7 +519,7 @@ public class FlutterLoader {
boolean isSoFile = aotSharedLibraryPathCanonicalPath.endsWith(".so");
if (livesWithinInternalStorage && isSoFile) {
return aotSharedLibraryPathCanonicalPath;
return aotSharedLibraryNameFlag + aotSharedLibraryPathCanonicalPath;
}
// If the library does not live within the application's internal storage, we will not use it.
Log.e(
@ -625,6 +535,14 @@ 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,6 +40,7 @@ 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;
@ -88,6 +89,7 @@ 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");
@ -104,6 +106,7 @@ 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");
@ -468,6 +471,8 @@ 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

@ -250,7 +250,7 @@ public class FlutterActivityTest {
assertNull(flutterActivity.getDartEntrypointLibraryUri());
assertNull(flutterActivity.getDartEntrypointArgs());
assertEquals("/", flutterActivity.getInitialRoute());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
@ -303,7 +303,7 @@ public class FlutterActivityTest {
assertEquals("/custom/route", flutterActivity.getInitialRoute());
assertArrayEquals(
new String[] {"foo", "bar"}, flutterActivity.getDartEntrypointArgs().toArray());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
@ -328,7 +328,7 @@ public class FlutterActivityTest {
assertEquals("my_cached_engine_group", flutterActivity.getCachedEngineGroupId());
assertEquals("custom_entrypoint", flutterActivity.getDartEntrypointFunctionName());
assertEquals("/custom/route", flutterActivity.getInitialRoute());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
assertNull(flutterActivity.getCachedEngineId());
@ -393,7 +393,7 @@ public class FlutterActivityTest {
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
assertFalse(flutterActivity.shouldDestroyEngineWithHost());
@ -409,7 +409,7 @@ public class FlutterActivityTest {
Robolectric.buildActivity(FlutterActivity.class, intent);
FlutterActivity flutterActivity = activityController.get();
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertEquals("my_cached_engine", flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());

View File

@ -31,6 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
@ -301,8 +302,8 @@ public class FlutterAndroidComponentTest {
@NonNull
@Override
public String[] getFlutterShellArgs() {
return new String[0];
public FlutterShellArgs getFlutterShellArgs() {
return new FlutterShellArgs(new String[] {});
}
@Nullable

View File

@ -69,7 +69,7 @@ public class FlutterFragmentTest {
assertNull(fragment.getDartEntrypointLibraryUri());
assertNull(fragment.getDartEntrypointArgs());
assertEquals("/", fragment.getInitialRoute());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertTrue(fragment.shouldAttachEngineToActivity());
assertFalse(fragment.shouldHandleDeeplinking());
assertNull(fragment.getCachedEngineId());
@ -100,7 +100,7 @@ public class FlutterFragmentTest {
assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri());
assertEquals("/custom/route", fragment.getInitialRoute());
assertArrayEquals(new String[] {"foo", "bar"}, fragment.getDartEntrypointArgs().toArray());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertFalse(fragment.shouldAttachEngineToActivity());
assertTrue(fragment.shouldHandleDeeplinking());
assertNull(fragment.getCachedEngineId());
@ -129,7 +129,7 @@ public class FlutterFragmentTest {
assertEquals("my_cached_engine_group", fragment.getCachedEngineGroupId());
assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName());
assertEquals("/custom/route", fragment.getInitialRoute());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs());
assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray());
assertFalse(fragment.shouldAttachEngineToActivity());
assertTrue(fragment.shouldHandleDeeplinking());
assertNull(fragment.getCachedEngineId());

View File

@ -11,19 +11,14 @@ 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;
@ -90,19 +85,6 @@ 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);
@ -145,31 +127,41 @@ public class FlutterEngineConnectionRegistryTest {
}
@Test
public void attachToActivityConfiguresSoftwareRendering() {
public void softwareRendering() {
Context context = mock(Context.class);
FlutterEngine flutterEngine = mock(FlutterEngine.class);
PlatformViewsController platformViewsController = mock(PlatformViewsController.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);
PlatformViewsController2 platformViewsController2 = mock(PlatformViewsController2.class);
PlatformViewsControllerDelegator platformViewsControllerDelegator =
mock(PlatformViewsControllerDelegator.class);
when(flutterEngine.getPlatformViewsControllerDelegator())
.thenReturn(platformViewsControllerDelegator);
when(flutterEngine.getDartExecutor()).thenReturn(mock(DartExecutor.class));
when(flutterEngine.getRenderer()).thenReturn(mock(FlutterRenderer.class));
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.
FlutterEngineConnectionRegistry registry =
new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null);
registry.attachToActivity(appComponent, mock(Lifecycle.class));
verify(platformViewsController).setSoftwareRendering(false);
when(flutterLoader.getSofwareRenderingEnabledViaManifest()).thenReturn(true);
when(appComponent.getAppComponent()).thenReturn(activity);
when(activity.getIntent()).thenReturn(mock(Intent.class));
Intent intent = mock(Intent.class);
when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(false);
when(activity.getIntent()).thenReturn(intent);
registry.attachToActivity(appComponent, lifecycle);
registry.attachToActivity(appComponent, mock(Lifecycle.class));
verify(platformViewsController, times(2)).setSoftwareRendering(false);
when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(true);
registry.attachToActivity(appComponent, mock(Lifecycle.class));
verify(platformViewsController).setSoftwareRendering(true);
}

View File

@ -1,36 +0,0 @@
// 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 test.io.flutter.embedding.engine;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterShellArgsIntentUtils;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class FlutterShellArgsIntentUtilsTest {
@Test
public void itProcessesShellFlags() {
// Setup the test.
Intent intent = new Intent();
intent.putExtra("dart-flags", "--observe --no-hot --no-pub");
intent.putExtra("trace-skia-allowlist", "skia.a,skia.b");
// Execute the behavior under test.
String[] args = FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(intent);
HashSet<String> argValues = new HashSet<String>(Arrays.asList(args));
// Verify results.
assertEquals(2, argValues.size());
assertTrue(argValues.contains("--dart-flags=--observe --no-hot --no-pub"));
assertTrue(argValues.contains("--trace-skia-allowlist=skia.a,skia.b"));
}
}

View File

@ -2,121 +2,35 @@
// 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;
package test.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 android.content.Intent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterShellArgs;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class FlutterShellArgsTest {
@Test
public void allFlags_containsAllFlags() {
// Count the number of declared flags in FlutterShellArgs.
int declaredFlagsCount = 0;
for (Field field : FlutterShellArgs.class.getDeclaredFields()) {
if (FlutterShellArgs.Flag.class.isAssignableFrom(field.getType())
&& Modifier.isStatic(field.getModifiers())
&& Modifier.isFinal(field.getModifiers())) {
declaredFlagsCount++;
}
}
public void itProcessesShellFlags() {
// Setup the test.
Intent intent = new Intent();
intent.putExtra("dart-flags", "--observe --no-hot --no-pub");
intent.putExtra("trace-skia-allowlist", "skia.a,skia.b");
// Check that the number of declared flags matches the size of ALL_FLAGS.
assertEquals(
"If you are adding a new Flag to FlutterShellArgs, please make sure it is added to ALL_FLAGS as well. Otherwise, the flag will be silently ignored when specified.",
declaredFlagsCount,
FlutterShellArgs.ALL_FLAGS.size());
}
// Execute the behavior under test.
FlutterShellArgs args = FlutterShellArgs.fromIntent(intent);
HashSet<String> argValues = new HashSet<String>(Arrays.asList(args.toArray()));
@SuppressWarnings("deprecation")
@Test
public void allFlags_haveExpectedMetaDataNamePrefix() {
String defaultPrefix = "io.flutter.embedding.android.";
for (FlutterShellArgs.Flag flag : FlutterShellArgs.ALL_FLAGS) {
// Test all non-deprecated flags that should have the default prefix.
if (!flag.equals(FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME)
&& !flag.equals(FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR)) {
assertTrue(
"Flag " + flag.commandLineArgument + " does not have the correct metadata key prefix.",
flag.metadataKey.startsWith(defaultPrefix));
}
}
}
@Test
public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() {
FlutterShellArgs.Flag flag =
FlutterShellArgs.getFlagByMetadataKey("io.flutter.embedding.android.AOTSharedLibraryName");
assertEquals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME, flag);
}
@Test
public void getFlagByMetadataKey_returnsNullWhenInvalidKeySpecified() {
FlutterShellArgs.Flag flag =
FlutterShellArgs.getFlagByMetadataKey("io.flutter.embedding.android.InvalidMetaDataKey");
assertNull("Should return null for an invalid meta-data key", flag);
}
@Test
public void getFlagByCommandLineArgument_returnsExpectedFlagWhenValidArgumentSpecified() {
FlutterShellArgs.Flag flag =
FlutterShellArgs.getFlagByCommandLineArgument("--flutter-assets-dir=");
assertEquals(FlutterShellArgs.FLUTTER_ASSETS_DIR, flag);
}
@Test
public void getFlagByCommandLineArgument_returnsNullWhenInvalidArgumentSpecified() {
assertNull(FlutterShellArgs.getFlagFromIntentKey("--non-existent-flag"));
}
@Test
public void getFlagFromIntentKey_returnsExpectedFlagWhenValidKeySpecified() {
// Test flag without value.
FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagFromIntentKey("old-gen-heap-size");
assertEquals(FlutterShellArgs.OLD_GEN_HEAP_SIZE, flag);
// Test with flag.
flag = FlutterShellArgs.getFlagFromIntentKey("vm-snapshot-data");
assertEquals(FlutterShellArgs.VM_SNAPSHOT_DATA, flag);
}
@Test
public void getFlagFromIntentKey_returnsNullWhenInvalidKeySpecified() {
assertNull(FlutterShellArgs.getFlagFromIntentKey("non-existent-flag"));
}
@Test
public void isDisabled_returnsTrueWhenFlagIsDisabled() {
assertTrue(FlutterShellArgs.isDisabled(FlutterShellArgs.DISABLE_MERGED_PLATFORM_UI_THREAD));
}
@Test
public void isDisabled_returnsFalseWhenFlagIsNotDisabled() {
assertFalse(FlutterShellArgs.isDisabled(FlutterShellArgs.VM_SNAPSHOT_DATA));
}
// Deprecated flags are tested in this test.
@SuppressWarnings("deprecation")
@Test
public void getReplacementFlagIfDeprecated_returnsExpectedFlag() {
assertEquals(
FlutterShellArgs.AOT_SHARED_LIBRARY_NAME,
FlutterShellArgs.getReplacementFlagIfDeprecated(
FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME));
assertEquals(
FlutterShellArgs.FLUTTER_ASSETS_DIR,
FlutterShellArgs.getReplacementFlagIfDeprecated(
FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR));
}
@Test
public void getReplacementFlagIfDeprecated_returnsNullWhenFlagIsNotDeprecated() {
assertNull(FlutterShellArgs.getReplacementFlagIfDeprecated(FlutterShellArgs.VM_SNAPSHOT_DATA));
// Verify results.
assertEquals(2, argValues.size());
assertTrue(argValues.contains("--dart-flags=--observe --no-hot --no-pub"));
assertTrue(argValues.contains("--trace-skia-allowlist=skia.a,skia.b"));
}
}

View File

@ -24,7 +24,7 @@ import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.FlutterShellArgs;
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(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey, "custom_name.so");
bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so");
bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "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(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "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(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets");
bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets");
Context spyContext = createSpyContext(bundle);
doReturn(null).when(spyContext).getAssets();

View File

@ -23,7 +23,6 @@ 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.FlutterShellArgs;
import java.io.StringReader;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -71,10 +70,10 @@ public class ApplicationInfoLoaderTest {
@Test
public void itGeneratesCorrectApplicationInfoWithCustomValues() throws Exception {
Bundle bundle = new Bundle();
bundle.putString(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey, "testaot");
bundle.putString(FlutterShellArgs.VM_SNAPSHOT_DATA.metadataKey, "testvmsnapshot");
bundle.putString(FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.metadataKey, "testisolatesnapshot");
bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "testassets");
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");
Context context = generateMockContext(bundle, null);
FlutterApplicationInfo info = ApplicationInfoLoader.load(context);
assertNotNull(info);

View File

@ -8,7 +8,6 @@ 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;
@ -204,6 +203,35 @@ 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);
@ -266,32 +294,22 @@ public class FlutterLoaderTest {
}
@Test
public void itSetsDeprecatedAotSharedLibraryNameIfPathIsInInternalStorage() throws IOException {
public void itSetsEnableImpellerFromMetaData() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI));
Context mockApplicationContext = mock(Context.class);
File internalStorageDir = ctx.getFilesDir();
Path internalStorageDirAsPathObj = internalStorageDir.toPath();
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
Bundle metaData = new Bundle();
metaData.putBoolean("io.flutter.embedding.android.EnableImpeller", true);
ctx.getApplicationInfo().metaData = metaData;
ctx.getApplicationInfo().nativeLibraryDir =
Paths.get("some", "path", "doesnt", "matter").toString();
FlutterLoader.Settings settings = new FlutterLoader.Settings();
assertFalse(flutterLoader.initialized());
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.startInitialization(ctx, settings);
flutterLoader.ensureInitializationComplete(ctx, null);
shadowOf(getMainLooper()).idle();
final String enableImpellerArg = "--enable-impeller=true";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
@ -300,51 +318,27 @@ public class FlutterLoaderTest {
anyString(),
anyLong(),
anyInt());
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);
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(enableImpellerArg));
}
@Test
public void itSetsAotSharedLibraryNameIfPathIsInInternalStorageInReleaseMode()
throws IOException {
public void itSetsEnableFlutterGPUFromMetaData() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI));
Context mockApplicationContext = mock(Context.class);
File internalStorageDir = ctx.getFilesDir();
Path internalStorageDirAsPathObj = internalStorageDir.toPath();
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
Bundle metaData = new Bundle();
metaData.putBoolean("io.flutter.embedding.android.EnableFlutterGPU", true);
ctx.getApplicationInfo().metaData = metaData;
ctx.getApplicationInfo().nativeLibraryDir =
Paths.get("some", "path", "doesnt", "matter").toString();
FlutterLoader.Settings settings = new FlutterLoader.Settings();
assertFalse(flutterLoader.initialized());
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);
flutterLoader.startInitialization(ctx, settings);
flutterLoader.ensureInitializationComplete(ctx, null);
shadowOf(getMainLooper()).idle();
final String enableImpellerArg = "--enable-flutter-gpu";
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
verify(mockFlutterJNI, times(1))
.init(
eq(ctx),
shellArgsCaptor.capture(),
@ -353,22 +347,66 @@ public class FlutterLoaderTest {
anyString(),
anyLong(),
anyInt());
List<String> arguments = Arrays.asList(shellArgsCaptor.getValue());
assertTrue(arguments.contains(enableImpellerArg));
}
List<String> actualArgs = Arrays.asList(shellArgsCaptor.getValue());
@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;
// 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));
FlutterLoader.Settings settings = new FlutterLoader.Settings();
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(ctx, settings);
flutterLoader.ensureInitializationComplete(ctx, null);
shadowOf(getMainLooper()).idle();
// Reset FlutterLoader and mockFlutterJNI to make more calls to
// FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing.
flutterLoader.initialized = false;
clearInvocations(mockFlutterJNI);
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));
}
@Test
@ -408,11 +446,9 @@ public class FlutterLoaderTest {
for (Path testPath : pathsToTest) {
String path = testPath.toString();
Bundle metadata = new Bundle();
metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path);
ctx.getApplicationInfo().metaData = metadata;
flutterLoader.ensureInitializationComplete(ctx, null);
String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path;
String[] args = {aotSharedLibraryNameArg};
flutterLoader.ensureInitializationComplete(ctx, args);
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -431,7 +467,8 @@ 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 = "--aot-shared-library-name=" + canonicalTestPath;
String canonicalAotSharedLibraryNameArg =
FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath;
assertTrue(
"Args sent to FlutterJni.init incorrectly did not include path " + path,
actualArgs.contains(canonicalAotSharedLibraryNameArg));
@ -486,11 +523,9 @@ public class FlutterLoaderTest {
for (Path testPath : pathsToTest) {
String path = testPath.toString();
Bundle metadata = new Bundle();
metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path);
ctx.getApplicationInfo().metaData = metadata;
flutterLoader.ensureInitializationComplete(ctx, null);
String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path;
String[] args = {aotSharedLibraryNameArg};
flutterLoader.ensureInitializationComplete(ctx, args);
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -509,7 +544,8 @@ 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 = "--aot-shared-library-name=" + canonicalTestPath;
String canonicalAotSharedLibraryNameArg =
FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included canonical path " + canonicalTestPath,
actualArgs.contains(canonicalAotSharedLibraryNameArg));
@ -536,11 +572,8 @@ public class FlutterLoaderTest {
String invalidFilePath = "my\0file.so";
Bundle metadata = new Bundle();
metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", invalidFilePath);
ctx.getApplicationInfo().metaData = metadata;
flutterLoader.ensureInitializationComplete(ctx, null);
String[] args = {FlutterLoader.aotSharedLibraryNameFlag + invalidFilePath};
flutterLoader.ensureInitializationComplete(ctx, args);
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -559,7 +592,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("--aot-shared-library-name=")) {
if (arg.startsWith(FlutterLoader.aotSharedLibraryNameFlag)) {
fail();
}
}
@ -587,11 +620,9 @@ public class FlutterLoaderTest {
when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile);
doReturn(realSoFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath();
Bundle metadata = new Bundle();
metadata.putString(
"io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getPath());
ctx.getApplicationInfo().metaData = metadata;
flutterLoader.ensureInitializationComplete(ctx, null);
String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getPath();
String[] args = {symlinkArg};
flutterLoader.ensureInitializationComplete(ctx, args);
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -607,14 +638,12 @@ 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 =
aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included absolute symlink path: "
+ spySymlinkFile.getAbsolutePath(),
actualArgs.contains(symlinkAotSharedLibraryNameArg));
actualArgs.contains(symlinkArg));
assertTrue(
"Args sent to FlutterJni.init incorrectly did not include canonicalized path of symlink: "
+ canonicalSymlinkCanonicalizedPath,
@ -645,17 +674,15 @@ public class FlutterLoaderTest {
List<File> unsafeFiles = Arrays.asList(nonSoFile, fileJustOutsideInternalStorage);
Files.deleteIfExists(spySymlinkFile.toPath());
Bundle metadata = new Bundle();
metadata.putString(
"io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getAbsolutePath());
ctx.getApplicationInfo().metaData = metadata;
String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath();
String[] args = {symlinkArg};
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, null);
flutterLoader.ensureInitializationComplete(ctx, args);
ArgumentCaptor<String[]> shellArgsCaptor = ArgumentCaptor.forClass(String[].class);
verify(mockFlutterJNI)
@ -671,11 +698,8 @@ 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 =
aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath;
assertFalse(
"Args sent to FlutterJni.init incorrectly included canonicalized path of symlink: "
+ canonicalSymlinkCanonicalizedPath,
@ -683,7 +707,7 @@ public class FlutterLoaderTest {
assertFalse(
"Args sent to FlutterJni.init incorrectly included absolute path of symlink: "
+ spySymlinkFile.getAbsolutePath(),
actualArgs.contains(symlinkAotSharedLibraryNameArg));
actualArgs.contains(symlinkArg));
// Clean up created files.
spySymlinkFile.delete();
@ -695,623 +719,4 @@ public class FlutterLoaderTest {
clearInvocations(mockFlutterJNI);
}
}
@Test
public void itSetsEnableSoftwareRenderingFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableSoftwareRendering",
true,
"--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",
true,
"--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 itSetsEnableImpellerFromMetadata() {
// Test debug mode.
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true");
// Test release mode.
testFlagFromMetadataPresentInReleaseMode(
"io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true");
}
@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 itSetsEnableSurfaceControlFromMetadata() {
// Test debug mode.
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control");
// Test release mode.
testFlagFromMetadataPresentInReleaseMode(
"io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control");
}
@Test
public void itSetsEnableFlutterGPUFromMetadata() {
// Test debug mode.
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu");
// Test release mode.
testFlagFromMetadataPresentInReleaseMode(
"io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu");
}
@Test
public void itSetsImpellerLazyShaderModeFromMetadata() {
// Test debug mode.
testFlagFromMetadataPresent(
"io.flutter.embedding.android.ImpellerLazyShaderInitialization",
true,
"--impeller-lazy-shader-mode=true");
// Test release mode.
testFlagFromMetadataPresentInReleaseMode(
"io.flutter.embedding.android.ImpellerLazyShaderInitialization",
true,
"--impeller-lazy-shader-mode=true");
}
@Test
public void itSetsImpellerAntiAliasLinesFromMetadata() {
// Test debug mode.
testFlagFromMetadataPresent(
"io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines");
// Test release mode.
testFlagFromMetadataPresentInReleaseMode(
"io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines");
}
@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", true, "--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", true, "--enable-vulkan-validation");
}
@Test
public void itSetsEnableOpenGLGPUTracingFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableOpenGLGPUTracing", true, "--enable-opengl-gpu-tracing");
}
@Test
public void itSetsEnableVulkanGPUTracingFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableVulkanGPUTracing", true, "--enable-vulkan-gpu-tracing");
}
@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", true, "--trace-startup");
}
@Test
public void itSetsStartPausedFromMetadata() {
testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", true, "--start-paused");
}
@Test
public void itSetsDisableServiceAuthCodesFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.DisableServiceAuthCodes",
true,
"--disable-service-auth-codes");
}
@Test
public void itSetsEndlessTraceBufferFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EndlessTraceBuffer", true, "--endless-trace-buffer");
}
@Test
public void itSetsEnableDartProfilingFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling");
}
@Test
public void itSetsProfileStartupFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.ProfileStartup", true, "--profile-startup");
}
@Test
public void itSetsTraceSkiaFromMetadata() {
testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", true, "--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", true, "--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", true, "--profile-microtasks");
}
@Test
public void itSetsDumpSkpOnShaderCompilationFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.DumpSkpOnShaderCompilation",
true,
"--dump-skp-on-shader-compilation");
}
@Test
public void itSetsPurgePersistentCacheFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.PurgePersistentCache", true, "--purge-persistent-cache");
}
@Test
public void itSetsVerboseLoggingFromMetadata() {
testFlagFromMetadataPresent(
"io.flutter.embedding.android.VerboseLogging", true, "--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 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 itDoesNotSetUnrecognizedCommandLineArgument() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
Bundle metadata = new Bundle();
String[] unrecognizedArg = {"--unrecognized-argument"};
FlutterLoader.Settings settings = new FlutterLoader.Settings();
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(ctx, settings);
flutterLoader.ensureInitializationComplete(ctx, unrecognizedArg);
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());
assertFalse(
"Unrecognized argument '"
+ unrecognizedArg[0]
+ "' was found in the arguments passed to FlutterJNI.init",
arguments.contains(unrecognizedArg[0]));
}
@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 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

@ -26,7 +26,7 @@ public class FlutterLaunchTests {
assertEquals("main", flutterActivity.getDartEntrypointFunctionName());
assertEquals("/", flutterActivity.getInitialRoute());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs());
assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray());
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());

View File

@ -15,14 +15,11 @@ 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;
@ -34,28 +31,34 @@ public class MainActivity extends AppCompatActivity {
private static final String PING = "ping";
private BasicMessageChannel<String> messageChannel;
// 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/Android-Flutter-Shell-Arguments.md for alternative methods.");
break;
}
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");
}
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);
warnIfEngineFlagsSetViaIntent(getIntent());
String[] args = getArgsFromIntent(getIntent());
if (flutterEngine == null) {
flutterEngine = new FlutterEngine(this);
flutterEngine = new FlutterEngine(this, args);
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);