diff --git a/docs/engine/Android-Flutter-Shell-Arguments.md b/docs/engine/Android-Flutter-Shell-Arguments.md deleted file mode 100644 index 41638bbe4f0..00000000000 --- a/docs/engine/Android-Flutter-Shell-Arguments.md +++ /dev/null @@ -1,190 +0,0 @@ -# Setting Flutter Android engine flags - -You can set flags for the Flutter engine on Android in two different ways: - -- From the command line when launching an app with the Flutter tool -- Via `AndroidManifest.xml` metadata (static, per-build configuration) - -All flags available on Android can be set via the command line **and** via -manifest metadata. See `src/flutter/shell/common/switches.cc` for -the list of all supported flags, and see -`src/flutter/shell/platform/android/io/flutter/embedding/engine/` -`FlutterShellArgs.java` for the list of flags that can be set for the -Android shell. - -## When to use manifest metadata versus the command line - -Use the manifest when: - -- You want a fixed, reproducible baseline of engine flags - for your app across all launches. This is ideal for CI and for enforcing a - consistent configuration for your app. -- You want to vary flags by build mode or product flavor - via manifest merging. For example, place metadata in - `src/debug/AndroidManifest.xml`, `src/profile/AndroidManifest.xml`, and - `src/release/AndroidManifest.xml` (or per-flavor manifests) to tailor flags - per variant. - -Use the command line when: - -- You want to quickly experiment with a flag for a single run of your app. -- You need to override a flag that is already set in the manifest temporarily for debugging - or testing purposes. - -**Note: If a flag is specified both on the command line and in the manifest, -the command-line value takes precedence at runtime.** - -See below for details on using each method. - -## How to set engine flags from the command line - -When you run a standalone Flutter app with the Flutter tool, engine flags -can be passed directly and are forwarded to the Android engine. Examples: - -```bash -flutter run --trace-startup \ - --enable-software-rendering \ - --dart-flags="--enable-asserts" -``` - -Notes: - -- Flags that take values use the `--flag=value` form (with `=`). The Flutter - tool forwards them in that form to the Android embedding. - -## How to set engine flags in the manifest - -All manifest metadata keys must be prefixed with the package name -`io.flutter.embedding.android` and are suffixed with the metadata name for the -related command line flag as determined in -`src/flutter/shell/platform/android/io/flutter/embedding/engine/` -`FlutterShellArgs.java`. For example, the `--impeller-lazy-shader-mode=` -command line flag corresponds to the metadata key -`io.flutter.embedding.android.ImpellerLazyShaderInitialization`. - -For flags that take values, set the numeric, string, or boolean value (without -the leading `--flag=` prefix). - -### Examples - -Set the `--old-gen-heap-size=` flag to 322 MB: - -```xml - - - - ... - - -``` - -Set the `--enable-flutter-gpu` flag: - -```xml - -``` - -## Release-mode restrictions - -- Some flags are not allowed in release mode. The Android embedding enforces - this policy (see `src/flutter/shell/platform/android/io/flutter/ - embedding/engine/FlutterShellArgs`, which marks allowed flags - with `allowedInRelease`). If a disallowed flag is set in release, it will - be ignored. -- If you need different behavior in release vs debug/profile mode, configure it - via variant-specific manifests or product flavors. - -## How to set engine flags dynamically - -As of the writing of this document, setting Flutter shell arguments via an -Android `Intent` is no longer supported. If you need per-launch or -runtime-controlled flags in an add-to-app integration, you may do so -programatically before engine initialization. - -To do that, supply engine arguments directly to a `FlutterEngine` with the -desired flags from the earliest point you can control in your -application. For example, if you are writing an add-to-app app that launches -a `FlutterActivity` or `FlutterFragment`, then you can cache a -`FlutterEngine` that is initialized with your desired -engine flags: - -```kotlin -// Your native Android application -class MyApp : Application() { - override fun onCreate() { - super.onCreate() - // Initialize the Flutter engine with desired flags - val args = arrayOf( - "--trace-startup", - "--old-gen-heap-size=256", - "--enable-software-rendering" - ) - val flutterEngine = FlutterEngine(this, args) - - // Start executing Dart code in the FlutterEngine - flutterEngine.dartExecutor.executeDartEntrypoint( - DartEntrypoint.createDefault() - ) - - // Store the engine in the cache for later use - FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine) - } -} -``` - -Then, your `Activity` can launch a `FlutterActivity` or `FlutterFragment` -with that cached `FlutterEngine`: - -```kotlin -// Start a FlutterActivity using the cached engine... -val intent = FlutterActivity.withCachedEngine("my_engine_id").build(this) -startActivity(intent) - -// Or launch a FlutterFragment using the cached engine -val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").build() -supportFragmentManager - .beginTransaction() - .add(R.id.fragment_container, flutterFragment, TAG_FLUTTER_FRAGMENT) - .commit() -``` - -For a normal Flutter Android app, you can create and initialize a `FlutterEngine` -with your desired flags the same as in the example above, then override -`provideFlutterEngine` in your app's `FlutterActivity` to provide the -configured `FlutterEngine`. For example: - -```kotlin -// Your Flutter Android application -class MyApplication : FlutterApplication() { - override fun onCreate() { - super.onCreate() - - val args = arrayOf( - "--trace-startup", - "--old-gen-heap-size=256", - "--enable-software-rendering" - ) - val flutterEngine = FlutterEngine(this, args) - flutterEngine.dartExecutor.executeDartEntrypoint( - DartExecutor.DartEntrypoint.createDefault() - ) - FlutterEngineCache - .getInstance() - .put(MY_ENGINE_ID, flutterEngine) - } -} - -// Your Flutter Android Activity -class MainActivity: FlutterActivity() { - override fun provideFlutterEngine(context: Context): FlutterEngine? { - return FlutterEngineCache - .getInstance() - .get(MyApplication.MY_ENGINE_ID) - } -} -``` diff --git a/engine/src/flutter/shell/platform/android/BUILD.gn b/engine/src/flutter/shell/platform/android/BUILD.gn index 197e5b39237..2b188414f04 100644 --- a/engine/src/flutter/shell/platform/android/BUILD.gn +++ b/engine/src/flutter/shell/platform/android/BUILD.gn @@ -255,7 +255,6 @@ android_java_sources = [ "io/flutter/embedding/engine/FlutterJNI.java", "io/flutter/embedding/engine/FlutterOverlaySurface.java", "io/flutter/embedding/engine/FlutterShellArgs.java", - "io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java", "io/flutter/embedding/engine/dart/DartExecutor.java", "io/flutter/embedding/engine/dart/DartMessenger.java", "io/flutter/embedding/engine/dart/PlatformMessageHandler.java", diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index fc5d387fb22..e7d982d4a81 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -49,7 +49,7 @@ import androidx.lifecycle.LifecycleRegistry; import io.flutter.Log; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterShellArgsIntentUtils; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; @@ -1042,8 +1042,8 @@ public class FlutterActivity extends Activity */ @NonNull @Override - public String[] getFlutterShellArgs() { - return FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(getIntent()); + public FlutterShellArgs getFlutterShellArgs() { + return FlutterShellArgs.fromIntent(getIntent()); } /** diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index eb02d2eff26..7eadd5c9065 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -38,7 +38,6 @@ import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.view.SensitiveContentPlugin; import java.util.Arrays; import java.util.List; -import java.util.Set; /** * Delegate that implements all Flutter logic that is the same between a {@link FlutterActivity} and @@ -332,12 +331,9 @@ import java.util.Set; "No preferred FlutterEngine was provided. Creating a new FlutterEngine for" + " this FlutterFragment."); - warnIfEngineFlagsSetViaIntent(host.getActivity().getIntent()); - String[] flutterShellArgs = - host.getFlutterShellArgs() == null ? new String[0] : host.getFlutterShellArgs(); FlutterEngineGroup group = engineGroup == null - ? new FlutterEngineGroup(host.getContext(), flutterShellArgs) + ? new FlutterEngineGroup(host.getContext(), host.getFlutterShellArgs().toArray()) : engineGroup; flutterEngine = group.createAndRunEngine( @@ -348,30 +344,6 @@ import java.util.Set; isFlutterEngineFromHost = false; } - // As part of https://github.com/flutter/flutter/issues/180686, the ability - // to set engine flags via Intent extras is planned to be removed, so warn - // developers that engine shell arguments set that way will be ignored. - private void warnIfEngineFlagsSetViaIntent(@NonNull Intent intent) { - if (intent.getExtras() == null) { - return; - } - - Bundle extras = intent.getExtras(); - Set extrasKeys = extras.keySet(); - - for (String extrasKey : extrasKeys) { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagFromIntentKey(extrasKey); - if (flag != null) { - Log.w( - TAG, - "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/180686 for more information on this breaking change. To migrate, set " - + flag.commandLineArgument - + " on the command line or see https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.md for alternative methods."); - break; - } - } - } - /** * Invoke this method from {@code Activity#onCreate(Bundle)} to create the content {@code View}, * or from {@code Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}. @@ -1118,8 +1090,9 @@ import java.util.Set; @NonNull Lifecycle getLifecycle(); + /** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */ @NonNull - String[] getFlutterShellArgs(); + FlutterShellArgs getFlutterShellArgs(); /** * Returns the ID of a statically cached {@link io.flutter.embedding.engine.FlutterEngine} to diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 716b1f3ea04..515c40475ef 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -27,6 +27,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.Lifecycle; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.view.SensitiveContentPlugin; @@ -255,7 +256,7 @@ public class FlutterFragment extends Fragment private String initialRoute = "/"; private boolean handleDeeplinking = false; private String appBundlePath = null; - private String[] shellArgs = null; + private FlutterShellArgs shellArgs = null; private RenderMode renderMode = RenderMode.surface; private TransparencyMode transparencyMode = TransparencyMode.transparent; private boolean shouldAttachEngineToActivity = true; @@ -331,7 +332,7 @@ public class FlutterFragment extends Fragment /** Any special configuration arguments for the Flutter engine */ @NonNull - public NewEngineFragmentBuilder flutterShellArgs(@NonNull String[] shellArgs) { + public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) { this.shellArgs = shellArgs; return this; } @@ -458,8 +459,9 @@ public class FlutterFragment extends Fragment dartEntrypointArgs != null ? new ArrayList(dartEntrypointArgs) : null); // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of // conflating. - args.putStringArray( - ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs == null ? new String[0] : shellArgs); + if (null != shellArgs) { + args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray()); + } args.putString( ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : RenderMode.surface.name()); @@ -1351,9 +1353,10 @@ public class FlutterFragment extends Fragment */ @Override @NonNull - public String[] getFlutterShellArgs() { + public FlutterShellArgs getFlutterShellArgs() { String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS); - return flutterShellArgsArray == null ? new String[0] : flutterShellArgsArray; + return new FlutterShellArgs( + flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {}); } /** diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java index ae35ed5da59..71185dd0f48 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java @@ -43,7 +43,7 @@ import androidx.fragment.app.FragmentManager; import io.flutter.Log; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterShellArgsIntentUtils; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; import java.util.ArrayList; @@ -591,7 +591,7 @@ public class FlutterFragmentActivity extends FragmentActivity .dartEntrypointArgs(getDartEntrypointArgs()) .initialRoute(getInitialRoute()) .appBundlePath(getAppBundlePath()) - .flutterShellArgs(FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(getIntent())) + .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent())) .handleDeeplinking(shouldHandleDeeplinking()) .renderMode(renderMode) .transparencyMode(transparencyMode) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 5d4771843cf..cf57c07eccd 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -177,8 +177,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater { * native library and start a Dart VM. * *

In order to pass Dart VM initialization arguments (see {@link - * io.flutter.embedding.engine.FlutterShellArgs} for all available flags) when creating the VM, - * manually set the initialization arguments by calling {@link + * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the + * initialization arguments by calling {@link * io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, * String[])} before constructing the engine. diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java index 2428e23fe01..bcb5addfff6 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java @@ -59,7 +59,6 @@ import java.util.Set; // Standard FlutterPlugin @NonNull private final FlutterEngine flutterEngine; - @NonNull private final FlutterLoader flutterLoader; @NonNull private final FlutterPlugin.FlutterPluginBinding pluginBinding; // ActivityAware @@ -101,7 +100,6 @@ import java.util.Set; @NonNull FlutterLoader flutterLoader, @Nullable FlutterEngineGroup group) { this.flutterEngine = flutterEngine; - this.flutterLoader = flutterLoader; pluginBinding = new FlutterPlugin.FlutterPluginBinding( appContext, @@ -328,32 +326,13 @@ import java.util.Set; private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) { this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); - final Intent intent = activity.getIntent(); - // TODO(camsim99): Remove ability to set this flag via Intents. See - // https://github.com/flutter/flutter/issues/180686. - boolean useSoftwareRendering = - intent != null - ? intent.getBooleanExtra( - FlutterShellArgsIntentUtils.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) + final boolean useSoftwareRendering = + activity.getIntent() != null + ? activity + .getIntent() + .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) : false; - - // As part of https://github.com/flutter/flutter/issues/172553, the ability to set - // --enable-software-rendering via Intent is planned to be removed. Warn - // developers about the new method for doing so if this was attempted. - // TODO(camsim99): Remove this warning after a stable release has passed: - // https://github.com/flutter/flutter/issues/179274. - if (useSoftwareRendering) { - Log.w( - TAG, - "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/172553 for more information on this breaking change. To migrate, set the " - + FlutterShellArgs.ENABLE_SOFTWARE_RENDERING.metadataKey - + " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.md for more info."); - } else { - // Check manifest for software rendering configuration. - useSoftwareRendering = flutterLoader.getSofwareRenderingEnabledViaManifest(); - } - flutterEngine.getPlatformViewsController().setSoftwareRendering(useSoftwareRendering); // Activate the PlatformViewsController. This must happen before any plugins attempt diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 6463f8310e8..697ecc742d7 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -4,434 +4,207 @@ package io.flutter.embedding.engine; -import androidx.annotation.VisibleForTesting; +import android.content.Context; +import android.content.Intent; +import androidx.annotation.NonNull; import java.util.*; /** - * Arguments that can be delivered to the Flutter shell on Android. + * Arguments that can be delivered to the Flutter shell when it is created. * *

