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