The term "shell" refers to the native code that adapts Flutter to different platforms. * Flutter's Android Java code initializes a native "shell" and passes these arguments to that * native shell when it is initialized. See {@link * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])} * for more information. - * - *

All of these flags map to a flag listed in shell/common/switches.cc, which contains the full - * list of flags that can be set across all platforms. - * - *

These flags can either be set via the manifest metadata in a Flutter component's - * AndroidManifest.xml or via the command line. See the inner {@code Flag} class for the - * specification of how to set each flag via the command line and manifest metadata. - * - *

If the same flag is provided both via command line arguments and via AndroidManifest.xml - * metadata, the command line value will take precedence at runtime. */ -public final class FlutterShellArgs { +@SuppressWarnings({"WeakerAccess", "unused"}) +public class FlutterShellArgs { + public static final String ARG_KEY_TRACE_STARTUP = "trace-startup"; + public static final String ARG_TRACE_STARTUP = "--trace-startup"; + public static final String ARG_KEY_START_PAUSED = "start-paused"; + public static final String ARG_START_PAUSED = "--start-paused"; + public static final String ARG_KEY_DISABLE_SERVICE_AUTH_CODES = "disable-service-auth-codes"; + public static final String ARG_DISABLE_SERVICE_AUTH_CODES = "--disable-service-auth-codes"; + public static final String ARG_KEY_ENDLESS_TRACE_BUFFER = "endless-trace-buffer"; + public static final String ARG_ENDLESS_TRACE_BUFFER = "--endless-trace-buffer"; + public static final String ARG_KEY_USE_TEST_FONTS = "use-test-fonts"; + public static final String ARG_USE_TEST_FONTS = "--use-test-fonts"; + public static final String ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling"; + public static final String ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling"; + public static final String ARG_KEY_PROFILE_STARTUP = "profile-startup"; + public static final String ARG_PROFILE_STARTUP = "--profile-startup"; + public static final String ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering"; + public static final String ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering"; + public static final String ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering"; + public static final String ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering"; + public static final String ARG_KEY_TRACE_SKIA = "trace-skia"; + public static final String ARG_TRACE_SKIA = "--trace-skia"; + public static final String ARG_KEY_TRACE_SKIA_ALLOWLIST = "trace-skia-allowlist"; + public static final String ARG_TRACE_SKIA_ALLOWLIST = "--trace-skia-allowlist="; + public static final String ARG_KEY_TRACE_SYSTRACE = "trace-systrace"; + public static final String ARG_TRACE_SYSTRACE = "--trace-systrace"; + public static final String ARG_KEY_TRACE_TO_FILE = "trace-to-file"; + public static final String ARG_TRACE_TO_FILE = "--trace-to-file"; + public static final String ARG_KEY_PROFILE_MICROTASKS = "profile-microtasks"; + public static final String ARG_PROFILE_MICROTASKS = "--profile-microtasks"; + public static final String ARG_KEY_TOGGLE_IMPELLER = "enable-impeller"; + public static final String ARG_ENABLE_IMPELLER = "--enable-impeller=true"; + public static final String ARG_DISABLE_IMPELLER = "--enable-impeller=false"; + public static final String ARG_KEY_ENABLE_VULKAN_VALIDATION = "enable-vulkan-validation"; + public static final String ARG_ENABLE_VULKAN_VALIDATION = "--enable-vulkan-validation"; + public static final String ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = + "dump-skp-on-shader-compilation"; + public static final String ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = + "--dump-skp-on-shader-compilation"; + public static final String ARG_KEY_CACHE_SKSL = "cache-sksl"; + public static final String ARG_CACHE_SKSL = "--cache-sksl"; + public static final String ARG_KEY_PURGE_PERSISTENT_CACHE = "purge-persistent-cache"; + public static final String ARG_PURGE_PERSISTENT_CACHE = "--purge-persistent-cache"; + public static final String ARG_KEY_VERBOSE_LOGGING = "verbose-logging"; + public static final String ARG_VERBOSE_LOGGING = "--verbose-logging"; + public static final String ARG_KEY_VM_SERVICE_PORT = "vm-service-port"; + public static final String ARG_VM_SERVICE_PORT = "--vm-service-port="; + public static final String ARG_KEY_DART_FLAGS = "dart-flags"; + public static final String ARG_DART_FLAGS = "--dart-flags"; - private FlutterShellArgs() {} + @NonNull + public static FlutterShellArgs fromIntent(@NonNull Intent intent) { + // Before adding more entries to this list, consider that arbitrary + // Android applications can generate intents with extra data and that + // there are many security-sensitive args in the binary. + ArrayList args = new ArrayList<>(); - /** Represents a Flutter shell flag that can be set via manifest metadata or command line. */ - public static class Flag { - /** The command line argument used to specify the flag. */ - public final String commandLineArgument; - - /** - * The metadata key name used to specify the flag in AndroidManifest.xml. - * - *

To specify a flag in a manifest, it should be prefixed with {@code - * io.flutter.embedding.android.}. This is enforced to avoid potential naming collisions with - * other metadata keys. The only exception are flags that have already been deprecated. - */ - public final String metadataKey; - - /** Whether this flag is allowed to be set in release mode. */ - public final boolean allowedInRelease; - - /** - * Creates a new Flutter shell flag that is not allowed in release mode with the default flag - * prefix. - */ - private Flag(String commandLineArgument, String metaDataName) { - this(commandLineArgument, metaDataName, "io.flutter.embedding.android.", false); + if (intent.getBooleanExtra(ARG_KEY_TRACE_STARTUP, false)) { + args.add(ARG_TRACE_STARTUP); } - - /** Creates a new Flutter shell flag with the default flag prefix. */ - private Flag(String commandLineArgument, String metaDataName, boolean allowedInRelease) { - this(commandLineArgument, metaDataName, "io.flutter.embedding.android.", allowedInRelease); + if (intent.getBooleanExtra(ARG_KEY_START_PAUSED, false)) { + args.add(ARG_START_PAUSED); } - - /** - * Creates a new Flutter shell flag. - * - *

{@param allowedInRelease} determines whether or not this flag is allowed in release mode. - * Whenever possible, it is recommended to NOT allow this flag in release mode. Many flags are - * designed for debugging purposes and if enabled in production, could expose sensitive - * application data or make the app vulnerable to malicious actors. - * - *

If creating a flag that will be allowed in release, please leave a comment in the Javadoc - * explaining why it should be allowed in release. - */ - private Flag( - String commandLineArgument, - String metaDataName, - String flagPrefix, - boolean allowedInRelease) { - this.commandLineArgument = commandLineArgument; - this.metadataKey = flagPrefix + metaDataName; - this.allowedInRelease = allowedInRelease; + int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0); + if (vmServicePort > 0) { + args.add(ARG_VM_SERVICE_PORT + vmServicePort); } - - /** Returns true if this flag requires a value to be specified. */ - public boolean hasValue() { - return commandLineArgument.endsWith("="); + if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) { + args.add(ARG_DISABLE_SERVICE_AUTH_CODES); } - } - - // Manifest flags allowed in release mode: - - /** - * Specifies the path to the AOT shared library containing compiled Dart code. - * - *

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. - * - *

This is allowed in release to support the same AOT configuration regardless of build mode. - */ - public static final Flag AOT_SHARED_LIBRARY_NAME = - new Flag("--aot-shared-library-name=", "AOTSharedLibraryName", true); - - /** - * Deprecated flag that specifies the path to the AOT shared library containing compiled Dart - * code. - * - *

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. - * - *

This is allowed in release to specify custom asset locations in production. - */ - public static final Flag FLUTTER_ASSETS_DIR = - new Flag("--flutter-assets-dir=", "FlutterAssetsDir", true); - - /** - * The deprecated flag that sets the directory containing Flutter assets. - * - *

Please use {@link DEPRECATED_FLUTTER_ASSETS_DIR} instead. - */ - @Deprecated - public static final Flag DEPRECATED_FLUTTER_ASSETS_DIR = - new Flag( - "--flutter-assets-dir=", - "flutter-assets-dir", - "io.flutter.embedding.engine.loader.FlutterLoader.", - true); - - /** - * Sets the old generation heap size for the Dart VM in megabytes. - * - *

This is allowed in release for performance tuning. - */ - public static final Flag OLD_GEN_HEAP_SIZE = - new Flag("--old-gen-heap-size=", "OldGenHeapSize", true); - - /** - * Enables or disables the Impeller renderer. - * - *

This is allowed in release to control which rendering backend is used in production. - */ - private static final Flag ENABLE_IMPELLER = - new Flag("--enable-impeller=", "EnableImpeller", true); - - /** - * Specifies the backend to use for Impeller rendering. - * - *

This is allowed in release to select a specific graphics backend for Impeller in production. - */ - private static final Flag IMPELLER_BACKEND = - new Flag("--impeller-backend=", "ImpellerBackend", true); - - /** - * Enables Android SurfaceControl for rendering. - * - *

This is allowed in release to opt-in to this rendering feature in production. - */ - private static final Flag ENABLE_SURFACE_CONTROL = - new Flag("--enable-surface-control", "EnableSurfaceControl", true); - - /** - * Enables the Flutter GPU backend. - * - *

This is allowed in release for developers to use the Flutter GPU backend in production. - */ - private static final Flag ENABLE_FLUTTER_GPU = - new Flag("--enable-flutter-gpu", "EnableFlutterGPU", true); - - /** - * Enables lazy initialization of Impeller shaders. - * - *

This is allowed in release for performance tuning of the Impeller backend. - */ - private static final Flag IMPELLER_LAZY_SHADER_MODE = - new Flag("--impeller-lazy-shader-mode=", "ImpellerLazyShaderInitialization", true); - - /** - * Enables antialiasing for lines in Impeller. - * - *

This is allowed in release to control rendering quality in production. - */ - private static final Flag IMPELLER_ANTIALIAS_LINES = - new Flag("--impeller-antialias-lines", "ImpellerAntialiasLines", true); - - /** - * Specifies the path to the VM snapshot data file. - * - *

This is allowed in release to support different snapshot configurations. - */ - public static final Flag VM_SNAPSHOT_DATA = - new Flag("--vm-snapshot-data=", "VmSnapshotData", true); - - /** - * Specifies the path to the isolate snapshot data file. - * - *

This is allowed in release to support different snapshot configurations. - */ - public static final Flag ISOLATE_SNAPSHOT_DATA = - new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true); - - // Manifest flags NOT allowed in release mode: - - /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ - private static final Flag SKIA_DETERMINISTIC_RENDERING = - new Flag("--skia-deterministic-rendering", "SkiaDeterministicRendering"); - - /** Use Skia software backend for rendering. */ - public static final Flag ENABLE_SOFTWARE_RENDERING = - new Flag("--enable-software-rendering", "EnableSoftwareRendering"); - - /** Use the Ahem test font for font resolution. */ - private static final Flag USE_TEST_FONTS = new Flag("--use-test-fonts", "UseTestFonts"); - - /** Sets the port for the Dart VM Service. */ - private static final Flag VM_SERVICE_PORT = new Flag("--vm-service-port=", "VMServicePort"); - - /** Enables Vulkan validation layers if available. */ - private static final Flag ENABLE_VULKAN_VALIDATION = - new Flag("--enable-vulkan-validation", "EnableVulkanValidation"); - - /** Enables GPU tracing for OpenGL. */ - private static final Flag ENABLE_OPENGL_GPU_TRACING = - new Flag("--enable-opengl-gpu-tracing", "EnableOpenGLGPUTracing"); - - /** Enables GPU tracing for Vulkan. */ - private static final Flag ENABLE_VULKAN_GPU_TRACING = - new Flag("--enable-vulkan-gpu-tracing", "EnableVulkanGPUTracing"); - - /** - * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's - * metadata in the application block in AndroidManifest.xml. Set it to true in to leave the Dart - * VM, set it to false to destroy VM. - * - *

If your want to let your app destroy the last shell and re-create shells more quickly, set - * it to true, otherwise if you want to clean up the memory of the leak VM, set it to false. - * - *

TODO(eggfly): Should it be set to false by default? - * https://github.com/flutter/flutter/issues/96843 - */ - public static final Flag LEAK_VM = new Flag("--leak-vm=", "LeakVM"); - - /** Measures startup time and switches to an endless trace buffer. */ - private static final Flag TRACE_STARTUP = new Flag("--trace-startup", "TraceStartup"); - - /** Pauses Dart code execution at launch until a debugger is attached. */ - private static final Flag START_PAUSED = new Flag("--start-paused", "StartPaused"); - - /** Disables authentication codes for VM service communication. */ - private static final Flag DISABLE_SERVICE_AUTH_CODES = - new Flag("--disable-service-auth-codes", "DisableServiceAuthCodes"); - - /** Enables an endless trace buffer for timeline events. */ - private static final Flag ENDLESS_TRACE_BUFFER = - new Flag("--endless-trace-buffer", "EndlessTraceBuffer"); - - /** Enables Dart profiling for use with DevTools. */ - private static final Flag ENABLE_DART_PROFILING = - new Flag("--enable-dart-profiling", "EnableDartProfiling"); - - /** Discards new profiler samples once the buffer is full. */ - private static final Flag PROFILE_STARTUP = new Flag("--profile-startup", "ProfileStartup"); - - /** Enables tracing of Skia GPU calls. */ - private static final Flag TRACE_SKIA = new Flag("--trace-skia", "TraceSkia"); - - /** Only traces specified Skia event categories. */ - private static final Flag TRACE_SKIA_ALLOWLIST = - new Flag("--trace-skia-allowlist=", "TraceSkiaAllowList"); - - /** Traces to the system tracer on supported platforms. */ - private static final Flag TRACE_SYSTRACE = new Flag("--trace-systrace", "TraceSystrace"); - - /** Writes timeline trace to a file in Perfetto format. */ - private static final Flag TRACE_TO_FILE = new Flag("--trace-to-file=", "TraceToFile"); - - /** Collects and logs information about microtasks. */ - private static final Flag PROFILE_MICROTASKS = - new Flag("--profile-microtasks", "ProfileMicrotasks"); - - /** Dumps SKP files that trigger shader compilations. */ - private static final Flag DUMP_SKP_ON_SHADER_COMPILATION = - new Flag("--dump-skp-on-shader-compilation", "DumpSkpOnShaderCompilation"); - - /** Removes all persistent cache files for debugging. */ - private static final Flag PURGE_PERSISTENT_CACHE = - new Flag("--purge-persistent-cache", "PurgePersistentCache"); - - /** Enables logging at all severity levels. */ - private static final Flag VERBOSE_LOGGING = new Flag("--verbose-logging", "VerboseLogging"); - - /** - * Passes additional flags to the Dart VM. - * - *

All flags provided with this argument are subject to filtering based on a list of allowed - * flags in shell/common/switches.cc. If any flag provided is not allowed, the process will - * immediately terminate. - * - *

Flags should be separated by a space, e.g. "--dart-flags=--flag-1 --flag-2=2". - */ - private static final Flag DART_FLAGS = new Flag("--dart-flags=", "DartFlags"); - - // Deprecated flags: - - /** Disables the merging of the UI and platform threads. */ - @VisibleForTesting - public static final Flag DISABLE_MERGED_PLATFORM_UI_THREAD = - new Flag("--no-enable-merged-platform-ui-thread", "DisableMergedPlatformUIThread"); - - @VisibleForTesting - public static final List ALL_FLAGS = - Collections.unmodifiableList( - Arrays.asList( - VM_SERVICE_PORT, - USE_TEST_FONTS, - ENABLE_SOFTWARE_RENDERING, - SKIA_DETERMINISTIC_RENDERING, - AOT_SHARED_LIBRARY_NAME, - FLUTTER_ASSETS_DIR, - OLD_GEN_HEAP_SIZE, - ENABLE_IMPELLER, - IMPELLER_BACKEND, - ENABLE_SURFACE_CONTROL, - ENABLE_FLUTTER_GPU, - IMPELLER_LAZY_SHADER_MODE, - IMPELLER_ANTIALIAS_LINES, - VM_SNAPSHOT_DATA, - ISOLATE_SNAPSHOT_DATA, - ENABLE_VULKAN_VALIDATION, - ENABLE_OPENGL_GPU_TRACING, - ENABLE_VULKAN_GPU_TRACING, - LEAK_VM, - TRACE_STARTUP, - START_PAUSED, - DISABLE_SERVICE_AUTH_CODES, - ENDLESS_TRACE_BUFFER, - ENABLE_DART_PROFILING, - PROFILE_STARTUP, - TRACE_SKIA, - TRACE_SKIA_ALLOWLIST, - TRACE_SYSTRACE, - TRACE_TO_FILE, - PROFILE_MICROTASKS, - DUMP_SKP_ON_SHADER_COMPILATION, - PURGE_PERSISTENT_CACHE, - VERBOSE_LOGGING, - DART_FLAGS, - DISABLE_MERGED_PLATFORM_UI_THREAD, - DEPRECATED_AOT_SHARED_LIBRARY_NAME, - DEPRECATED_FLUTTER_ASSETS_DIR)); - - // Flags that have been turned off. - private static final List DISABLED_FLAGS = - Collections.unmodifiableList(Arrays.asList(DISABLE_MERGED_PLATFORM_UI_THREAD)); - - // Lookup map for current flags that replace deprecated ones. - private static final Map DEPRECATED_FLAGS_BY_REPLACEMENT = - new HashMap() { - { - put(DEPRECATED_AOT_SHARED_LIBRARY_NAME, AOT_SHARED_LIBRARY_NAME); - put(DEPRECATED_FLUTTER_ASSETS_DIR, FLUTTER_ASSETS_DIR); - } - }; - - // Lookup map for retrieving the Flag corresponding to a specific command line argument. - private static final Map FLAG_BY_COMMAND_LINE_ARG; - - // Lookup map for retrieving the Flag corresponding to a specific metadata key. - private static final Map FLAG_BY_META_DATA_KEY; - - static { - Map map = new HashMap(ALL_FLAGS.size()); - Map metaMap = new HashMap(ALL_FLAGS.size()); - for (Flag flag : ALL_FLAGS) { - map.put(flag.commandLineArgument, flag); - metaMap.put(flag.metadataKey, flag); + if (intent.getBooleanExtra(ARG_KEY_ENDLESS_TRACE_BUFFER, false)) { + args.add(ARG_ENDLESS_TRACE_BUFFER); } - FLAG_BY_COMMAND_LINE_ARG = Collections.unmodifiableMap(map); - FLAG_BY_META_DATA_KEY = Collections.unmodifiableMap(metaMap); - } - - /** Looks up a {@link Flag} by its metadataKey. */ - public static Flag getFlagByMetadataKey(String key) { - Flag flag = FLAG_BY_META_DATA_KEY.get(key); - Flag replacementFlag = getReplacementFlagIfDeprecated(flag); - return replacementFlag != null ? replacementFlag : flag; - } - - /** Looks up a {@link Flag} by its commandLineArgument. */ - public static Flag getFlagByCommandLineArgument(String arg) { - int equalsIndex = arg.indexOf('='); - Flag flag = - FLAG_BY_COMMAND_LINE_ARG.get(equalsIndex == -1 ? arg : arg.substring(0, equalsIndex + 1)); - Flag replacementFlag = getReplacementFlagIfDeprecated(flag); - return replacementFlag != null ? replacementFlag : flag; - } - - /** - * Looks up a {@link Flag} by its Intent key. - * - *

Previously, the Intent keys were used to set Flutter shell arguments via Intent. The Intent - * keys match the command line argument without the "--" prefix and "=" suffix if the argument - * takes a value. - */ - public static Flag getFlagFromIntentKey(String intentKey) { - for (Flag flag : ALL_FLAGS) { - String commandLineArg = flag.commandLineArgument; - String key = commandLineArg.startsWith("--") ? commandLineArg.substring(2) : commandLineArg; - if (key.endsWith("=")) { - key = key.substring(0, key.length() - 1); - } - if (key.equals(intentKey)) { - return flag; + if (intent.getBooleanExtra(ARG_KEY_USE_TEST_FONTS, false)) { + args.add(ARG_USE_TEST_FONTS); + } + if (intent.getBooleanExtra(ARG_KEY_ENABLE_DART_PROFILING, false)) { + args.add(ARG_ENABLE_DART_PROFILING); + } + if (intent.getBooleanExtra(ARG_KEY_PROFILE_STARTUP, false)) { + args.add(ARG_PROFILE_STARTUP); + } + if (intent.getBooleanExtra(ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)) { + args.add(ARG_ENABLE_SOFTWARE_RENDERING); + } + if (intent.getBooleanExtra(ARG_KEY_SKIA_DETERMINISTIC_RENDERING, false)) { + args.add(ARG_SKIA_DETERMINISTIC_RENDERING); + } + if (intent.getBooleanExtra(ARG_KEY_TRACE_SKIA, false)) { + args.add(ARG_TRACE_SKIA); + } + String traceSkiaAllowlist = intent.getStringExtra(ARG_KEY_TRACE_SKIA_ALLOWLIST); + if (traceSkiaAllowlist != null) { + args.add(ARG_TRACE_SKIA_ALLOWLIST + traceSkiaAllowlist); + } + if (intent.getBooleanExtra(ARG_KEY_TRACE_SYSTRACE, false)) { + args.add(ARG_TRACE_SYSTRACE); + } + if (intent.hasExtra(ARG_KEY_TRACE_TO_FILE)) { + args.add(ARG_TRACE_TO_FILE + "=" + intent.getStringExtra(ARG_KEY_TRACE_TO_FILE)); + } + if (intent.hasExtra(ARG_KEY_PROFILE_MICROTASKS)) { + args.add(ARG_PROFILE_MICROTASKS); + } + if (intent.hasExtra(ARG_KEY_TOGGLE_IMPELLER)) { + if (intent.getBooleanExtra(ARG_KEY_TOGGLE_IMPELLER, false)) { + args.add(ARG_ENABLE_IMPELLER); + } else { + args.add(ARG_DISABLE_IMPELLER); } } - return null; + if (intent.getBooleanExtra(ARG_KEY_ENABLE_VULKAN_VALIDATION, false)) { + args.add(ARG_ENABLE_VULKAN_VALIDATION); + } + if (intent.getBooleanExtra(ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, false)) { + args.add(ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION); + } + if (intent.getBooleanExtra(ARG_KEY_CACHE_SKSL, false)) { + args.add(ARG_CACHE_SKSL); + } + if (intent.getBooleanExtra(ARG_KEY_PURGE_PERSISTENT_CACHE, false)) { + args.add(ARG_PURGE_PERSISTENT_CACHE); + } + if (intent.getBooleanExtra(ARG_KEY_VERBOSE_LOGGING, false)) { + args.add(ARG_VERBOSE_LOGGING); + } + + // NOTE: all flags provided with this argument are subject to filtering + // based on a list of allowed flags in shell/common/switches.cc. If any + // flag provided is not allowed, the process will immediately terminate. + if (intent.hasExtra(ARG_KEY_DART_FLAGS)) { + args.add(ARG_DART_FLAGS + "=" + intent.getStringExtra(ARG_KEY_DART_FLAGS)); + } + + return new FlutterShellArgs(args); } - /** Returns whether or not a flag is disabled and should raise an exception if used. */ - public static boolean isDisabled(Flag flag) { - return DISABLED_FLAGS.contains(flag); + @NonNull private Set args; + + /** + * Creates a set of Flutter shell arguments from a given {@code String[]} array. The given + * arguments are automatically de-duplicated. + */ + public FlutterShellArgs(@NonNull String[] args) { + this.args = new HashSet<>(Arrays.asList(args)); } - /** Returns the replacement flag of that given if it is deprecated. */ - public static Flag getReplacementFlagIfDeprecated(Flag flag) { - return DEPRECATED_FLAGS_BY_REPLACEMENT.get(flag); + /** + * Creates a set of Flutter shell arguments from a given {@code List}. The given arguments + * are automatically de-duplicated. + */ + public FlutterShellArgs(@NonNull List args) { + this.args = new HashSet<>(args); + } + + /** Creates a set of Flutter shell arguments from a given {@code Set}. */ + public FlutterShellArgs(@NonNull Set args) { + this.args = new HashSet<>(args); + } + + /** + * Adds the given {@code arg} to this set of arguments. + * + * @param arg argument to add + */ + public void add(@NonNull String arg) { + args.add(arg); + } + + /** + * Removes the given {@code arg} from this set of arguments. + * + * @param arg argument to remove + */ + public void remove(@NonNull String arg) { + args.remove(arg); + } + + /** + * Returns a new {@code String[]} array which contains each of the arguments within this {@code + * FlutterShellArgs}. + * + * @return array of arguments + */ + @NonNull + public String[] toArray() { + String[] argsArray = new String[args.size()]; + return args.toArray(argsArray); } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java deleted file mode 100644 index 81ffc72fb16..00000000000 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.embedding.engine; - -import android.content.Intent; -import androidx.annotation.NonNull; -import java.util.*; - -/** - * Arguments that can be delivered to the Flutter shell on Android as Intent extras. - * - *

The term "shell" refers to the native code that adapts Flutter to different platforms. - * Flutter's Android Java code initializes a native "shell" and passes these arguments to that - * native "shell" when it is initialized. See {@link - * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])} - * for more information. - * - *

All of these flags map to a flag listed in shell/common/switches.cc, which contains the full - * list of flags that can be set across all platforms. - * - *

These flags are preferably set via the manifest metadata in a Flutter component's - * AndroidManifest.xml or via the command line for security purposes as Intent extras may expose - * sensitive information to malicious actors. See {@link FlutterShellArgs} for the specification of - * how to set each flag via the command line and manifest metadata. - */ -// TODO(camsim99): Delete this class when support for setting engine shell arguments via Intent -// is no longer supported. See https://github.com/flutter/flutter/issues/180686. -public final class FlutterShellArgsIntentUtils { - - private FlutterShellArgsIntentUtils() {} - - public static final String ARG_KEY_TRACE_STARTUP = "trace-startup"; - public static final String ARG_TRACE_STARTUP = "--trace-startup"; - public static final String ARG_KEY_START_PAUSED = "start-paused"; - public static final String ARG_START_PAUSED = "--start-paused"; - public static final String ARG_KEY_DISABLE_SERVICE_AUTH_CODES = "disable-service-auth-codes"; - public static final String ARG_DISABLE_SERVICE_AUTH_CODES = "--disable-service-auth-codes"; - public static final String ARG_KEY_ENDLESS_TRACE_BUFFER = "endless-trace-buffer"; - public static final String ARG_ENDLESS_TRACE_BUFFER = "--endless-trace-buffer"; - public static final String ARG_KEY_USE_TEST_FONTS = "use-test-fonts"; - public static final String ARG_USE_TEST_FONTS = "--use-test-fonts"; - public static final String ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling"; - public static final String ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling"; - public static final String ARG_KEY_PROFILE_STARTUP = "profile-startup"; - public static final String ARG_PROFILE_STARTUP = "--profile-startup"; - public static final String ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering"; - public static final String ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering"; - public static final String ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering"; - public static final String ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering"; - public static final String ARG_KEY_TRACE_SKIA = "trace-skia"; - public static final String ARG_TRACE_SKIA = "--trace-skia"; - public static final String ARG_KEY_TRACE_SKIA_ALLOWLIST = "trace-skia-allowlist"; - public static final String ARG_TRACE_SKIA_ALLOWLIST = "--trace-skia-allowlist="; - public static final String ARG_KEY_TRACE_SYSTRACE = "trace-systrace"; - public static final String ARG_TRACE_SYSTRACE = "--trace-systrace"; - public static final String ARG_KEY_TRACE_TO_FILE = "trace-to-file"; - public static final String ARG_TRACE_TO_FILE = "--trace-to-file"; - public static final String ARG_KEY_PROFILE_MICROTASKS = "profile-microtasks"; - public static final String ARG_PROFILE_MICROTASKS = "--profile-microtasks"; - public static final String ARG_KEY_TOGGLE_IMPELLER = "enable-impeller"; - public static final String ARG_ENABLE_IMPELLER = "--enable-impeller=true"; - public static final String ARG_DISABLE_IMPELLER = "--enable-impeller=false"; - public static final String ARG_KEY_ENABLE_VULKAN_VALIDATION = "enable-vulkan-validation"; - public static final String ARG_ENABLE_VULKAN_VALIDATION = "--enable-vulkan-validation"; - public static final String ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = - "dump-skp-on-shader-compilation"; - public static final String ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = - "--dump-skp-on-shader-compilation"; - public static final String ARG_KEY_CACHE_SKSL = "cache-sksl"; - public static final String ARG_CACHE_SKSL = "--cache-sksl"; - public static final String ARG_KEY_PURGE_PERSISTENT_CACHE = "purge-persistent-cache"; - public static final String ARG_PURGE_PERSISTENT_CACHE = "--purge-persistent-cache"; - public static final String ARG_KEY_VERBOSE_LOGGING = "verbose-logging"; - public static final String ARG_VERBOSE_LOGGING = "--verbose-logging"; - public static final String ARG_KEY_VM_SERVICE_PORT = "vm-service-port"; - public static final String ARG_VM_SERVICE_PORT = "--vm-service-port="; - public static final String ARG_KEY_DART_FLAGS = "dart-flags"; - public static final String ARG_DART_FLAGS = "--dart-flags"; - - @NonNull - public static String[] getFlutterShellCommandLineArgs(@NonNull Intent intent) { - // Before adding more entries to this list, consider that arbitrary - // Android applications can generate intents with extra data and that - // there are many security-sensitive args in the binary. - ArrayList args = new ArrayList<>(); - - if (intent.getBooleanExtra(ARG_KEY_TRACE_STARTUP, false)) { - args.add(ARG_TRACE_STARTUP); - } - if (intent.getBooleanExtra(ARG_KEY_START_PAUSED, false)) { - args.add(ARG_START_PAUSED); - } - int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0); - if (vmServicePort > 0) { - args.add(ARG_VM_SERVICE_PORT + vmServicePort); - } - if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) { - args.add(ARG_DISABLE_SERVICE_AUTH_CODES); - } - if (intent.getBooleanExtra(ARG_KEY_ENDLESS_TRACE_BUFFER, false)) { - args.add(ARG_ENDLESS_TRACE_BUFFER); - } - if (intent.getBooleanExtra(ARG_KEY_USE_TEST_FONTS, false)) { - args.add(ARG_USE_TEST_FONTS); - } - if (intent.getBooleanExtra(ARG_KEY_ENABLE_DART_PROFILING, false)) { - args.add(ARG_ENABLE_DART_PROFILING); - } - if (intent.getBooleanExtra(ARG_KEY_PROFILE_STARTUP, false)) { - args.add(ARG_PROFILE_STARTUP); - } - if (intent.getBooleanExtra(ARG_KEY_ENABLE_SOFTWARE_RENDERING, false)) { - args.add(ARG_ENABLE_SOFTWARE_RENDERING); - } - if (intent.getBooleanExtra(ARG_KEY_SKIA_DETERMINISTIC_RENDERING, false)) { - args.add(ARG_SKIA_DETERMINISTIC_RENDERING); - } - if (intent.getBooleanExtra(ARG_KEY_TRACE_SKIA, false)) { - args.add(ARG_TRACE_SKIA); - } - String traceSkiaAllowlist = intent.getStringExtra(ARG_KEY_TRACE_SKIA_ALLOWLIST); - if (traceSkiaAllowlist != null) { - args.add(ARG_TRACE_SKIA_ALLOWLIST + traceSkiaAllowlist); - } - if (intent.getBooleanExtra(ARG_KEY_TRACE_SYSTRACE, false)) { - args.add(ARG_TRACE_SYSTRACE); - } - if (intent.hasExtra(ARG_KEY_TRACE_TO_FILE)) { - args.add(ARG_TRACE_TO_FILE + "=" + intent.getStringExtra(ARG_KEY_TRACE_TO_FILE)); - } - if (intent.hasExtra(ARG_KEY_PROFILE_MICROTASKS)) { - args.add(ARG_PROFILE_MICROTASKS); - } - if (intent.hasExtra(ARG_KEY_TOGGLE_IMPELLER)) { - if (intent.getBooleanExtra(ARG_KEY_TOGGLE_IMPELLER, false)) { - args.add(ARG_ENABLE_IMPELLER); - } else { - args.add(ARG_DISABLE_IMPELLER); - } - } - if (intent.getBooleanExtra(ARG_KEY_ENABLE_VULKAN_VALIDATION, false)) { - args.add(ARG_ENABLE_VULKAN_VALIDATION); - } - if (intent.getBooleanExtra(ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, false)) { - args.add(ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION); - } - if (intent.getBooleanExtra(ARG_KEY_CACHE_SKSL, false)) { - args.add(ARG_CACHE_SKSL); - } - if (intent.getBooleanExtra(ARG_KEY_PURGE_PERSISTENT_CACHE, false)) { - args.add(ARG_PURGE_PERSISTENT_CACHE); - } - if (intent.getBooleanExtra(ARG_KEY_VERBOSE_LOGGING, false)) { - args.add(ARG_VERBOSE_LOGGING); - } - - // All flags provided with this argument are subject to filtering - // based on a list of allowed flags in shell/common/switches.cc. If any - // flag provided is not allowed, the process will immediately terminate. - if (intent.hasExtra(ARG_KEY_DART_FLAGS)) { - args.add(ARG_DART_FLAGS + "=" + intent.getStringExtra(ARG_KEY_DART_FLAGS)); - } - - String[] argsArray = new String[args.size()]; - return args.toArray(argsArray); - } -} diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java index fb682cdbfcf..24c50298358 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java @@ -10,19 +10,24 @@ import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.os.Bundle; import androidx.annotation.NonNull; -import io.flutter.embedding.engine.FlutterShellArgs; import java.io.IOException; import org.json.JSONArray; import org.xmlpull.v1.XmlPullParserException; /** Loads application information given a Context. */ public final class ApplicationInfoLoader { - // TODO(camsim99): Remove support for these flags: - // https://github.com/flutter/flutter/issues/179276. - // AndroidManifest.xml metadata keys for setting internal respective Flutter configuration values. + // XML Attribute keys supported in AndroidManifest.xml + public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME = + FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME; + public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY = + FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY; + public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY = + FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY; + public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY = + FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY; public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy"; public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY = - "io.flutter.automatically-register-plugins"; + "io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY; @NonNull private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) { @@ -42,20 +47,6 @@ public final class ApplicationInfoLoader { return metadata.getString(key, null); } - private static String getStringWithFallback(Bundle metadata, String key, String fallbackKey) { - if (metadata == null) { - return null; - } - - String metadataString = metadata.getString(key, null); - - if (metadataString == null) { - metadataString = metadata.getString(fallbackKey); - } - - return metadataString; - } - private static boolean getBoolean(Bundle metadata, String key, boolean defaultValue) { if (metadata == null) { return defaultValue; @@ -155,21 +146,11 @@ public final class ApplicationInfoLoader { @NonNull public static FlutterApplicationInfo load(@NonNull Context applicationContext) { ApplicationInfo appInfo = getApplicationInfo(applicationContext); - - // TODO(camsim99): Remove support for DEPRECATED_AOT_SHARED_LIBRARY_NAME and - // DEPRECATED_FLUTTER_ASSETS_DIR - // when all usage of the deprecated names has been removed. return new FlutterApplicationInfo( - getStringWithFallback( - appInfo.metaData, - FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME.metadataKey, - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey), - getString(appInfo.metaData, FlutterShellArgs.VM_SNAPSHOT_DATA.metadataKey), - getString(appInfo.metaData, FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.metadataKey), - getStringWithFallback( - appInfo.metaData, - FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR.metadataKey, - FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey), + getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME), + getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY), + getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY), + getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY), getNetworkPolicy(appInfo, applicationContext), appInfo.nativeLibraryDir, getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true)); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index c4897f83a9b..07df17b5d85 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -23,7 +23,6 @@ import io.flutter.BuildConfig; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.util.HandlerCompat; import io.flutter.util.PathUtils; import io.flutter.util.TraceSection; @@ -34,19 +33,54 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; /** Finds Flutter resources in an application APK and also loads Flutter's native library. */ public class FlutterLoader { private static final String TAG = "FlutterLoader"; - // Flags to only be set internally by default. Match values in flutter::switches. - private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path"; - private static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME = - "aot-vmservice-shared-library-name"; + private static final String OLD_GEN_HEAP_SIZE_META_DATA_KEY = + "io.flutter.embedding.android.OldGenHeapSize"; + private static final String ENABLE_IMPELLER_META_DATA_KEY = + "io.flutter.embedding.android.EnableImpeller"; + private static final String ENABLE_VULKAN_VALIDATION_META_DATA_KEY = + "io.flutter.embedding.android.EnableVulkanValidation"; + private static final String IMPELLER_BACKEND_META_DATA_KEY = + "io.flutter.embedding.android.ImpellerBackend"; + private static final String IMPELLER_OPENGL_GPU_TRACING_DATA_KEY = + "io.flutter.embedding.android.EnableOpenGLGPUTracing"; + private static final String IMPELLER_VULKAN_GPU_TRACING_DATA_KEY = + "io.flutter.embedding.android.EnableVulkanGPUTracing"; + private static final String DISABLE_MERGED_PLATFORM_UI_THREAD_KEY = + "io.flutter.embedding.android.DisableMergedPlatformUIThread"; + private static final String ENABLE_SURFACE_CONTROL = + "io.flutter.embedding.android.EnableSurfaceControl"; + private static final String ENABLE_FLUTTER_GPU = "io.flutter.embedding.android.EnableFlutterGPU"; + private static final String IMPELLER_LAZY_SHADER_MODE = + "io.flutter.embedding.android.ImpellerLazyShaderInitialization"; + private static final String IMPELLER_ANTIALIAS_LINES = + "io.flutter.embedding.android.ImpellerAntialiasLines"; - // Flag set for generating GeneratedPluginRegistrant.java. - private static final String FLUTTER_EMBEDDING_KEY = "flutterEmbedding"; + /** + * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's + * meta-data in in AndroidManifest.xml. Set it to true in to leave the Dart VM, + * set it to false to destroy VM. + * + *

If your want to let your app destroy the last shell and re-create shells more quickly, set + * it to true, otherwise if you want to clean up the memory of the leak VM, set it to false. + * + *

TODO(eggfly): Should it be set to false by default? + * https://github.com/flutter/flutter/issues/96843 + */ + private static final String LEAK_VM_META_DATA_KEY = "io.flutter.embedding.android.LeakVM"; + + // Must match values in flutter::switches + static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name"; + static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME = "aot-vmservice-shared-library-name"; + static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path"; + static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data"; + static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data"; + static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir"; + static final String AUTOMATICALLY_REGISTER_PLUGINS_KEY = "automatically-register-plugins"; // Resource names used for components of the precompiled snapshot. private static final String DEFAULT_LIBRARY = "libflutter.so"; @@ -55,7 +89,8 @@ public class FlutterLoader { private static FlutterLoader instance; - private boolean enableSoftwareRendering = false; + @VisibleForTesting + static final String aotSharedLibraryNameFlag = "--" + AOT_SHARED_LIBRARY_NAME + "="; /** * Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link @@ -260,21 +295,6 @@ public class FlutterLoader { */ public void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args) { - ensureInitializationComplete(applicationContext, args, BuildConfig.RELEASE); - } - - /** - * Blocks until initialization of the native system has completed. - * - *

Calling this method multiple times has no effect. - * - * @param applicationContext The Android application context. - * @param args Flags sent to the Flutter runtime. - * @param isRelease Whether or not the Flutter component is running in release mode. - */ - @VisibleForTesting - void ensureInitializationComplete( - @NonNull Context applicationContext, @Nullable String[] args, @NonNull boolean isRelease) { if (initialized) { return; } @@ -291,183 +311,59 @@ public class FlutterLoader { InitResult result = initResultFuture.get(); List shellArgs = new ArrayList<>(); - - // Add engine flags for which defaults set internally take precedent. shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); + shellArgs.add( "--icu-native-lib-path=" + flutterApplicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY); - // Add AOT shared library name flag if set via the command line. This flag, - // unlike others, gives precedence to the first occurrence found in shellArgs, - // so we must add it here before adding flags from the manifest. if (args != null) { for (String arg : args) { - if (arg.startsWith(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument)) { - // Perform security check for path containing application's compiled Dart - // code and potentially user-provided compiled native code. - String aotSharedLibraryPath = - arg.substring( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length()); - maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs); - break; - } - } - } - - // Add engine flags provided by metadata in the application manifest. These settings will take - // precedent over any defaults set below, but will be overridden if additionally set by the - // command line. - ApplicationInfo applicationInfo = - applicationContext - .getPackageManager() - .getApplicationInfo( - applicationContext.getPackageName(), PackageManager.GET_META_DATA); - Bundle applicationMetaData = applicationInfo.metaData; - final AtomicBoolean oldGenHeapSizeSet = new AtomicBoolean(false); - final AtomicBoolean isLeakVMSet = new AtomicBoolean(false); - - if (applicationMetaData != null) { - applicationMetaData.keySet().stream() - .filter(metadataKey -> !metadataKey.equals(FLUTTER_EMBEDDING_KEY)) - .forEach( - metadataKey -> { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByMetadataKey(metadataKey); - if (flag == null) { - // Manifest flag was not recognized. - Log.w( - TAG, - "Flag with metadata key " - + metadataKey - + " is not recognized. Please ensure that the flag is defined in the FlutterShellArgs."); - return; - } else if (FlutterShellArgs.isDisabled(flag)) { - // Do not allow disabled flags. - throw new IllegalArgumentException( - metadataKey - + " is disabled and no longer allowed. Please remove this flag from your application manifest."); - } else if (FlutterShellArgs.getReplacementFlagIfDeprecated(flag) != null) { - Log.w( - TAG, - "If you are trying to specify " - + flag.metadataKey - + " in your application manifest, please make sure to use the new metadata key name: " - + FlutterShellArgs.getReplacementFlagIfDeprecated(flag).metadataKey); - } else if (!flag.allowedInRelease && isRelease) { - // Manifest flag is not allowed in release builds. - Log.w( - TAG, - "Flag with metadata key " - + metadataKey - + " is not allowed in release builds and will be ignored if specified in the application manifest or via the command line."); - return; - } - - // Handle special cases for specific flags. - if (flag == FlutterShellArgs.OLD_GEN_HEAP_SIZE) { - // Mark if old gen heap size is set to track whether or not to set default - // internally. - oldGenHeapSizeSet.set(true); - } else if (flag == FlutterShellArgs.LEAK_VM) { - // Mark if leak VM is set to track whether or not to set default internally. - isLeakVMSet.set(true); - } else if (flag == FlutterShellArgs.ENABLE_SOFTWARE_RENDERING) { - // Enabling software rendering impacts platform views, so save this value - // so that the PlatformViewsController can be properly configured. - enableSoftwareRendering = - applicationMetaData.getBoolean( - FlutterShellArgs.ENABLE_SOFTWARE_RENDERING.metadataKey, false); - } else if (flag == FlutterShellArgs.AOT_SHARED_LIBRARY_NAME - || flag == FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME) { - // Perform security check for path containing application's compiled Dart - // code and potentially user-provided compiled native code. - String aotSharedLibraryPath = applicationMetaData.getString(metadataKey); - maybeAddAotSharedLibraryNameArg( - applicationContext, aotSharedLibraryPath, shellArgs); - return; - } - - // Add flag to shell args. - String arg = flag.commandLineArgument; - if (flag.hasValue()) { - Object valueObj = applicationMetaData.get(metadataKey); - String value = valueObj != null ? valueObj.toString() : null; - if (value == null) { - Log.w( - TAG, - "Flag with metadata key " - + metadataKey - + " requires a value, but no value was found. Please ensure that the value is a string."); - return; - } - arg += value; - } - - shellArgs.add(arg); - }); - } - - // Add any remaining engine flags provided by the command line. These settings will take - // precedent over any flag settings specified by application manifest - // metadata and any defaults set below. - if (args != null) { - for (String arg : args) { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByCommandLineArgument(arg); - if (flag == null) { - // Command line flag was not recognized. - Log.w( - TAG, - "Command line argument " - + arg - + "is not recognized. Please ensure that the flag is defined in the FlutterShellArgs."); - continue; - } else if (flag.equals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME)) { - // This flag has already been handled. - continue; - } else if (!flag.allowedInRelease && isRelease) { - // Flag is not allowed in release builds. - Log.w( - TAG, - "Command line argument " - + arg - + " is not allowed in release builds and will be ignored if specified in the application manifest or via the command line."); - continue; + // Perform security check for path containing application's compiled Dart code and + // potentially user-provided compiled native code. + if (arg.startsWith(aotSharedLibraryNameFlag)) { + String safeAotSharedLibraryNameFlag = + getSafeAotSharedLibraryNameFlag(applicationContext, arg); + if (safeAotSharedLibraryNameFlag != null) { + arg = safeAotSharedLibraryNameFlag; + } else { + // If the library path is not safe, we will skip adding this argument. + Log.w( + TAG, + "Skipping unsafe AOT shared library name flag: " + + arg + + ". Please ensure that the library is vetted and placed in your application's internal storage."); + continue; + } } + // TODO(camsim99): This is a dangerous pattern that blindly allows potentially malicious + // arguments to be used for engine initialization and should be fixed. See + // https://github.com/flutter/flutter/issues/172553. shellArgs.add(arg); } } - // Add engine flags set by default internally. Some of these settings can be overridden - // by command line args or application manifest metadata. - String kernelPath = null; if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { String snapshotAssetPath = result.dataDirPath + File.separator + flutterApplicationInfo.flutterAssetsDir; kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB; shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath); + shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.vmSnapshotData); shellArgs.add( - FlutterShellArgs.VM_SNAPSHOT_DATA.commandLineArgument - + flutterApplicationInfo.vmSnapshotData); - shellArgs.add( - FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.commandLineArgument - + flutterApplicationInfo.isolateSnapshotData); + "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData); } else { - // Add default AOT shared library name arg. Note that if a different library - // is set in the manifest, that value will take precendence and the default - // libraries will be used as fallbacks in the order that they are added. - shellArgs.add( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument - + flutterApplicationInfo.aotSharedLibraryName); + // Add default AOT shared library name arg. + shellArgs.add(aotSharedLibraryNameFlag + flutterApplicationInfo.aotSharedLibraryName); // Some devices cannot load the an AOT shared library based on the library name // with no directory path. So, we provide a fully qualified path to the default library // as a workaround for devices where that fails. shellArgs.add( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + aotSharedLibraryNameFlag + flutterApplicationInfo.nativeLibraryDir + File.separator + flutterApplicationInfo.aotSharedLibraryName); @@ -488,17 +384,23 @@ public class FlutterLoader { shellArgs.add("--log-tag=" + settings.getLogTag()); } - if (!oldGenHeapSizeSet.get()) { - // Default to half of total memory. + ApplicationInfo applicationInfo = + applicationContext + .getPackageManager() + .getApplicationInfo( + applicationContext.getPackageName(), PackageManager.GET_META_DATA); + Bundle metaData = applicationInfo.metaData; + int oldGenHeapSizeMegaBytes = + metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0; + if (oldGenHeapSizeMegaBytes == 0) { + // default to half of total memory. ActivityManager activityManager = (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memInfo); - int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); - shellArgs.add( - FlutterShellArgs.OLD_GEN_HEAP_SIZE.commandLineArgument - + String.valueOf(oldGenHeapSizeMegaBytes)); + oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); } + shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes); DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; @@ -510,10 +412,49 @@ public class FlutterLoader { shellArgs.add("--prefetched-default-font-manager"); - if (!isLeakVMSet.get()) { - shellArgs.add(FlutterShellArgs.LEAK_VM.commandLineArgument + "true"); + if (metaData != null) { + if (metaData.containsKey(ENABLE_IMPELLER_META_DATA_KEY)) { + if (metaData.getBoolean(ENABLE_IMPELLER_META_DATA_KEY)) { + shellArgs.add("--enable-impeller=true"); + } else { + shellArgs.add("--enable-impeller=false"); + } + } + if (metaData.getBoolean(ENABLE_VULKAN_VALIDATION_META_DATA_KEY, false)) { + shellArgs.add("--enable-vulkan-validation"); + } + if (metaData.getBoolean(IMPELLER_OPENGL_GPU_TRACING_DATA_KEY, false)) { + shellArgs.add("--enable-opengl-gpu-tracing"); + } + if (metaData.getBoolean(IMPELLER_VULKAN_GPU_TRACING_DATA_KEY, false)) { + shellArgs.add("--enable-vulkan-gpu-tracing"); + } + if (metaData.getBoolean(DISABLE_MERGED_PLATFORM_UI_THREAD_KEY, false)) { + throw new IllegalArgumentException( + DISABLE_MERGED_PLATFORM_UI_THREAD_KEY + " is no longer allowed."); + } + if (metaData.getBoolean(ENABLE_FLUTTER_GPU, false)) { + shellArgs.add("--enable-flutter-gpu"); + } + if (metaData.getBoolean(ENABLE_SURFACE_CONTROL, false)) { + shellArgs.add("--enable-surface-control"); + } + + String backend = metaData.getString(IMPELLER_BACKEND_META_DATA_KEY); + if (backend != null) { + shellArgs.add("--impeller-backend=" + backend); + } + if (metaData.getBoolean(IMPELLER_LAZY_SHADER_MODE)) { + shellArgs.add("--impeller-lazy-shader-mode"); + } + if (metaData.getBoolean(IMPELLER_ANTIALIAS_LINES)) { + shellArgs.add("--impeller-antialias-lines"); + } } + final String leakVM = isLeakVM(metaData) ? "true" : "false"; + shellArgs.add("--leak-vm=" + leakVM); + long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; flutterJNI.init( @@ -532,49 +473,10 @@ public class FlutterLoader { } } - /** Adds the AOT shared library name argument to the shell args if the provided path is safe. */ - private void maybeAddAotSharedLibraryNameArg( - @NonNull Context applicationContext, - @NonNull String aotSharedLibraryPath, - @NonNull List shellArgs) { - String safeAotSharedLibraryName = null; - try { - safeAotSharedLibraryName = - getSafeAotSharedLibraryName(applicationContext, aotSharedLibraryPath); - } catch (IOException exception) { - Log.w( - TAG, - "Error while validating AOT shared library name flag: " + aotSharedLibraryPath, - exception); - } - - if (safeAotSharedLibraryName != null) { - shellArgs.add( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + safeAotSharedLibraryName); - } else { - // If the library path is not safe, we will skip adding this argument. - Log.w( - TAG, - "Skipping unsafe AOT shared library name flag: " - + aotSharedLibraryPath - + ". Please ensure that the library is vetted and placed in your application's internal storage."); - } - } - /** - * Returns whether software rendering is enabled. - * - *

{@link #ensureInitializationComplete} must be called first in order to retrieve this value. - * Otherwise, this will return false. - */ - public boolean getSofwareRenderingEnabledViaManifest() { - return enableSoftwareRendering; - } - - /** - * Returns the canonical path to the AOT shared library that the engine will use to load the - * application's Dart code if it lives within a path we consider safe, which is a path within the - * application's internal storage. Otherwise, returns null. + * Returns the AOT shared library name flag with the canonical path to the library that the engine + * will use to load application's Dart code if it lives within a path we consider safe, which is a + * path within the application's internal storage. Otherwise, returns null. * *

If the library lives within the application's internal storage, this means that the * application developer either explicitly placed the library there or set the Android Gradle @@ -582,9 +484,17 @@ public class FlutterLoader { * https://developer.android.com/build/releases/past-releases/agp-4-2-0-release-notes#compress-native-libs-dsl * for more information. */ - private String getSafeAotSharedLibraryName( - @NonNull Context applicationContext, @NonNull String aotSharedLibraryPath) + private String getSafeAotSharedLibraryNameFlag( + @NonNull Context applicationContext, @NonNull String aotSharedLibraryNameArg) throws IOException { + // Isolate AOT shared library path. + if (!aotSharedLibraryNameArg.startsWith(aotSharedLibraryNameFlag)) { + throw new IllegalArgumentException( + "AOT shared library name flag was not specified correctly; please use --aot-shared-library-name=."); + } + String aotSharedLibraryPath = + aotSharedLibraryNameArg.substring(aotSharedLibraryNameFlag.length()); + // Canocalize path for safety analysis. File aotSharedLibraryFile = getFileFromPath(aotSharedLibraryPath); @@ -609,7 +519,7 @@ public class FlutterLoader { boolean isSoFile = aotSharedLibraryPathCanonicalPath.endsWith(".so"); if (livesWithinInternalStorage && isSoFile) { - return aotSharedLibraryPathCanonicalPath; + return aotSharedLibraryNameFlag + aotSharedLibraryPathCanonicalPath; } // If the library does not live within the application's internal storage, we will not use it. Log.e( @@ -625,6 +535,14 @@ public class FlutterLoader { return new File(path); } + private static boolean isLeakVM(@Nullable Bundle metaData) { + final boolean leakVMDefaultValue = true; + if (metaData == null) { + return leakVMDefaultValue; + } + return metaData.getBoolean(LEAK_VM_META_DATA_KEY, leakVMDefaultValue); + } + /** * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background * thread, then invoking {@code callback} on the {@code callbackHandler}. diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 253ecf5005f..ca2e40d1b96 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -40,6 +40,7 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterEngineGroup; import io.flutter.embedding.engine.FlutterEngineGroupCache; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; @@ -88,6 +89,7 @@ public class FlutterActivityAndFragmentDelegateTest { mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class); when(mockHost.getContext()).thenReturn(ctx); when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class)); + when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {})); when(mockHost.getDartEntrypointFunctionName()).thenReturn("main"); when(mockHost.getDartEntrypointArgs()).thenReturn(null); when(mockHost.getAppBundlePath()).thenReturn("/fake/path"); @@ -104,6 +106,7 @@ public class FlutterActivityAndFragmentDelegateTest { mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class); when(mockHost2.getContext()).thenReturn(ctx); when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class)); + when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {})); when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main"); when(mockHost2.getDartEntrypointArgs()).thenReturn(null); when(mockHost2.getAppBundlePath()).thenReturn("/fake/path"); @@ -468,6 +471,8 @@ public class FlutterActivityAndFragmentDelegateTest { activity -> { when(customMockHost.getActivity()).thenReturn(activity); when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class)); + when(customMockHost.getFlutterShellArgs()) + .thenReturn(new FlutterShellArgs(new String[] {})); when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main"); when(customMockHost.getAppBundlePath()).thenReturn("/fake/path"); when(customMockHost.getInitialRoute()).thenReturn("/"); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index ce711b86c3a..6875f8b756b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java @@ -250,7 +250,7 @@ public class FlutterActivityTest { assertNull(flutterActivity.getDartEntrypointLibraryUri()); assertNull(flutterActivity.getDartEntrypointArgs()); assertEquals("/", flutterActivity.getInitialRoute()); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertNull(flutterActivity.getCachedEngineId()); assertTrue(flutterActivity.shouldDestroyEngineWithHost()); @@ -303,7 +303,7 @@ public class FlutterActivityTest { assertEquals("/custom/route", flutterActivity.getInitialRoute()); assertArrayEquals( new String[] {"foo", "bar"}, flutterActivity.getDartEntrypointArgs().toArray()); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertNull(flutterActivity.getCachedEngineId()); assertTrue(flutterActivity.shouldDestroyEngineWithHost()); @@ -328,7 +328,7 @@ public class FlutterActivityTest { assertEquals("my_cached_engine_group", flutterActivity.getCachedEngineGroupId()); assertEquals("custom_entrypoint", flutterActivity.getDartEntrypointFunctionName()); assertEquals("/custom/route", flutterActivity.getInitialRoute()); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertTrue(flutterActivity.shouldDestroyEngineWithHost()); assertNull(flutterActivity.getCachedEngineId()); @@ -393,7 +393,7 @@ public class FlutterActivityTest { Robolectric.buildActivity(FlutterActivity.class, intent); FlutterActivity flutterActivity = activityController.get(); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertEquals("my_cached_engine", flutterActivity.getCachedEngineId()); assertFalse(flutterActivity.shouldDestroyEngineWithHost()); @@ -409,7 +409,7 @@ public class FlutterActivityTest { Robolectric.buildActivity(FlutterActivity.class, intent); FlutterActivity flutterActivity = activityController.get(); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertEquals("my_cached_engine", flutterActivity.getCachedEngineId()); assertTrue(flutterActivity.shouldDestroyEngineWithHost()); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java index 2e733635fcd..a9ac78f4fec 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -31,6 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; @@ -301,8 +302,8 @@ public class FlutterAndroidComponentTest { @NonNull @Override - public String[] getFlutterShellArgs() { - return new String[0]; + public FlutterShellArgs getFlutterShellArgs() { + return new FlutterShellArgs(new String[] {}); } @Nullable diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java index 048b72e7506..581154ed66e 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterFragmentTest.java @@ -69,7 +69,7 @@ public class FlutterFragmentTest { assertNull(fragment.getDartEntrypointLibraryUri()); assertNull(fragment.getDartEntrypointArgs()); assertEquals("/", fragment.getInitialRoute()); - assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertTrue(fragment.shouldAttachEngineToActivity()); assertFalse(fragment.shouldHandleDeeplinking()); assertNull(fragment.getCachedEngineId()); @@ -100,7 +100,7 @@ public class FlutterFragmentTest { assertEquals("package:foo/bar.dart", fragment.getDartEntrypointLibraryUri()); assertEquals("/custom/route", fragment.getInitialRoute()); assertArrayEquals(new String[] {"foo", "bar"}, fragment.getDartEntrypointArgs().toArray()); - assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertFalse(fragment.shouldAttachEngineToActivity()); assertTrue(fragment.shouldHandleDeeplinking()); assertNull(fragment.getCachedEngineId()); @@ -129,7 +129,7 @@ public class FlutterFragmentTest { assertEquals("my_cached_engine_group", fragment.getCachedEngineGroupId()); assertEquals("custom_entrypoint", fragment.getDartEntrypointFunctionName()); assertEquals("/custom/route", fragment.getInitialRoute()); - assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs().toArray()); assertFalse(fragment.shouldAttachEngineToActivity()); assertTrue(fragment.shouldHandleDeeplinking()); assertNull(fragment.getCachedEngineId()); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java index 311d66beba1..091ca42931f 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java @@ -11,19 +11,14 @@ import static org.mockito.Mockito.*; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.ExclusiveAppComponent; -import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugin.platform.PlatformViewsController2; @@ -90,19 +85,6 @@ public class FlutterEngineConnectionRegistryTest { when(flutterEngine.getPlatformViewsControllerDelegator()) .thenReturn(platformViewsControllerDelegator); - PackageManager packageManager = mock(PackageManager.class); - String packageName = "io.flutter.test"; - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.metaData = new Bundle(); - when(context.getPackageName()).thenReturn(packageName); - when(context.getPackageManager()).thenReturn(packageManager); - try { - when(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)) - .thenReturn(applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - fail("Mocking application info threw an exception"); - } - FlutterLoader flutterLoader = mock(FlutterLoader.class); ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); @@ -145,31 +127,41 @@ public class FlutterEngineConnectionRegistryTest { } @Test - public void attachToActivityConfiguresSoftwareRendering() { + public void softwareRendering() { Context context = mock(Context.class); + FlutterEngine flutterEngine = mock(FlutterEngine.class); PlatformViewsController platformViewsController = mock(PlatformViewsController.class); - FlutterLoader flutterLoader = mock(FlutterLoader.class); - ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); - Activity activity = mock(Activity.class); - Lifecycle lifecycle = mock(Lifecycle.class); - - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + PlatformViewsController2 platformViewsController2 = mock(PlatformViewsController2.class); PlatformViewsControllerDelegator platformViewsControllerDelegator = mock(PlatformViewsControllerDelegator.class); when(flutterEngine.getPlatformViewsControllerDelegator()) .thenReturn(platformViewsControllerDelegator); - when(flutterEngine.getDartExecutor()).thenReturn(mock(DartExecutor.class)); - when(flutterEngine.getRenderer()).thenReturn(mock(FlutterRenderer.class)); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2); + + FlutterLoader flutterLoader = mock(FlutterLoader.class); + + ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); + Activity activity = mock(Activity.class); + when(appComponent.getAppComponent()).thenReturn(activity); + + // Test attachToActivity with an Activity that has no Intent. FlutterEngineConnectionRegistry registry = new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null); + registry.attachToActivity(appComponent, mock(Lifecycle.class)); + verify(platformViewsController).setSoftwareRendering(false); - when(flutterLoader.getSofwareRenderingEnabledViaManifest()).thenReturn(true); - when(appComponent.getAppComponent()).thenReturn(activity); - when(activity.getIntent()).thenReturn(mock(Intent.class)); + Intent intent = mock(Intent.class); + when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(false); + when(activity.getIntent()).thenReturn(intent); - registry.attachToActivity(appComponent, lifecycle); + registry.attachToActivity(appComponent, mock(Lifecycle.class)); + verify(platformViewsController, times(2)).setSoftwareRendering(false); + when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(true); + + registry.attachToActivity(appComponent, mock(Lifecycle.class)); verify(platformViewsController).setSoftwareRendering(true); } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java deleted file mode 100644 index 4f5fbfdf96c..00000000000 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package test.io.flutter.embedding.engine; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.content.Intent; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.flutter.embedding.engine.FlutterShellArgsIntentUtils; -import java.util.Arrays; -import java.util.HashSet; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class FlutterShellArgsIntentUtilsTest { - @Test - public void itProcessesShellFlags() { - // Setup the test. - Intent intent = new Intent(); - intent.putExtra("dart-flags", "--observe --no-hot --no-pub"); - intent.putExtra("trace-skia-allowlist", "skia.a,skia.b"); - - // Execute the behavior under test. - String[] args = FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(intent); - HashSet argValues = new HashSet(Arrays.asList(args)); - - // Verify results. - assertEquals(2, argValues.size()); - assertTrue(argValues.contains("--dart-flags=--observe --no-hot --no-pub")); - assertTrue(argValues.contains("--trace-skia-allowlist=skia.a,skia.b")); - } -} diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java index ebd0085c619..0f05dc67f67 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java @@ -2,121 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package io.flutter.embedding.engine; +package test.io.flutter.embedding.engine; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; +import android.content.Intent; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.embedding.engine.FlutterShellArgs; +import java.util.Arrays; +import java.util.HashSet; import org.junit.Test; +import org.junit.runner.RunWith; +@RunWith(AndroidJUnit4.class) public class FlutterShellArgsTest { - @Test - public void allFlags_containsAllFlags() { - // Count the number of declared flags in FlutterShellArgs. - int declaredFlagsCount = 0; - for (Field field : FlutterShellArgs.class.getDeclaredFields()) { - if (FlutterShellArgs.Flag.class.isAssignableFrom(field.getType()) - && Modifier.isStatic(field.getModifiers()) - && Modifier.isFinal(field.getModifiers())) { - declaredFlagsCount++; - } - } + public void itProcessesShellFlags() { + // Setup the test. + Intent intent = new Intent(); + intent.putExtra("dart-flags", "--observe --no-hot --no-pub"); + intent.putExtra("trace-skia-allowlist", "skia.a,skia.b"); - // Check that the number of declared flags matches the size of ALL_FLAGS. - assertEquals( - "If you are adding a new Flag to FlutterShellArgs, please make sure it is added to ALL_FLAGS as well. Otherwise, the flag will be silently ignored when specified.", - declaredFlagsCount, - FlutterShellArgs.ALL_FLAGS.size()); - } + // Execute the behavior under test. + FlutterShellArgs args = FlutterShellArgs.fromIntent(intent); + HashSet argValues = new HashSet(Arrays.asList(args.toArray())); - @SuppressWarnings("deprecation") - @Test - public void allFlags_haveExpectedMetaDataNamePrefix() { - String defaultPrefix = "io.flutter.embedding.android."; - for (FlutterShellArgs.Flag flag : FlutterShellArgs.ALL_FLAGS) { - // Test all non-deprecated flags that should have the default prefix. - if (!flag.equals(FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME) - && !flag.equals(FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR)) { - assertTrue( - "Flag " + flag.commandLineArgument + " does not have the correct metadata key prefix.", - flag.metadataKey.startsWith(defaultPrefix)); - } - } - } - - @Test - public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() { - FlutterShellArgs.Flag flag = - FlutterShellArgs.getFlagByMetadataKey("io.flutter.embedding.android.AOTSharedLibraryName"); - assertEquals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME, flag); - } - - @Test - public void getFlagByMetadataKey_returnsNullWhenInvalidKeySpecified() { - FlutterShellArgs.Flag flag = - FlutterShellArgs.getFlagByMetadataKey("io.flutter.embedding.android.InvalidMetaDataKey"); - assertNull("Should return null for an invalid meta-data key", flag); - } - - @Test - public void getFlagByCommandLineArgument_returnsExpectedFlagWhenValidArgumentSpecified() { - FlutterShellArgs.Flag flag = - FlutterShellArgs.getFlagByCommandLineArgument("--flutter-assets-dir="); - assertEquals(FlutterShellArgs.FLUTTER_ASSETS_DIR, flag); - } - - @Test - public void getFlagByCommandLineArgument_returnsNullWhenInvalidArgumentSpecified() { - assertNull(FlutterShellArgs.getFlagFromIntentKey("--non-existent-flag")); - } - - @Test - public void getFlagFromIntentKey_returnsExpectedFlagWhenValidKeySpecified() { - // Test flag without value. - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagFromIntentKey("old-gen-heap-size"); - assertEquals(FlutterShellArgs.OLD_GEN_HEAP_SIZE, flag); - - // Test with flag. - flag = FlutterShellArgs.getFlagFromIntentKey("vm-snapshot-data"); - assertEquals(FlutterShellArgs.VM_SNAPSHOT_DATA, flag); - } - - @Test - public void getFlagFromIntentKey_returnsNullWhenInvalidKeySpecified() { - assertNull(FlutterShellArgs.getFlagFromIntentKey("non-existent-flag")); - } - - @Test - public void isDisabled_returnsTrueWhenFlagIsDisabled() { - assertTrue(FlutterShellArgs.isDisabled(FlutterShellArgs.DISABLE_MERGED_PLATFORM_UI_THREAD)); - } - - @Test - public void isDisabled_returnsFalseWhenFlagIsNotDisabled() { - assertFalse(FlutterShellArgs.isDisabled(FlutterShellArgs.VM_SNAPSHOT_DATA)); - } - - // Deprecated flags are tested in this test. - @SuppressWarnings("deprecation") - @Test - public void getReplacementFlagIfDeprecated_returnsExpectedFlag() { - assertEquals( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME, - FlutterShellArgs.getReplacementFlagIfDeprecated( - FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME)); - assertEquals( - FlutterShellArgs.FLUTTER_ASSETS_DIR, - FlutterShellArgs.getReplacementFlagIfDeprecated( - FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR)); - } - - @Test - public void getReplacementFlagIfDeprecated_returnsNullWhenFlagIsNotDeprecated() { - assertNull(FlutterShellArgs.getReplacementFlagIfDeprecated(FlutterShellArgs.VM_SNAPSHOT_DATA)); + // Verify results. + assertEquals(2, argValues.size()); + assertTrue(argValues.contains("--dart-flags=--observe --no-hot --no-pub")); + assertTrue(argValues.contains("--trace-skia-allowlist=skia.a,skia.b")); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java index 089bbd3a030..e594ce80057 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java @@ -24,7 +24,7 @@ import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.loader.ApplicationInfoLoader; import java.io.File; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,8 +129,8 @@ public class PlayStoreDeferredComponentManagerTest { TestFlutterJNI jni = new TestFlutterJNI(); Bundle bundle = new Bundle(); - bundle.putString(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey, "custom_name.so"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); + bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so"); + bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -162,7 +162,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); + bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -194,7 +194,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString( PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so,3:,4:"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); + bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java index 58a51c4f29e..4a5f6183305 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java @@ -23,7 +23,6 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.flutter.embedding.engine.FlutterShellArgs; import java.io.StringReader; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,10 +70,10 @@ public class ApplicationInfoLoaderTest { @Test public void itGeneratesCorrectApplicationInfoWithCustomValues() throws Exception { Bundle bundle = new Bundle(); - bundle.putString(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey, "testaot"); - bundle.putString(FlutterShellArgs.VM_SNAPSHOT_DATA.metadataKey, "testvmsnapshot"); - bundle.putString(FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.metadataKey, "testisolatesnapshot"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "testassets"); + bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "testaot"); + bundle.putString(ApplicationInfoLoader.PUBLIC_VM_SNAPSHOT_DATA_KEY, "testvmsnapshot"); + bundle.putString(ApplicationInfoLoader.PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "testisolatesnapshot"); + bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "testassets"); Context context = generateMockContext(bundle, null); FlutterApplicationInfo info = ApplicationInfoLoader.load(context); assertNotNull(info); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java index a7c8f93cbf8..eff98068a1e 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -8,7 +8,6 @@ import static android.os.Looper.getMainLooper; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -204,6 +203,35 @@ public class FlutterLoaderTest { assertTrue(arguments.contains(leakVMArg)); } + @Test + public void itSetsTheLeakVMFromMetaData() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + Bundle metaData = new Bundle(); + metaData.putBoolean("io.flutter.embedding.android.LeakVM", false); + ctx.getApplicationInfo().metaData = metaData; + + FlutterLoader.Settings settings = new FlutterLoader.Settings(); + assertFalse(flutterLoader.initialized()); + flutterLoader.startInitialization(ctx, settings); + flutterLoader.ensureInitializationComplete(ctx, null); + shadowOf(getMainLooper()).idle(); + + final String leakVMArg = "--leak-vm=false"; + ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); + verify(mockFlutterJNI, times(1)) + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(leakVMArg)); + } + @Test public void itUsesCorrectExecutorService() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); @@ -266,32 +294,22 @@ public class FlutterLoaderTest { } @Test - public void itSetsDeprecatedAotSharedLibraryNameIfPathIsInInternalStorage() throws IOException { + public void itSetsEnableImpellerFromMetaData() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI)); - Context mockApplicationContext = mock(Context.class); - File internalStorageDir = ctx.getFilesDir(); - Path internalStorageDirAsPathObj = internalStorageDir.toPath(); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + Bundle metaData = new Bundle(); + metaData.putBoolean("io.flutter.embedding.android.EnableImpeller", true); + ctx.getApplicationInfo().metaData = metaData; - ctx.getApplicationInfo().nativeLibraryDir = - Paths.get("some", "path", "doesnt", "matter").toString(); + FlutterLoader.Settings settings = new FlutterLoader.Settings(); assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx); - - // Test paths for library living within internal storage. - String librarySoFileName = "library.so"; - Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName); - - String path = testPath.toString(); - Bundle metadata = new Bundle(); - metadata.putString( - "io.flutter.embedding.engine.loader.FlutterLoader.aot-shared-library-name", path); - ctx.getApplicationInfo().metaData = metadata; - + flutterLoader.startInitialization(ctx, settings); flutterLoader.ensureInitializationComplete(ctx, null); + shadowOf(getMainLooper()).idle(); + final String enableImpellerArg = "--enable-impeller=true"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI) + verify(mockFlutterJNI, times(1)) .init( eq(ctx), shellArgsCaptor.capture(), @@ -300,51 +318,27 @@ public class FlutterLoaderTest { anyString(), anyLong(), anyInt()); - - List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); - - // This check works because the tests run in debug mode. If run in release (or JIT release) - // mode, actualArgs would contain the default arguments for AOT shared library name on top - // of aotSharedLibraryNameArg. - String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; - assertTrue( - "Args sent to FlutterJni.init incorrectly did not include path " + path, - actualArgs.contains(canonicalAotSharedLibraryNameArg)); - - // Reset FlutterLoader and mockFlutterJNI to make more calls to - // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing. - flutterLoader.initialized = false; - clearInvocations(mockFlutterJNI); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(enableImpellerArg)); } @Test - public void itSetsAotSharedLibraryNameIfPathIsInInternalStorageInReleaseMode() - throws IOException { + public void itSetsEnableFlutterGPUFromMetaData() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI)); - Context mockApplicationContext = mock(Context.class); - File internalStorageDir = ctx.getFilesDir(); - Path internalStorageDirAsPathObj = internalStorageDir.toPath(); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + Bundle metaData = new Bundle(); + metaData.putBoolean("io.flutter.embedding.android.EnableFlutterGPU", true); + ctx.getApplicationInfo().metaData = metaData; - ctx.getApplicationInfo().nativeLibraryDir = - Paths.get("some", "path", "doesnt", "matter").toString(); + FlutterLoader.Settings settings = new FlutterLoader.Settings(); assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx); - - // Test paths for library living within internal storage. - String librarySoFileName = "library.so"; - Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName); - - String path = testPath.toString(); - Bundle metadata = new Bundle(); - metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); - ctx.getApplicationInfo().metaData = metadata; - - flutterLoader.ensureInitializationComplete(ctx, null, true); + flutterLoader.startInitialization(ctx, settings); + flutterLoader.ensureInitializationComplete(ctx, null); + shadowOf(getMainLooper()).idle(); + final String enableImpellerArg = "--enable-flutter-gpu"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI) + verify(mockFlutterJNI, times(1)) .init( eq(ctx), shellArgsCaptor.capture(), @@ -353,22 +347,66 @@ public class FlutterLoaderTest { anyString(), anyLong(), anyInt()); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(enableImpellerArg)); + } - List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); + @Test + public void itSetsEnableSurfaceControlFromMetaData() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + Bundle metaData = new Bundle(); + metaData.putBoolean("io.flutter.embedding.android.EnableSurfaceControl", true); + ctx.getApplicationInfo().metaData = metaData; - // This check works because the tests run in debug mode. If run in release (or JIT release) - // mode, actualArgs would contain the default arguments for AOT shared library name on top - // of aotSharedLibraryNameArg. - String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; - assertTrue( - "Args sent to FlutterJni.init incorrectly did not include path " + path, - actualArgs.contains(canonicalAotSharedLibraryNameArg)); + FlutterLoader.Settings settings = new FlutterLoader.Settings(); + assertFalse(flutterLoader.initialized()); + flutterLoader.startInitialization(ctx, settings); + flutterLoader.ensureInitializationComplete(ctx, null); + shadowOf(getMainLooper()).idle(); - // Reset FlutterLoader and mockFlutterJNI to make more calls to - // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing. - flutterLoader.initialized = false; - clearInvocations(mockFlutterJNI); + final String disabledControlArg = "--enable-surface-control"; + ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); + verify(mockFlutterJNI, times(1)) + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(disabledControlArg)); + } + + @Test + public void itSetsShaderInitModeFromMetaData() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); + Bundle metaData = new Bundle(); + metaData.putBoolean("io.flutter.embedding.android.ImpellerLazyShaderInitialization", true); + ctx.getApplicationInfo().metaData = metaData; + + FlutterLoader.Settings settings = new FlutterLoader.Settings(); + assertFalse(flutterLoader.initialized()); + flutterLoader.startInitialization(ctx, settings); + flutterLoader.ensureInitializationComplete(ctx, null); + shadowOf(getMainLooper()).idle(); + + final String shaderModeArg = "--impeller-lazy-shader-mode"; + ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); + verify(mockFlutterJNI, times(1)) + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); + List arguments = Arrays.asList(shellArgsCaptor.getValue()); + assertTrue(arguments.contains(shaderModeArg)); } @Test @@ -408,11 +446,9 @@ public class FlutterLoaderTest { for (Path testPath : pathsToTest) { String path = testPath.toString(); - Bundle metadata = new Bundle(); - metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); - ctx.getApplicationInfo().metaData = metadata; - - flutterLoader.ensureInitializationComplete(ctx, null); + String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path; + String[] args = {aotSharedLibraryNameArg}; + flutterLoader.ensureInitializationComplete(ctx, args); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -431,7 +467,8 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; + String canonicalAotSharedLibraryNameArg = + FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath; assertTrue( "Args sent to FlutterJni.init incorrectly did not include path " + path, actualArgs.contains(canonicalAotSharedLibraryNameArg)); @@ -486,11 +523,9 @@ public class FlutterLoaderTest { for (Path testPath : pathsToTest) { String path = testPath.toString(); - Bundle metadata = new Bundle(); - metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); - ctx.getApplicationInfo().metaData = metadata; - - flutterLoader.ensureInitializationComplete(ctx, null); + String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path; + String[] args = {aotSharedLibraryNameArg}; + flutterLoader.ensureInitializationComplete(ctx, args); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -509,7 +544,8 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; + String canonicalAotSharedLibraryNameArg = + FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath; assertFalse( "Args sent to FlutterJni.init incorrectly included canonical path " + canonicalTestPath, actualArgs.contains(canonicalAotSharedLibraryNameArg)); @@ -536,11 +572,8 @@ public class FlutterLoaderTest { String invalidFilePath = "my\0file.so"; - Bundle metadata = new Bundle(); - metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", invalidFilePath); - ctx.getApplicationInfo().metaData = metadata; - - flutterLoader.ensureInitializationComplete(ctx, null); + String[] args = {FlutterLoader.aotSharedLibraryNameFlag + invalidFilePath}; + flutterLoader.ensureInitializationComplete(ctx, args); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -559,7 +592,7 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. for (String arg : actualArgs) { - if (arg.startsWith("--aot-shared-library-name=")) { + if (arg.startsWith(FlutterLoader.aotSharedLibraryNameFlag)) { fail(); } } @@ -587,11 +620,9 @@ public class FlutterLoaderTest { when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile); doReturn(realSoFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath(); - Bundle metadata = new Bundle(); - metadata.putString( - "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getPath()); - ctx.getApplicationInfo().metaData = metadata; - flutterLoader.ensureInitializationComplete(ctx, null); + String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getPath(); + String[] args = {symlinkArg}; + flutterLoader.ensureInitializationComplete(ctx, args); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -607,14 +638,12 @@ public class FlutterLoaderTest { List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); String canonicalSymlinkCanonicalizedPath = realSoFile.getCanonicalPath(); - String aotSharedLibraryNameFlag = "--aot-shared-library-name="; - String symlinkAotSharedLibraryNameArg = aotSharedLibraryNameFlag + spySymlinkFile.getPath(); String canonicalAotSharedLibraryNameArg = - aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; + FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; assertFalse( "Args sent to FlutterJni.init incorrectly included absolute symlink path: " + spySymlinkFile.getAbsolutePath(), - actualArgs.contains(symlinkAotSharedLibraryNameArg)); + actualArgs.contains(symlinkArg)); assertTrue( "Args sent to FlutterJni.init incorrectly did not include canonicalized path of symlink: " + canonicalSymlinkCanonicalizedPath, @@ -645,17 +674,15 @@ public class FlutterLoaderTest { List unsafeFiles = Arrays.asList(nonSoFile, fileJustOutsideInternalStorage); Files.deleteIfExists(spySymlinkFile.toPath()); - Bundle metadata = new Bundle(); - metadata.putString( - "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getAbsolutePath()); - ctx.getApplicationInfo().metaData = metadata; + String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath(); + String[] args = {symlinkArg}; for (File unsafeFile : unsafeFiles) { // Simulate a symlink since some filesystems do not support symlinks. when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile); doReturn(unsafeFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath(); - flutterLoader.ensureInitializationComplete(ctx, null); + flutterLoader.ensureInitializationComplete(ctx, args); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -671,11 +698,8 @@ public class FlutterLoaderTest { List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); String canonicalSymlinkCanonicalizedPath = unsafeFile.getCanonicalPath(); - String aotSharedLibraryNameFlag = "--aot-shared-library-name="; - String symlinkAotSharedLibraryNameArg = - aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath(); String canonicalAotSharedLibraryNameArg = - aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; + FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; assertFalse( "Args sent to FlutterJni.init incorrectly included canonicalized path of symlink: " + canonicalSymlinkCanonicalizedPath, @@ -683,7 +707,7 @@ public class FlutterLoaderTest { assertFalse( "Args sent to FlutterJni.init incorrectly included absolute path of symlink: " + spySymlinkFile.getAbsolutePath(), - actualArgs.contains(symlinkAotSharedLibraryNameArg)); + actualArgs.contains(symlinkArg)); // Clean up created files. spySymlinkFile.delete(); @@ -695,623 +719,4 @@ public class FlutterLoaderTest { clearInvocations(mockFlutterJNI); } } - - @Test - public void itSetsEnableSoftwareRenderingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableSoftwareRendering", - true, - "--enable-software-rendering"); - } - - @Test - public void getSofwareRenderingEnabledViaManifest_returnsExpectedValueWhenSetViaManifest() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - metadata.putBoolean("io.flutter.embedding.android.EnableSoftwareRendering", true); - - ctx.getApplicationInfo().metaData = metadata; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); - - assertTrue(flutterLoader.getSofwareRenderingEnabledViaManifest()); - } - - @Test - public void itSetsSkiaDeterministicRenderingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.SkiaDeterministicRendering", - true, - "--skia-deterministic-rendering"); - } - - @Test - public void itSetsFlutterAssetsDirFromMetadata() { - String expectedAssetsDir = "flutter_assets_dir"; - // Test debug mode - testFlagFromMetadataPresent( - "io.flutter.embedding.android.FlutterAssetsDir", - expectedAssetsDir, - "--flutter-assets-dir=" + expectedAssetsDir); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.FlutterAssetsDir", - expectedAssetsDir, - "--flutter-assets-dir=" + expectedAssetsDir); - } - - @Test - public void itSetsDeprecatedFlutterAssetsDirFromMetadata() { - String expectedAssetsDir = "flutter_assets_dir"; - - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir", - expectedAssetsDir, - "--flutter-assets-dir=" + expectedAssetsDir); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.engine.loader.FlutterLoader.flutter-assets-dir", - expectedAssetsDir, - "--flutter-assets-dir=" + expectedAssetsDir); - } - - @Test - public void itSetsOldGenHeapSizeFromMetadata() { - // Test old gen heap size can be set from metadata in debug mode. - int expectedOldGenHeapSize = 256; - testFlagFromMetadataPresent( - "io.flutter.embedding.android.OldGenHeapSize", - expectedOldGenHeapSize, - "--old-gen-heap-size=" + expectedOldGenHeapSize); - - // Test old gen heap size can be set from metadta in release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.OldGenHeapSize", - expectedOldGenHeapSize, - "--old-gen-heap-size=" + expectedOldGenHeapSize); - - // Test that default old gen heap size will not be included if it - // is configured via the manifest. - ActivityManager activityManager = - (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE); - ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); - activityManager.getMemoryInfo(memInfo); - int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); - testFlagFromMetadataNotPresent( - "io.flutter.embedding.android.OldGenHeapSize", - expectedOldGenHeapSize, - "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes); - } - - @Test - public void itSetsEnableImpellerFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true"); - } - - @Test - public void itSetsImpellerBackendFromMetadata() { - String expectedImpellerBackend = "Vulkan"; - - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.ImpellerBackend", - expectedImpellerBackend, - "--impeller-backend=" + expectedImpellerBackend); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ImpellerBackend", - expectedImpellerBackend, - "--impeller-backend=" + expectedImpellerBackend); - } - - @Test - public void itSetsEnableSurfaceControlFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control"); - } - - @Test - public void itSetsEnableFlutterGPUFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu"); - } - - @Test - public void itSetsImpellerLazyShaderModeFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.ImpellerLazyShaderInitialization", - true, - "--impeller-lazy-shader-mode=true"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ImpellerLazyShaderInitialization", - true, - "--impeller-lazy-shader-mode=true"); - } - - @Test - public void itSetsImpellerAntiAliasLinesFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines"); - } - - @Test - public void itSetsVmSnapshotDataFromMetadata() { - String expectedVmSnapshotData = "vm_snapshot_data"; - - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.VmSnapshotData", - expectedVmSnapshotData, - "--vm-snapshot-data=" + expectedVmSnapshotData); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.VmSnapshotData", - expectedVmSnapshotData, - "--vm-snapshot-data=" + expectedVmSnapshotData); - } - - @Test - public void itSetsIsolateSnapshotDataFromMetadata() { - String expectedIsolateSnapshotData = "isolate_snapshot_data"; - - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.IsolateSnapshotData", - expectedIsolateSnapshotData, - "--isolate-snapshot-data=" + expectedIsolateSnapshotData); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.IsolateSnapshotData", - expectedIsolateSnapshotData, - "--isolate-snapshot-data=" + expectedIsolateSnapshotData); - } - - @Test - public void itSetsUseTestFontsFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.UseTestFonts", true, "--use-test-fonts"); - } - - @Test - public void itSetsVmServicePortFromMetadata() { - int expectedVmServicePort = 12345; - testFlagFromMetadataPresent( - "io.flutter.embedding.android.VMServicePort", - expectedVmServicePort, - "--vm-service-port=" + expectedVmServicePort); - } - - @Test - public void itSetsEnableVulkanValidationFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanValidation", true, "--enable-vulkan-validation"); - } - - @Test - public void itSetsEnableOpenGLGPUTracingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableOpenGLGPUTracing", true, "--enable-opengl-gpu-tracing"); - } - - @Test - public void itSetsEnableVulkanGPUTracingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanGPUTracing", true, "--enable-vulkan-gpu-tracing"); - } - - @Test - public void itSetsLeakVMFromMetadata() { - // Test that LeakVM can be set via manifest. - testFlagFromMetadataPresent("io.flutter.embedding.android.LeakVM", false, "--leak-vm=false"); - - // Test that default LeakVM will not be included if it is configured via the manifest. - testFlagFromMetadataNotPresent("io.flutter.embedding.android.LeakVM", false, "--leak-vm=true"); - } - - @Test - public void itSetsTraceStartupFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceStartup", true, "--trace-startup"); - } - - @Test - public void itSetsStartPausedFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", true, "--start-paused"); - } - - @Test - public void itSetsDisableServiceAuthCodesFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.DisableServiceAuthCodes", - true, - "--disable-service-auth-codes"); - } - - @Test - public void itSetsEndlessTraceBufferFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EndlessTraceBuffer", true, "--endless-trace-buffer"); - } - - @Test - public void itSetsEnableDartProfilingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); - } - - @Test - public void itSetsProfileStartupFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileStartup", true, "--profile-startup"); - } - - @Test - public void itSetsTraceSkiaFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", true, "--trace-skia"); - } - - @Test - public void itSetsTraceSkiaAllowlistFromMetadata() { - String expectedTraceSkiaAllowList = "allowed1,allowed2,allowed3"; - testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceSkiaAllowList", - expectedTraceSkiaAllowList, - "--trace-skia-allowlist=" + expectedTraceSkiaAllowList); - } - - @Test - public void itSetsTraceSystraceFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceSystrace", true, "--trace-systrace"); - } - - @Test - public void itSetsTraceToFileFromMetadata() { - String expectedTraceToFilePath = "/path/to/trace/file"; - testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceToFile", - expectedTraceToFilePath, - "--trace-to-file=" + expectedTraceToFilePath); - } - - @Test - public void itSetsProfileMicrotasksFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileMicrotasks", true, "--profile-microtasks"); - } - - @Test - public void itSetsDumpSkpOnShaderCompilationFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.DumpSkpOnShaderCompilation", - true, - "--dump-skp-on-shader-compilation"); - } - - @Test - public void itSetsPurgePersistentCacheFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.PurgePersistentCache", true, "--purge-persistent-cache"); - } - - @Test - public void itSetsVerboseLoggingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.VerboseLogging", true, "--verbose-logging"); - } - - @Test - public void itSetsDartFlagsFromMetadata() { - String expectedDartFlags = "--enable-asserts --enable-vm-service"; - testFlagFromMetadataPresent( - "io.flutter.embedding.android.DartFlags", - expectedDartFlags, - "--dart-flags=" + expectedDartFlags); - } - - @Test - public void itDoesNotSetDisableMergedPlatformUIThreadFromMetadata() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - metadata.putBoolean("io.flutter.embedding.android.DisableMergedPlatformUIThread", true); - ctx.getApplicationInfo().metaData = metadata; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - - // Verify that an IllegalArgumentException is thrown when DisableMergedPlatformUIThread is set, - // as it is no longer supported. - Exception exception = - assertThrows( - RuntimeException.class, () -> flutterLoader.ensureInitializationComplete(ctx, null)); - Throwable cause = exception.getCause(); - - assertNotNull(cause); - assertTrue( - "Expected cause to be IllegalArgumentException", cause instanceof IllegalArgumentException); - assertTrue( - cause - .getMessage() - .contains( - "io.flutter.embedding.android.DisableMergedPlatformUIThread is disabled and no longer allowed.")); - } - - @Test - public void itDoesNotSetUnrecognizedCommandLineArgument() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - String[] unrecognizedArg = {"--unrecognized-argument"}; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, unrecognizedArg); - shadowOf(getMainLooper()).idle(); - - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - - assertFalse( - "Unrecognized argument '" - + unrecognizedArg[0] - + "' was found in the arguments passed to FlutterJNI.init", - arguments.contains(unrecognizedArg[0])); - } - - @Test - public void itDoesSetRecognizedCommandLineArgument() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - String[] recognizedArg = {"--enable-impeller=true"}; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, recognizedArg); - shadowOf(getMainLooper()).idle(); - - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - - assertTrue( - "Recognized argument '" - + recognizedArg[0] - + "' was not found in the arguments passed to FlutterJNI.init", - arguments.contains(recognizedArg[0])); - } - - @Test - public void ifFlagSetViaManifestAndCommandLineThenCommandLineTakesPrecedence() { - String expectedImpellerArgFromMetadata = "--enable-impeller=true"; - String expectedImpellerArgFromCommandLine = "--enable-impeller=false"; - - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - // Place metadata key and value into the metadata bundle used to mock the manifest. - metadata.putBoolean("io.flutter.embedding.android.EnableImpeller", true); - ctx.getApplicationInfo().metaData = metadata; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete( - ctx, new String[] {expectedImpellerArgFromCommandLine}); - shadowOf(getMainLooper()).idle(); - - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - - // Verify that the command line argument takes precedence over the manifest metadata. - assertTrue( - arguments.indexOf(expectedImpellerArgFromMetadata) - < arguments.indexOf(expectedImpellerArgFromCommandLine)); - } - - @Test - public void ifAOTSharedLibraryNameSetViaManifestAndCommandLineThenCommandLineTakesPrecedence() - throws IOException { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI)); - File internalStorageDir = ctx.getFilesDir(); - Path internalStorageDirAsPathObj = internalStorageDir.toPath(); - - ctx.getApplicationInfo().nativeLibraryDir = - Paths.get("some", "path", "doesnt", "matter").toString(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx); - - // Test paths for library living within internal storage. - Path pathWithDirectInternalStoragePath1 = internalStorageDirAsPathObj.resolve("library1.so"); - Path pathWithDirectInternalStoragePath2 = internalStorageDirAsPathObj.resolve("library2.so"); - - String expectedAotSharedLibraryNameFromMetadata = - "--aot-shared-library-name=" - + pathWithDirectInternalStoragePath1.toFile().getCanonicalPath(); - String expectedAotSharedLibraryNameFromCommandLine = - "--aot-shared-library-name=" - + pathWithDirectInternalStoragePath2.toFile().getCanonicalPath(); - - Bundle metadata = new Bundle(); - - // Place metadata key and value into the metadata bundle used to mock the manifest. - metadata.putString( - "io.flutter.embedding.android.AOTSharedLibraryName", - pathWithDirectInternalStoragePath1.toFile().getCanonicalPath()); - ctx.getApplicationInfo().metaData = metadata; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete( - ctx, - new String[] {expectedAotSharedLibraryNameFromCommandLine, "--enable-opengl-gpu-tracing"}); - shadowOf(getMainLooper()).idle(); - - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - - // Verify that the command line argument takes precedence over the manifest metadata. - assertTrue( - arguments.indexOf(expectedAotSharedLibraryNameFromCommandLine) - < arguments.indexOf(expectedAotSharedLibraryNameFromMetadata)); - - // Verify other command line arguments are still passed through. - assertTrue( - "Expected argument --enable-opengl-gpu-tracing was not found in the arguments passed to FlutterJNI.init", - arguments.contains("--enable-opengl-gpu-tracing")); - } - - private void testFlagFromMetadataPresentInReleaseMode( - String metadataKey, Object metadataValue, String expectedArg) { - testFlagFromMetadata(metadataKey, metadataValue, expectedArg, true, true); - } - - private void testFlagFromMetadataNotPresent( - String metadataKey, Object metadataValue, String expectedArg) { - testFlagFromMetadata(metadataKey, metadataValue, expectedArg, false, false); - } - - private void testFlagFromMetadataPresent( - String metadataKey, Object metadataValue, String expectedArg) { - testFlagFromMetadata(metadataKey, metadataValue, expectedArg, true, false); - } - - // Test that specified shell argument can be set via manifest metadata as expected. - private void testFlagFromMetadata( - String metadataKey, - Object metadataValue, - String expectedArg, - boolean shouldBeSet, - boolean isReleaseMode) { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metadata = new Bundle(); - - // Place metadata key and value into the metadata bundle used to mock the manifest. - if (metadataValue instanceof Boolean) { - metadata.putBoolean(metadataKey, (Boolean) metadataValue); - } else if (metadataValue instanceof Integer) { - metadata.putInt(metadataKey, (Integer) metadataValue); - } else if (metadataValue instanceof String) { - metadata.putString(metadataKey, (String) metadataValue); - } else { - throw new IllegalArgumentException( - "Unsupported metadataValue type: " + metadataValue.getClass()); - } - - ctx.getApplicationInfo().metaData = metadata; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null, isReleaseMode); - shadowOf(getMainLooper()).idle(); - - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - - if (shouldBeSet) { - assertTrue( - "Expected argument '" - + expectedArg - + "' was not found in the arguments passed to FlutterJNI.init", - arguments.contains(expectedArg)); - } else { - assertFalse( - "Unexpected argument '" - + expectedArg - + "' was found in the arguments passed to FlutterJNI.init", - arguments.contains(expectedArg)); - } - } -} +} \ No newline at end of file diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/external/FlutterLaunchTests.java b/engine/src/flutter/shell/platform/android/test/io/flutter/external/FlutterLaunchTests.java index d10fcc77c26..16b82490175 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/external/FlutterLaunchTests.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/external/FlutterLaunchTests.java @@ -26,7 +26,7 @@ public class FlutterLaunchTests { assertEquals("main", flutterActivity.getDartEntrypointFunctionName()); assertEquals("/", flutterActivity.getInitialRoute()); - assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs().toArray()); assertTrue(flutterActivity.shouldAttachEngineToActivity()); assertNull(flutterActivity.getCachedEngineId()); assertTrue(flutterActivity.shouldDestroyEngineWithHost()); diff --git a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java index 125868b3af6..3c53005f354 100644 --- a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java +++ b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java @@ -15,14 +15,11 @@ import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; -import io.flutter.Log; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BasicMessageChannel.MessageHandler; import io.flutter.plugin.common.BasicMessageChannel.Reply; import io.flutter.plugin.common.StringCodec; -import java.util.Arrays; import java.util.ArrayList; -import java.util.List; public class MainActivity extends AppCompatActivity { private static FlutterEngine flutterEngine; @@ -34,28 +31,34 @@ public class MainActivity extends AppCompatActivity { private static final String PING = "ping"; private BasicMessageChannel messageChannel; - // Previously, this example checked for certain flags set via Intent. Engine - // flags can no longer be set via Intent, so warn developers that Intent extras - // will be ignored and point to alternative methods for setting engine flags. - private void warnIfEngineFlagsSetViaIntent(Intent intent) { - List previouslySupportedFlagsViaIntent = Arrays.asList( - "trace-startup", "start-paused", "enable-dart-profiling"); - for (String flag : previouslySupportedFlagsViaIntent) { - if (intent.hasExtra(flag)) { - Log.w("MainActivity", "Engine flags can no longer be set via Intent on Android. If you wish to set " + flag + ", see https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.md for alternative methods."); - break; - } + private String[] getArgsFromIntent(Intent intent) { + // Before adding more entries to this list, consider that arbitrary + // Android applications can generate intents with extra data and that + // there are many security-sensitive args in the binary. + ArrayList args = new ArrayList<>(); + if (intent.getBooleanExtra("trace-startup", false)) { + args.add("--trace-startup"); } + if (intent.getBooleanExtra("start-paused", false)) { + args.add("--start-paused"); + } + if (intent.getBooleanExtra("enable-dart-profiling", false)) { + args.add("--enable-dart-profiling"); + } + if (!args.isEmpty()) { + String[] argsArray = new String[args.size()]; + return args.toArray(argsArray); + } + return null; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - warnIfEngineFlagsSetViaIntent(getIntent()); - + String[] args = getArgsFromIntent(getIntent()); if (flutterEngine == null) { - flutterEngine = new FlutterEngine(this); + flutterEngine = new FlutterEngine(this, args); flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() );