From 3d83f89e019f958eedf8e8f91b4ae1c395616deb Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 10 Feb 2026 11:06:24 -0800 Subject: [PATCH 01/17] Refactor FlutterShellArgs --- ...nts.md => Android-Flutter-Engine-Flags.md} | 6 +- .../flutter/shell/platform/android/BUILD.gn | 2 +- .../embedding/android/FlutterActivity.java | 6 +- .../FlutterActivityAndFragmentDelegate.java | 11 +- .../embedding/android/FlutterFragment.java | 15 +- .../android/FlutterFragmentActivity.java | 4 +- .../embedding/engine/FlutterEngine.java | 2 +- .../FlutterEngineConnectionRegistry.java | 9 +- .../embedding/engine/FlutterEngineFlags.java | 437 +++++++++++++ .../embedding/engine/FlutterShellArgs.java | 591 ++++++------------ .../engine/FlutterShellArgsIntentUtils.java | 169 ----- .../engine/loader/ApplicationInfoLoader.java | 14 +- .../engine/loader/FlutterLoader.java | 50 +- .../android/FlutterActivityTest.java | 10 +- .../android/FlutterAndroidComponentTest.java | 6 +- .../android/FlutterFragmentTest.java | 6 +- .../engine/FlutterEngineFlagsTest.java | 122 ++++ .../FlutterShellArgsIntentUtilsTest.java | 36 -- .../engine/FlutterShellArgsTest.java | 125 +--- ...PlayStoreDeferredComponentManagerTest.java | 10 +- .../loader/ApplicationInfoLoaderTest.java | 10 +- .../flutter/external/FlutterLaunchTests.java | 2 +- .../java/com/example/view/MainActivity.java | 2 +- 23 files changed, 850 insertions(+), 795 deletions(-) rename docs/engine/{Android-Flutter-Shell-Arguments.md => Android-Flutter-Engine-Flags.md} (96%) create mode 100644 engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java delete mode 100644 engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java create mode 100644 engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java delete mode 100644 engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java diff --git a/docs/engine/Android-Flutter-Shell-Arguments.md b/docs/engine/Android-Flutter-Engine-Flags.md similarity index 96% rename from docs/engine/Android-Flutter-Shell-Arguments.md rename to docs/engine/Android-Flutter-Engine-Flags.md index 41638bbe4f0..b9341c47bac 100644 --- a/docs/engine/Android-Flutter-Shell-Arguments.md +++ b/docs/engine/Android-Flutter-Engine-Flags.md @@ -9,7 +9,7 @@ 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 +`FlutterEngineFlags.java` for the list of flags that can be set for the Android shell. ## When to use manifest metadata versus the command line @@ -58,7 +58,7 @@ 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=` +`FlutterEngineFlags.java`. For example, the `--impeller-lazy-shader-mode=` command line flag corresponds to the metadata key `io.flutter.embedding.android.ImpellerLazyShaderInitialization`. @@ -93,7 +93,7 @@ Set the `--enable-flutter-gpu` flag: - 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 + embedding/engine/FlutterEngineFlags`, which marks allowed flags with `allowedInRelease`). If a disallowed flag is set in release, it will be ignored. - If you need different behavior in release vs debug/profile mode, configure it diff --git a/engine/src/flutter/shell/platform/android/BUILD.gn b/engine/src/flutter/shell/platform/android/BUILD.gn index 197e5b39237..c15ac362d7d 100644 --- a/engine/src/flutter/shell/platform/android/BUILD.gn +++ b/engine/src/flutter/shell/platform/android/BUILD.gn @@ -250,12 +250,12 @@ android_java_sources = [ "io/flutter/embedding/engine/FlutterEngine.java", "io/flutter/embedding/engine/FlutterEngineCache.java", "io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java", + "io/flutter/embedding/engine/FlutterEngineFlags.java", "io/flutter/embedding/engine/FlutterEngineGroup.java", "io/flutter/embedding/engine/FlutterEngineGroupCache.java", "io/flutter/embedding/engine/FlutterJNI.java", "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..037d9af90b9 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 @@ -28,6 +28,7 @@ import io.flutter.Build.API_LEVELS; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterEngineFlags; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterEngineGroup; import io.flutter.embedding.engine.FlutterEngineGroupCache; @@ -333,11 +334,9 @@ import java.util.Set; + " 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( @@ -360,13 +359,13 @@ import java.util.Set; Set extrasKeys = extras.keySet(); for (String extrasKey : extrasKeys) { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagFromIntentKey(extrasKey); + FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagFromIntentKey(extrasKey); if (flag != null) { Log.w( TAG, "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/180686 for more information on this breaking change. To migrate, set " + flag.commandLineArgument - + " on the command line or see https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.md for alternative methods."); + + " on the command line or see https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Engine-Flags.md for alternative methods."); break; } } @@ -1119,7 +1118,7 @@ import java.util.Set; Lifecycle getLifecycle(); @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..c8a66a02985 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -177,7 +177,7 @@ 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, + * io.flutter.embedding.engine.FlutterEngineFlags} for all available flags) when creating the VM, * manually set the initialization arguments by calling {@link * io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, 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..d98035c3cb7 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 @@ -15,6 +15,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; import io.flutter.Log; +import io.flutter.embedding.engine.FlutterEngineFlags; +import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.android.ExclusiveAppComponent; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; @@ -334,8 +336,7 @@ import java.util.Set; // https://github.com/flutter/flutter/issues/180686. boolean useSoftwareRendering = intent != null - ? intent.getBooleanExtra( - FlutterShellArgsIntentUtils.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) + ? intent.getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) : false; // As part of https://github.com/flutter/flutter/issues/172553, the ability to set @@ -347,8 +348,8 @@ import java.util.Set; 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."); + + FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey + + " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Engine-Flags.md for more info."); } else { // Check manifest for software rendering configuration. useSoftwareRendering = flutterLoader.getSofwareRenderingEnabledViaManifest(); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java new file mode 100644 index 00000000000..9474da536f3 --- /dev/null +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -0,0 +1,437 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine; + +import androidx.annotation.VisibleForTesting; +import java.util.*; + +/** + * Arguments that can be delivered to the Flutter shell on Android. + * + *

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

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

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

If the same flag is provided both via command line arguments and via AndroidManifest.xml + * metadata, the command line value will take precedence at runtime. + */ +public final class FlutterEngineFlags { + + private FlutterEngineFlags() {} + + /** Represents a Flutter shell flag that can be set via manifest metadata or command line. */ + public static class Flag { + /** The command line argument used to specify the flag. */ + public final String commandLineArgument; + + /** + * The metadata key name used to specify the flag in AndroidManifest.xml. + * + *

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

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

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

The AOT shared library that the engine uses will default to the library set by this flag, + * but will fall back to the libraries set internally by the embedding if the path specified by + * this argument is invalid. + * + *

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); + } + 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; + } + } + return null; + } + + /** Returns whether or not a flag is disabled and should raise an exception if used. */ + public static boolean isDisabled(Flag flag) { + return DISABLED_FLAGS.contains(flag); + } + + /** Returns the replacement flag of that given if it is deprecated. */ + public static Flag getReplacementFlagIfDeprecated(Flag flag) { + return DEPRECATED_FLAGS_BY_REPLACEMENT.get(flag); + } +} diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 6463f8310e8..60754c1e1b8 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,213 @@ 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. + * DEPRECATED. Please see {@link FlutterEngineFlags} for the list of arguments to use or update if + * you are adding a new flag. + * + *

Arguments that can be delivered to the Flutter shell when it is created. * *

The term "shell" refers to the native code that adapts Flutter to different platforms. * Flutter's Android Java code initializes a native "shell" and passes these arguments to that * 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 { +// TODO(camsim99): Delete this class when support for setting engine shell arguments via Intent +// is no longer supported. See https://github.com/flutter/flutter/issues/180686. +@SuppressWarnings({"WeakerAccess", "unused"}) +@Deprecated +public class FlutterShellArgs { + public static final String ARG_KEY_TRACE_STARTUP = "trace-startup"; + public static final String ARG_TRACE_STARTUP = "--trace-startup"; + 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 + Integer.toString(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..412af23b691 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java @@ -10,7 +10,7 @@ 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 io.flutter.embedding.engine.FlutterEngineFlags; import java.io.IOException; import org.json.JSONArray; import org.xmlpull.v1.XmlPullParserException; @@ -162,14 +162,14 @@ public final class ApplicationInfoLoader { 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), + FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME.metadataKey, + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey), + getString(appInfo.metaData, FlutterEngineFlags.VM_SNAPSHOT_DATA.metadataKey), + getString(appInfo.metaData, FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.metadataKey), getStringWithFallback( appInfo.metaData, - FlutterShellArgs.DEPRECATED_FLUTTER_ASSETS_DIR.metadataKey, - FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey), + FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR.metadataKey, + FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey), getNetworkPolicy(appInfo, applicationContext), appInfo.nativeLibraryDir, getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true)); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index c4897f83a9b..594ee93e245 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -22,8 +22,8 @@ import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; import io.flutter.FlutterInjector; import io.flutter.Log; +import io.flutter.embedding.engine.FlutterEngineFlags; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.util.HandlerCompat; import io.flutter.util.PathUtils; import io.flutter.util.TraceSection; @@ -305,12 +305,12 @@ public class FlutterLoader { // 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)) { + if (arg.startsWith(FlutterEngineFlags.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()); + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length()); maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs); break; } @@ -334,27 +334,28 @@ public class FlutterLoader { .filter(metadataKey -> !metadataKey.equals(FLUTTER_EMBEDDING_KEY)) .forEach( metadataKey -> { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByMetadataKey(metadataKey); + FlutterEngineFlags.Flag flag = + FlutterEngineFlags.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."); + + " is not recognized. Please ensure that the flag is defined in the FlutterEngineFlags."); return; - } else if (FlutterShellArgs.isDisabled(flag)) { + } else if (FlutterEngineFlags.isDisabled(flag)) { // Do not allow disabled flags. throw new IllegalArgumentException( metadataKey + " is disabled and no longer allowed. Please remove this flag from your application manifest."); - } else if (FlutterShellArgs.getReplacementFlagIfDeprecated(flag) != null) { + } else if (FlutterEngineFlags.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); + + FlutterEngineFlags.getReplacementFlagIfDeprecated(flag).metadataKey); } else if (!flag.allowedInRelease && isRelease) { // Manifest flag is not allowed in release builds. Log.w( @@ -366,21 +367,21 @@ public class FlutterLoader { } // Handle special cases for specific flags. - if (flag == FlutterShellArgs.OLD_GEN_HEAP_SIZE) { + if (flag == FlutterEngineFlags.OLD_GEN_HEAP_SIZE) { // Mark if old gen heap size is set to track whether or not to set default // internally. oldGenHeapSizeSet.set(true); - } else if (flag == FlutterShellArgs.LEAK_VM) { + } else if (flag == FlutterEngineFlags.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) { + } else if (flag == FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING) { // Enabling software rendering impacts platform views, so save this value // so that the PlatformViewsController can be properly configured. enableSoftwareRendering = applicationMetaData.getBoolean( - FlutterShellArgs.ENABLE_SOFTWARE_RENDERING.metadataKey, false); - } else if (flag == FlutterShellArgs.AOT_SHARED_LIBRARY_NAME - || flag == FlutterShellArgs.DEPRECATED_AOT_SHARED_LIBRARY_NAME) { + FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey, false); + } else if (flag == FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME + || flag == FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME) { // Perform security check for path containing application's compiled Dart // code and potentially user-provided compiled native code. String aotSharedLibraryPath = applicationMetaData.getString(metadataKey); @@ -414,16 +415,16 @@ public class FlutterLoader { // metadata and any defaults set below. if (args != null) { for (String arg : args) { - FlutterShellArgs.Flag flag = FlutterShellArgs.getFlagByCommandLineArgument(arg); + FlutterEngineFlags.Flag flag = FlutterEngineFlags.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."); + + "is not recognized. Please ensure that the flag is defined in the FlutterEngineFlags."); continue; - } else if (flag.equals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME)) { + } else if (flag.equals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME)) { // This flag has already been handled. continue; } else if (!flag.allowedInRelease && isRelease) { @@ -450,24 +451,24 @@ public class FlutterLoader { kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB; shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath); shellArgs.add( - FlutterShellArgs.VM_SNAPSHOT_DATA.commandLineArgument + FlutterEngineFlags.VM_SNAPSHOT_DATA.commandLineArgument + flutterApplicationInfo.vmSnapshotData); shellArgs.add( - FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.commandLineArgument + FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.commandLineArgument + 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 + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument + flutterApplicationInfo.aotSharedLibraryName); // Some devices cannot load the an AOT shared library based on the library name // with no directory path. So, we provide a fully qualified path to the default library // as a workaround for devices where that fails. shellArgs.add( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument + flutterApplicationInfo.nativeLibraryDir + File.separator + flutterApplicationInfo.aotSharedLibraryName); @@ -496,7 +497,7 @@ public class FlutterLoader { activityManager.getMemoryInfo(memInfo); int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); shellArgs.add( - FlutterShellArgs.OLD_GEN_HEAP_SIZE.commandLineArgument + FlutterEngineFlags.OLD_GEN_HEAP_SIZE.commandLineArgument + String.valueOf(oldGenHeapSizeMegaBytes)); } @@ -511,7 +512,7 @@ public class FlutterLoader { shellArgs.add("--prefetched-default-font-manager"); if (!isLeakVMSet.get()) { - shellArgs.add(FlutterShellArgs.LEAK_VM.commandLineArgument + "true"); + shellArgs.add(FlutterEngineFlags.LEAK_VM.commandLineArgument + "true"); } long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; @@ -550,7 +551,8 @@ public class FlutterLoader { if (safeAotSharedLibraryName != null) { shellArgs.add( - FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + safeAotSharedLibraryName); + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument + + safeAotSharedLibraryName); } else { // If the library path is not safe, we will skip adding this argument. Log.w( 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..a1fb6437e74 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,9 @@ public class FlutterAndroidComponentTest { @NonNull @Override - public String[] getFlutterShellArgs() { - return new String[0]; + @SuppressWarnings("deprecation") + 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/FlutterEngineFlagsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java new file mode 100644 index 00000000000..799fabcacdd --- /dev/null +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java @@ -0,0 +1,122 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import org.junit.Test; + +public class FlutterEngineFlagsTest { + + @Test + public void allFlags_containsAllFlags() { + // Count the number of declared flags in FlutterEngineFlags. + int declaredFlagsCount = 0; + for (Field field : FlutterEngineFlags.class.getDeclaredFields()) { + if (FlutterEngineFlags.Flag.class.isAssignableFrom(field.getType()) + && Modifier.isStatic(field.getModifiers()) + && Modifier.isFinal(field.getModifiers())) { + declaredFlagsCount++; + } + } + + // Check that the number of declared flags matches the size of ALL_FLAGS. + assertEquals( + "If you are adding a new Flag to FlutterEngineFlags, please make sure it is added to ALL_FLAGS as well. Otherwise, the flag will be silently ignored when specified.", + declaredFlagsCount, + FlutterEngineFlags.ALL_FLAGS.size()); + } + + @SuppressWarnings("deprecation") + @Test + public void allFlags_haveExpectedMetaDataNamePrefix() { + String defaultPrefix = "io.flutter.embedding.android."; + for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) { + // Test all non-deprecated flags that should have the default prefix. + if (!flag.equals(FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME) + && !flag.equals(FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR)) { + assertTrue( + "Flag " + flag.commandLineArgument + " does not have the correct metadata key prefix.", + flag.metadataKey.startsWith(defaultPrefix)); + } + } + } + + @Test + public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() { + FlutterEngineFlags.Flag flag = + FlutterEngineFlags.getFlagByMetadataKey("io.flutter.embedding.android.AOTSharedLibraryName"); + assertEquals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME, flag); + } + + @Test + public void getFlagByMetadataKey_returnsNullWhenInvalidKeySpecified() { + FlutterEngineFlags.Flag flag = + FlutterEngineFlags.getFlagByMetadataKey("io.flutter.embedding.android.InvalidMetaDataKey"); + assertNull("Should return null for an invalid meta-data key", flag); + } + + @Test + public void getFlagByCommandLineArgument_returnsExpectedFlagWhenValidArgumentSpecified() { + FlutterEngineFlags.Flag flag = + FlutterEngineFlags.getFlagByCommandLineArgument("--flutter-assets-dir="); + assertEquals(FlutterEngineFlags.FLUTTER_ASSETS_DIR, flag); + } + + @Test + public void getFlagByCommandLineArgument_returnsNullWhenInvalidArgumentSpecified() { + assertNull(FlutterEngineFlags.getFlagFromIntentKey("--non-existent-flag")); + } + + @Test + public void getFlagFromIntentKey_returnsExpectedFlagWhenValidKeySpecified() { + // Test flag without value. + FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagFromIntentKey("old-gen-heap-size"); + assertEquals(FlutterEngineFlags.OLD_GEN_HEAP_SIZE, flag); + + // Test with flag. + flag = FlutterEngineFlags.getFlagFromIntentKey("vm-snapshot-data"); + assertEquals(FlutterEngineFlags.VM_SNAPSHOT_DATA, flag); + } + + @Test + public void getFlagFromIntentKey_returnsNullWhenInvalidKeySpecified() { + assertNull(FlutterEngineFlags.getFlagFromIntentKey("non-existent-flag")); + } + + @Test + public void isDisabled_returnsTrueWhenFlagIsDisabled() { + assertTrue(FlutterEngineFlags.isDisabled(FlutterEngineFlags.DISABLE_MERGED_PLATFORM_UI_THREAD)); + } + + @Test + public void isDisabled_returnsFalseWhenFlagIsNotDisabled() { + assertFalse(FlutterEngineFlags.isDisabled(FlutterEngineFlags.VM_SNAPSHOT_DATA)); + } + + // Deprecated flags are tested in this test. + @SuppressWarnings("deprecation") + @Test + public void getReplacementFlagIfDeprecated_returnsExpectedFlag() { + assertEquals( + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME, + FlutterEngineFlags.getReplacementFlagIfDeprecated( + FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME)); + assertEquals( + FlutterEngineFlags.FLUTTER_ASSETS_DIR, + FlutterEngineFlags.getReplacementFlagIfDeprecated( + FlutterEngineFlags.DEPRECATED_FLUTTER_ASSETS_DIR)); + } + + @Test + public void getReplacementFlagIfDeprecated_returnsNullWhenFlagIsNotDeprecated() { + assertNull(FlutterEngineFlags.getReplacementFlagIfDeprecated(FlutterEngineFlags.VM_SNAPSHOT_DATA)); + } +} diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/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..9d1be0e26d7 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsTest.java @@ -2,121 +2,36 @@ // 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++; - } - } - - // 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()); - } - @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)); - } - } - } + 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"); - @Test - public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() { - FlutterShellArgs.Flag flag = - FlutterShellArgs.getFlagByMetadataKey("io.flutter.embedding.android.AOTSharedLibraryName"); - assertEquals(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME, flag); - } + // Execute the behavior under test. + FlutterShellArgs args = FlutterShellArgs.fromIntent(intent); + HashSet argValues = new HashSet(Arrays.asList(args.toArray())); - @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..20c64ea281d 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java @@ -23,8 +23,8 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.embedding.engine.FlutterEngineFlags; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.FlutterShellArgs; 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(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey, "custom_name.so"); + bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -162,7 +162,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); + bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -194,7 +194,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString( PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so,3:,4:"); - bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); + bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java index 58a51c4f29e..387781718ba 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java @@ -23,7 +23,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.FlutterEngineFlags; import java.io.StringReader; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,10 +71,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(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.metadataKey, "testaot"); + bundle.putString(FlutterEngineFlags.VM_SNAPSHOT_DATA.metadataKey, "testvmsnapshot"); + bundle.putString(FlutterEngineFlags.ISOLATE_SNAPSHOT_DATA.metadataKey, "testisolatesnapshot"); + bundle.putString(FlutterEngineFlags.FLUTTER_ASSETS_DIR.metadataKey, "testassets"); Context context = generateMockContext(bundle, null); FlutterApplicationInfo info = ApplicationInfoLoader.load(context); assertNotNull(info); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/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..70f47167fc2 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 @@ -42,7 +42,7 @@ public class MainActivity extends AppCompatActivity { "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."); + 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-Engine-Flags.md for alternative methods."); break; } } From be6d388ec8d54def6fe4df3d32084faa3e304cb4 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 10 Feb 2026 11:07:03 -0800 Subject: [PATCH 02/17] format --- .../android/FlutterActivityAndFragmentDelegate.java | 2 +- .../embedding/engine/FlutterEngineConnectionRegistry.java | 2 -- .../io/flutter/embedding/engine/FlutterEngineFlagsTest.java | 6 ++++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 037d9af90b9..135bdb7b5a5 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 @@ -28,8 +28,8 @@ import io.flutter.Build.API_LEVELS; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterEngineFlags; import io.flutter.embedding.engine.FlutterEngineCache; +import io.flutter.embedding.engine.FlutterEngineFlags; import io.flutter.embedding.engine.FlutterEngineGroup; import io.flutter.embedding.engine.FlutterEngineGroupCache; import io.flutter.embedding.engine.FlutterShellArgs; 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 d98035c3cb7..49425aa23df 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 @@ -15,8 +15,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; import io.flutter.Log; -import io.flutter.embedding.engine.FlutterEngineFlags; -import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.android.ExclusiveAppComponent; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java index 799fabcacdd..abfae82d3ef 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java @@ -52,7 +52,8 @@ public class FlutterEngineFlagsTest { @Test public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() { FlutterEngineFlags.Flag flag = - FlutterEngineFlags.getFlagByMetadataKey("io.flutter.embedding.android.AOTSharedLibraryName"); + FlutterEngineFlags.getFlagByMetadataKey( + "io.flutter.embedding.android.AOTSharedLibraryName"); assertEquals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME, flag); } @@ -117,6 +118,7 @@ public class FlutterEngineFlagsTest { @Test public void getReplacementFlagIfDeprecated_returnsNullWhenFlagIsNotDeprecated() { - assertNull(FlutterEngineFlags.getReplacementFlagIfDeprecated(FlutterEngineFlags.VM_SNAPSHOT_DATA)); + assertNull( + FlutterEngineFlags.getReplacementFlagIfDeprecated(FlutterEngineFlags.VM_SNAPSHOT_DATA)); } } From d8ae1db343c4fc60d713af31adf8ef8795f5d580 Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:33:05 -0800 Subject: [PATCH 03/17] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../android/io/flutter/embedding/engine/FlutterEngineFlags.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 9474da536f3..a1cb8fccc4d 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -126,7 +126,7 @@ public final class FlutterEngineFlags { /** * The deprecated flag that sets the directory containing Flutter assets. * - *

Please use {@link DEPRECATED_FLUTTER_ASSETS_DIR} instead. + *

Please use {@link FLUTTER_ASSETS_DIR} instead. */ @Deprecated public static final Flag DEPRECATED_FLUTTER_ASSETS_DIR = From 00095d66a7c13f8f1b04132a9cca157cbbf9baac Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 10 Feb 2026 16:50:53 -0800 Subject: [PATCH 04/17] allow EnableDartProfiling in release mode --- .../flutter/embedding/engine/FlutterEngineFlags.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 9474da536f3..86f2a5a7ac6 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -208,6 +208,14 @@ public final class FlutterEngineFlags { public static final Flag ISOLATE_SNAPSHOT_DATA = new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true); + /** + * Enables Dart profiling for use with DevTools. + * + *

Allowed in release mode for testing purposes. + */ + private static final Flag ENABLE_DART_PROFILING = + new Flag("--enable-dart-profiling", "EnableDartProfiling", true); + // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ @@ -263,10 +271,6 @@ public final class FlutterEngineFlags { 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"); From 6bd6535f593d7036a325f4ce6001f8d5e7aa9497 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 10 Feb 2026 16:54:46 -0800 Subject: [PATCH 05/17] add test for enable-dart-profiling --- .../flutter/embedding/engine/loader/FlutterLoaderTest.java | 5 +++++ 1 file changed, 5 insertions(+) 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..5856baedc60 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 @@ -971,8 +971,13 @@ public class FlutterLoaderTest { @Test public void itSetsEnableDartProfilingFromMetadata() { + // Test debug mode. testFlagFromMetadataPresent( "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode( + "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); } @Test From 66397ed6cc08b23c3e5725abb8e1341571e4bc3a Mon Sep 17 00:00:00 2001 From: Camille Simon <43054281+camsim99@users.noreply.github.com> Date: Mon, 9 Feb 2026 14:03:18 -0800 Subject: [PATCH 06/17] [Android] Add mechanism for setting Android engine flags via Android manifest (take 2) (#181632) A continuation of https://github.com/flutter/flutter/pull/177127. Copying from there: # Overview > [!NOTE] > This PR is based on conversation & feedback on go/flutter-android-harden-engine-shell-arguments. Adds a mechanism for setting Android engine flags via the manifest. If a flag is specified on the command line and in manifest metadata, the value specified on the command line will take precedence. Documentation is added on this mechanism Additionally, this PR removes the exposure of`--cache-sksl` command line flag as per [https://github.com/flutter/flutter/issues/140310#issuecomment-2708459007](https://www.google.com/url?q=https://github.com/flutter/flutter/issues/140310%23issuecomment-2708459007&sa=D&source=docs&ust=1761156167162464&usg=AOvVaw3a8ubXTtv3apknY2-P9dKe). Additionally, this PR adds documentation for the only two supported ways of setting engine flags moving forward -- via the command line or manifest. The `Intent` mechanism will be removed when https://github.com/flutter/flutter/issues/180686 is completed (intended to be a follow up to this PR). As the unit tests in this PR only cover setting flags via manifest in debug mode, I will follow up this PR with an integration test to test that flags are appropriately respected/ignored in release mode. See https://github.com/flutter/flutter/pull/178383 for my currently working but WIP draft. Part of https://github.com/flutter/flutter/issues/172553. # Follow up work: ## Add integration test for this new added mechanism This will land as an immediate follow-up to this PR. WIP in https://github.com/flutter/flutter/pull/178383. ## Remove support for setting shell arguments via `Intent` This task will be a follow up to this work + the integration test landing, and will complete work for https://github.com/flutter/flutter/issues/172553. See https://github.com/flutter/flutter/issues/180686 for details. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../engine/Android-Flutter-Shell-Arguments.md | 190 ++++ .../flutter/shell/platform/android/BUILD.gn | 1 + .../embedding/android/FlutterActivity.java | 6 +- .../FlutterActivityAndFragmentDelegate.java | 33 +- .../embedding/android/FlutterFragment.java | 15 +- .../android/FlutterFragmentActivity.java | 4 +- .../embedding/engine/FlutterEngine.java | 4 +- .../FlutterEngineConnectionRegistry.java | 31 +- .../embedding/engine/FlutterShellArgs.java | 585 ++++++++---- .../engine/FlutterShellArgsIntentUtils.java | 169 ++++ .../engine/loader/ApplicationInfoLoader.java | 47 +- .../engine/loader/FlutterLoader.java | 370 +++++--- ...lutterActivityAndFragmentDelegateTest.java | 5 - .../android/FlutterActivityTest.java | 10 +- .../android/FlutterAndroidComponentTest.java | 5 +- .../android/FlutterFragmentTest.java | 6 +- .../FlutterEngineConnectionRegistryTest.java | 54 +- .../FlutterShellArgsIntentUtilsTest.java | 36 + .../engine/FlutterShellArgsTest.java | 126 ++- ...PlayStoreDeferredComponentManagerTest.java | 10 +- .../loader/ApplicationInfoLoaderTest.java | 9 +- .../engine/loader/FlutterLoaderTest.java | 863 +++++++++++++++--- .../flutter/external/FlutterLaunchTests.java | 2 +- .../java/com/example/view/MainActivity.java | 37 +- 24 files changed, 2034 insertions(+), 584 deletions(-) create mode 100644 docs/engine/Android-Flutter-Shell-Arguments.md create mode 100644 engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java create mode 100644 engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java diff --git a/docs/engine/Android-Flutter-Shell-Arguments.md b/docs/engine/Android-Flutter-Shell-Arguments.md new file mode 100644 index 00000000000..41638bbe4f0 --- /dev/null +++ b/docs/engine/Android-Flutter-Shell-Arguments.md @@ -0,0 +1,190 @@ +# Setting Flutter Android engine flags + +You can set flags for the Flutter engine on Android in two different ways: + +- From the command line when launching an app with the Flutter tool +- Via `AndroidManifest.xml` metadata (static, per-build configuration) + +All flags available on Android can be set via the command line **and** via +manifest metadata. See `src/flutter/shell/common/switches.cc` for +the list of all supported flags, and see +`src/flutter/shell/platform/android/io/flutter/embedding/engine/` +`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 2b188414f04..197e5b39237 100644 --- a/engine/src/flutter/shell/platform/android/BUILD.gn +++ b/engine/src/flutter/shell/platform/android/BUILD.gn @@ -255,6 +255,7 @@ 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 e7d982d4a81..fc5d387fb22 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.FlutterShellArgs; +import io.flutter.embedding.engine.FlutterShellArgsIntentUtils; 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 FlutterShellArgs getFlutterShellArgs() { - return FlutterShellArgs.fromIntent(getIntent()); + public String[] getFlutterShellArgs() { + return FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(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 7eadd5c9065..eb02d2eff26 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,6 +38,7 @@ import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.view.SensitiveContentPlugin; import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Delegate that implements all Flutter logic that is the same between a {@link FlutterActivity} and @@ -331,9 +332,12 @@ import java.util.List; "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(), host.getFlutterShellArgs().toArray()) + ? new FlutterEngineGroup(host.getContext(), flutterShellArgs) : engineGroup; flutterEngine = group.createAndRunEngine( @@ -344,6 +348,30 @@ import java.util.List; isFlutterEngineFromHost = false; } + // As part of https://github.com/flutter/flutter/issues/180686, the ability + // to set engine flags via Intent extras is planned to be removed, so warn + // developers that engine shell arguments set that way will be ignored. + private void warnIfEngineFlagsSetViaIntent(@NonNull Intent intent) { + if (intent.getExtras() == null) { + return; + } + + Bundle extras = intent.getExtras(); + Set 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)}. @@ -1090,9 +1118,8 @@ import java.util.List; @NonNull Lifecycle getLifecycle(); - /** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */ @NonNull - FlutterShellArgs getFlutterShellArgs(); + String[] 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 515c40475ef..716b1f3ea04 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,7 +27,6 @@ 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; @@ -256,7 +255,7 @@ public class FlutterFragment extends Fragment private String initialRoute = "/"; private boolean handleDeeplinking = false; private String appBundlePath = null; - private FlutterShellArgs shellArgs = null; + private String[] shellArgs = null; private RenderMode renderMode = RenderMode.surface; private TransparencyMode transparencyMode = TransparencyMode.transparent; private boolean shouldAttachEngineToActivity = true; @@ -332,7 +331,7 @@ public class FlutterFragment extends Fragment /** Any special configuration arguments for the Flutter engine */ @NonNull - public NewEngineFragmentBuilder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) { + public NewEngineFragmentBuilder flutterShellArgs(@NonNull String[] shellArgs) { this.shellArgs = shellArgs; return this; } @@ -459,9 +458,8 @@ public class FlutterFragment extends Fragment dartEntrypointArgs != null ? new ArrayList(dartEntrypointArgs) : null); // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of // conflating. - if (null != shellArgs) { - args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs.toArray()); - } + args.putStringArray( + ARG_FLUTTER_INITIALIZATION_ARGS, shellArgs == null ? new String[0] : shellArgs); args.putString( ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : RenderMode.surface.name()); @@ -1353,10 +1351,9 @@ public class FlutterFragment extends Fragment */ @Override @NonNull - public FlutterShellArgs getFlutterShellArgs() { + public String[] getFlutterShellArgs() { String[] flutterShellArgsArray = getArguments().getStringArray(ARG_FLUTTER_INITIALIZATION_ARGS); - return new FlutterShellArgs( - flutterShellArgsArray != null ? flutterShellArgsArray : new String[] {}); + return flutterShellArgsArray == null ? new String[0] : flutterShellArgsArray; } /** 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 71185dd0f48..ae35ed5da59 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.FlutterShellArgs; +import io.flutter.embedding.engine.FlutterShellArgsIntentUtils; 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(FlutterShellArgs.fromIntent(getIntent())) + .flutterShellArgs(FlutterShellArgsIntentUtils.getFlutterShellCommandLineArgs(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 cf57c07eccd..5d4771843cf 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -177,8 +177,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater { * native library and start a Dart VM. * *

In order to pass Dart VM initialization arguments (see {@link - * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the - * initialization arguments by calling {@link + * io.flutter.embedding.engine.FlutterShellArgs} for all available flags) when creating the VM, + * manually set the initialization arguments by calling {@link * io.flutter.embedding.engine.loader.FlutterLoader#startInitialization(Context)} and {@link * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, * String[])} before constructing the engine. diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java index bcb5addfff6..2428e23fe01 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java @@ -59,6 +59,7 @@ import java.util.Set; // Standard FlutterPlugin @NonNull private final FlutterEngine flutterEngine; + @NonNull private final FlutterLoader flutterLoader; @NonNull private final FlutterPlugin.FlutterPluginBinding pluginBinding; // ActivityAware @@ -100,6 +101,7 @@ import java.util.Set; @NonNull FlutterLoader flutterLoader, @Nullable FlutterEngineGroup group) { this.flutterEngine = flutterEngine; + this.flutterLoader = flutterLoader; pluginBinding = new FlutterPlugin.FlutterPluginBinding( appContext, @@ -326,13 +328,32 @@ import java.util.Set; private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) { this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); + final Intent intent = activity.getIntent(); - final boolean useSoftwareRendering = - activity.getIntent() != null - ? activity - .getIntent() - .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) + // TODO(camsim99): Remove ability to set this flag via Intents. See + // https://github.com/flutter/flutter/issues/180686. + boolean useSoftwareRendering = + intent != null + ? intent.getBooleanExtra( + FlutterShellArgsIntentUtils.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 697ecc742d7..6463f8310e8 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,207 +4,434 @@ package io.flutter.embedding.engine; -import android.content.Context; -import android.content.Intent; -import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import java.util.*; /** - * Arguments that can be delivered to the Flutter shell when it is created. + * Arguments that can be delivered to the Flutter shell on Android. * *

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

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

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

If the same flag is provided both via command line arguments and via AndroidManifest.xml + * metadata, the command line value will take precedence at runtime. */ -@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"; +public final class 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<>(); + private FlutterShellArgs() {} - if (intent.getBooleanExtra(ARG_KEY_TRACE_STARTUP, false)) { - args.add(ARG_TRACE_STARTUP); + /** 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_START_PAUSED, false)) { - args.add(ARG_START_PAUSED); + + /** 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); } - int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0); - if (vmServicePort > 0) { - args.add(ARG_VM_SERVICE_PORT + vmServicePort); + + /** + * 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; } - if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) { - args.add(ARG_DISABLE_SERVICE_AUTH_CODES); + + /** Returns true if this flag requires a value to be specified. */ + public boolean hasValue() { + return commandLineArgument.endsWith("="); } - if (intent.getBooleanExtra(ARG_KEY_ENDLESS_TRACE_BUFFER, false)) { - args.add(ARG_ENDLESS_TRACE_BUFFER); + } + + // 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_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); + 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_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); + return null; } - @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 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); } - /** - * 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); + /** Returns the replacement flag of that given if it is deprecated. */ + public static Flag getReplacementFlagIfDeprecated(Flag flag) { + return DEPRECATED_FLAGS_BY_REPLACEMENT.get(flag); } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java new file mode 100644 index 00000000000..81ffc72fb16 --- /dev/null +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgsIntentUtils.java @@ -0,0 +1,169 @@ +// 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 24c50298358..fb682cdbfcf 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java @@ -10,24 +10,19 @@ import android.content.pm.PackageManager; import android.content.res.XmlResourceParser; import android.os.Bundle; import androidx.annotation.NonNull; +import io.flutter.embedding.engine.FlutterShellArgs; import java.io.IOException; import org.json.JSONArray; import org.xmlpull.v1.XmlPullParserException; /** Loads application information given a Context. */ public final class ApplicationInfoLoader { - // XML Attribute keys supported in AndroidManifest.xml - public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME = - FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME; - public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY = - FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY; - public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY = - FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY; - public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY = - FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY; + // TODO(camsim99): Remove support for these flags: + // https://github.com/flutter/flutter/issues/179276. + // AndroidManifest.xml metadata keys for setting internal respective Flutter configuration values. public static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy"; public static final String PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY = - "io.flutter." + FlutterLoader.AUTOMATICALLY_REGISTER_PLUGINS_KEY; + "io.flutter.automatically-register-plugins"; @NonNull private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) { @@ -47,6 +42,20 @@ public final class ApplicationInfoLoader { return metadata.getString(key, null); } + private static String getStringWithFallback(Bundle metadata, String key, String fallbackKey) { + if (metadata == null) { + return null; + } + + String metadataString = metadata.getString(key, null); + + if (metadataString == null) { + metadataString = metadata.getString(fallbackKey); + } + + return metadataString; + } + private static boolean getBoolean(Bundle metadata, String key, boolean defaultValue) { if (metadata == null) { return defaultValue; @@ -146,11 +155,21 @@ public final class ApplicationInfoLoader { @NonNull public static FlutterApplicationInfo load(@NonNull Context applicationContext) { ApplicationInfo appInfo = getApplicationInfo(applicationContext); + + // TODO(camsim99): Remove support for DEPRECATED_AOT_SHARED_LIBRARY_NAME and + // DEPRECATED_FLUTTER_ASSETS_DIR + // when all usage of the deprecated names has been removed. return new FlutterApplicationInfo( - getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME), - getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY), - getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY), - getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY), + getStringWithFallback( + appInfo.metaData, + 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), getNetworkPolicy(appInfo, applicationContext), appInfo.nativeLibraryDir, getBoolean(appInfo.metaData, PUBLIC_AUTOMATICALLY_REGISTER_PLUGINS_METADATA_KEY, true)); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 07df17b5d85..c4897f83a9b 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,6 +23,7 @@ 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; @@ -33,54 +34,19 @@ 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"; - 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"; + // 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"; - /** - * 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"; + // Flag set for generating GeneratedPluginRegistrant.java. + private static final String FLUTTER_EMBEDDING_KEY = "flutterEmbedding"; // Resource names used for components of the precompiled snapshot. private static final String DEFAULT_LIBRARY = "libflutter.so"; @@ -89,8 +55,7 @@ public class FlutterLoader { private static FlutterLoader instance; - @VisibleForTesting - static final String aotSharedLibraryNameFlag = "--" + AOT_SHARED_LIBRARY_NAME + "="; + private boolean enableSoftwareRendering = false; /** * Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI} and {@link @@ -295,6 +260,21 @@ public class FlutterLoader { */ public void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args) { + ensureInitializationComplete(applicationContext, args, BuildConfig.RELEASE); + } + + /** + * Blocks until initialization of the native system has completed. + * + *

Calling this method multiple times has no effect. + * + * @param applicationContext The Android application context. + * @param args Flags sent to the Flutter runtime. + * @param isRelease Whether or not the Flutter component is running in release mode. + */ + @VisibleForTesting + void ensureInitializationComplete( + @NonNull Context applicationContext, @Nullable String[] args, @NonNull boolean isRelease) { if (initialized) { return; } @@ -311,59 +291,183 @@ public class FlutterLoader { InitResult result = initResultFuture.get(); List shellArgs = new ArrayList<>(); - shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); + // Add engine flags for which defaults set internally take precedent. + shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); shellArgs.add( "--icu-native-lib-path=" + flutterApplicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY); + // 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) { - // 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; - } + 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; } - // 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( - "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + flutterApplicationInfo.isolateSnapshotData); + FlutterShellArgs.VM_SNAPSHOT_DATA.commandLineArgument + + flutterApplicationInfo.vmSnapshotData); + shellArgs.add( + FlutterShellArgs.ISOLATE_SNAPSHOT_DATA.commandLineArgument + + flutterApplicationInfo.isolateSnapshotData); } else { - // Add default AOT shared library name arg. - shellArgs.add(aotSharedLibraryNameFlag + flutterApplicationInfo.aotSharedLibraryName); + // Add default AOT shared library name arg. Note that if a different library + // is set in the manifest, that value will take precendence and the default + // libraries will be used as fallbacks in the order that they are added. + shellArgs.add( + FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + + flutterApplicationInfo.aotSharedLibraryName); // Some devices cannot load the an AOT shared library based on the library name // with no directory path. So, we provide a fully qualified path to the default library // as a workaround for devices where that fails. shellArgs.add( - aotSharedLibraryNameFlag + FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.commandLineArgument + flutterApplicationInfo.nativeLibraryDir + File.separator + flutterApplicationInfo.aotSharedLibraryName); @@ -384,23 +488,17 @@ public class FlutterLoader { shellArgs.add("--log-tag=" + settings.getLogTag()); } - ApplicationInfo applicationInfo = - applicationContext - .getPackageManager() - .getApplicationInfo( - applicationContext.getPackageName(), PackageManager.GET_META_DATA); - Bundle metaData = applicationInfo.metaData; - int oldGenHeapSizeMegaBytes = - metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0; - if (oldGenHeapSizeMegaBytes == 0) { - // default to half of total memory. + if (!oldGenHeapSizeSet.get()) { + // Default to half of total memory. ActivityManager activityManager = (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); activityManager.getMemoryInfo(memInfo); - oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); + int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2); + shellArgs.add( + FlutterShellArgs.OLD_GEN_HEAP_SIZE.commandLineArgument + + String.valueOf(oldGenHeapSizeMegaBytes)); } - shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes); DisplayMetrics displayMetrics = applicationContext.getResources().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; @@ -412,49 +510,10 @@ public class FlutterLoader { shellArgs.add("--prefetched-default-font-manager"); - if (metaData != null) { - if (metaData.containsKey(ENABLE_IMPELLER_META_DATA_KEY)) { - if (metaData.getBoolean(ENABLE_IMPELLER_META_DATA_KEY)) { - shellArgs.add("--enable-impeller=true"); - } else { - shellArgs.add("--enable-impeller=false"); - } - } - if (metaData.getBoolean(ENABLE_VULKAN_VALIDATION_META_DATA_KEY, false)) { - shellArgs.add("--enable-vulkan-validation"); - } - if (metaData.getBoolean(IMPELLER_OPENGL_GPU_TRACING_DATA_KEY, false)) { - shellArgs.add("--enable-opengl-gpu-tracing"); - } - if (metaData.getBoolean(IMPELLER_VULKAN_GPU_TRACING_DATA_KEY, false)) { - shellArgs.add("--enable-vulkan-gpu-tracing"); - } - if (metaData.getBoolean(DISABLE_MERGED_PLATFORM_UI_THREAD_KEY, false)) { - throw new IllegalArgumentException( - DISABLE_MERGED_PLATFORM_UI_THREAD_KEY + " is no longer allowed."); - } - if (metaData.getBoolean(ENABLE_FLUTTER_GPU, false)) { - shellArgs.add("--enable-flutter-gpu"); - } - if (metaData.getBoolean(ENABLE_SURFACE_CONTROL, false)) { - shellArgs.add("--enable-surface-control"); - } - - String backend = metaData.getString(IMPELLER_BACKEND_META_DATA_KEY); - if (backend != null) { - shellArgs.add("--impeller-backend=" + backend); - } - if (metaData.getBoolean(IMPELLER_LAZY_SHADER_MODE)) { - shellArgs.add("--impeller-lazy-shader-mode"); - } - if (metaData.getBoolean(IMPELLER_ANTIALIAS_LINES)) { - shellArgs.add("--impeller-antialias-lines"); - } + if (!isLeakVMSet.get()) { + shellArgs.add(FlutterShellArgs.LEAK_VM.commandLineArgument + "true"); } - final String leakVM = isLeakVM(metaData) ? "true" : "false"; - shellArgs.add("--leak-vm=" + leakVM); - long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; flutterJNI.init( @@ -473,10 +532,49 @@ 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 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. + * Returns whether software rendering is enabled. + * + *

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

If the library lives within the application's internal storage, this means that the * application developer either explicitly placed the library there or set the Android Gradle @@ -484,17 +582,9 @@ public class FlutterLoader { * https://developer.android.com/build/releases/past-releases/agp-4-2-0-release-notes#compress-native-libs-dsl * for more information. */ - private String getSafeAotSharedLibraryNameFlag( - @NonNull Context applicationContext, @NonNull String aotSharedLibraryNameArg) + private String getSafeAotSharedLibraryName( + @NonNull Context applicationContext, @NonNull String aotSharedLibraryPath) throws IOException { - // Isolate AOT shared library path. - if (!aotSharedLibraryNameArg.startsWith(aotSharedLibraryNameFlag)) { - throw new IllegalArgumentException( - "AOT shared library name flag was not specified correctly; please use --aot-shared-library-name=."); - } - String aotSharedLibraryPath = - aotSharedLibraryNameArg.substring(aotSharedLibraryNameFlag.length()); - // Canocalize path for safety analysis. File aotSharedLibraryFile = getFileFromPath(aotSharedLibraryPath); @@ -519,7 +609,7 @@ public class FlutterLoader { boolean isSoFile = aotSharedLibraryPathCanonicalPath.endsWith(".so"); if (livesWithinInternalStorage && isSoFile) { - return aotSharedLibraryNameFlag + aotSharedLibraryPathCanonicalPath; + return aotSharedLibraryPathCanonicalPath; } // If the library does not live within the application's internal storage, we will not use it. Log.e( @@ -535,14 +625,6 @@ public class FlutterLoader { return new File(path); } - private static boolean isLeakVM(@Nullable Bundle metaData) { - final boolean leakVMDefaultValue = true; - if (metaData == null) { - return leakVMDefaultValue; - } - return metaData.getBoolean(LEAK_VM_META_DATA_KEY, leakVMDefaultValue); - } - /** * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background * thread, then invoking {@code callback} on the {@code callbackHandler}. diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index ca2e40d1b96..253ecf5005f 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -40,7 +40,6 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngineCache; import io.flutter.embedding.engine.FlutterEngineGroup; import io.flutter.embedding.engine.FlutterEngineGroupCache; -import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; @@ -89,7 +88,6 @@ public class FlutterActivityAndFragmentDelegateTest { mockHost = mock(FlutterActivityAndFragmentDelegate.Host.class); when(mockHost.getContext()).thenReturn(ctx); when(mockHost.getLifecycle()).thenReturn(mock(Lifecycle.class)); - when(mockHost.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {})); when(mockHost.getDartEntrypointFunctionName()).thenReturn("main"); when(mockHost.getDartEntrypointArgs()).thenReturn(null); when(mockHost.getAppBundlePath()).thenReturn("/fake/path"); @@ -106,7 +104,6 @@ public class FlutterActivityAndFragmentDelegateTest { mockHost2 = mock(FlutterActivityAndFragmentDelegate.Host.class); when(mockHost2.getContext()).thenReturn(ctx); when(mockHost2.getLifecycle()).thenReturn(mock(Lifecycle.class)); - when(mockHost2.getFlutterShellArgs()).thenReturn(new FlutterShellArgs(new String[] {})); when(mockHost2.getDartEntrypointFunctionName()).thenReturn("main"); when(mockHost2.getDartEntrypointArgs()).thenReturn(null); when(mockHost2.getAppBundlePath()).thenReturn("/fake/path"); @@ -471,8 +468,6 @@ public class FlutterActivityAndFragmentDelegateTest { activity -> { when(customMockHost.getActivity()).thenReturn(activity); when(customMockHost.getLifecycle()).thenReturn(mock(Lifecycle.class)); - when(customMockHost.getFlutterShellArgs()) - .thenReturn(new FlutterShellArgs(new String[] {})); when(customMockHost.getDartEntrypointFunctionName()).thenReturn("main"); when(customMockHost.getAppBundlePath()).thenReturn("/fake/path"); when(customMockHost.getInitialRoute()).thenReturn("/"); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java index 6875f8b756b..ce711b86c3a 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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 a9ac78f4fec..2e733635fcd 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,7 +31,6 @@ 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; @@ -302,8 +301,8 @@ public class FlutterAndroidComponentTest { @NonNull @Override - public FlutterShellArgs getFlutterShellArgs() { - return new FlutterShellArgs(new String[] {}); + public String[] getFlutterShellArgs() { + return new String[0]; } @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 581154ed66e..048b72e7506 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().toArray()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); 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().toArray()); + assertArrayEquals(new String[] {}, fragment.getFlutterShellArgs()); 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 091ca42931f..311d66beba1 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java @@ -11,14 +11,19 @@ import static org.mockito.Mockito.*; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.ExclusiveAppComponent; +import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugin.platform.PlatformViewsController2; @@ -85,6 +90,19 @@ public class FlutterEngineConnectionRegistryTest { when(flutterEngine.getPlatformViewsControllerDelegator()) .thenReturn(platformViewsControllerDelegator); + PackageManager packageManager = mock(PackageManager.class); + String packageName = "io.flutter.test"; + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.metaData = new Bundle(); + when(context.getPackageName()).thenReturn(packageName); + when(context.getPackageManager()).thenReturn(packageManager); + try { + when(packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)) + .thenReturn(applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + fail("Mocking application info threw an exception"); + } + FlutterLoader flutterLoader = mock(FlutterLoader.class); ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); @@ -127,41 +145,31 @@ public class FlutterEngineConnectionRegistryTest { } @Test - public void softwareRendering() { + public void attachToActivityConfiguresSoftwareRendering() { Context context = mock(Context.class); - FlutterEngine flutterEngine = mock(FlutterEngine.class); PlatformViewsController platformViewsController = mock(PlatformViewsController.class); - PlatformViewsController2 platformViewsController2 = mock(PlatformViewsController2.class); + FlutterLoader flutterLoader = mock(FlutterLoader.class); + ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); + Activity activity = mock(Activity.class); + Lifecycle lifecycle = mock(Lifecycle.class); + + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); PlatformViewsControllerDelegator platformViewsControllerDelegator = mock(PlatformViewsControllerDelegator.class); when(flutterEngine.getPlatformViewsControllerDelegator()) .thenReturn(platformViewsControllerDelegator); - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - when(flutterEngine.getPlatformViewsController2()).thenReturn(platformViewsController2); - - FlutterLoader flutterLoader = mock(FlutterLoader.class); - - ExclusiveAppComponent appComponent = mock(ExclusiveAppComponent.class); - Activity activity = mock(Activity.class); - when(appComponent.getAppComponent()).thenReturn(activity); - - // Test attachToActivity with an Activity that has no Intent. + when(flutterEngine.getDartExecutor()).thenReturn(mock(DartExecutor.class)); + when(flutterEngine.getRenderer()).thenReturn(mock(FlutterRenderer.class)); FlutterEngineConnectionRegistry registry = new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader, null); - registry.attachToActivity(appComponent, mock(Lifecycle.class)); - verify(platformViewsController).setSoftwareRendering(false); - Intent intent = mock(Intent.class); - when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(false); - when(activity.getIntent()).thenReturn(intent); + when(flutterLoader.getSofwareRenderingEnabledViaManifest()).thenReturn(true); + when(appComponent.getAppComponent()).thenReturn(activity); + when(activity.getIntent()).thenReturn(mock(Intent.class)); - registry.attachToActivity(appComponent, mock(Lifecycle.class)); - verify(platformViewsController, times(2)).setSoftwareRendering(false); + registry.attachToActivity(appComponent, lifecycle); - when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(true); - - registry.attachToActivity(appComponent, mock(Lifecycle.class)); verify(platformViewsController).setSoftwareRendering(true); } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java new file mode 100644 index 00000000000..4f5fbfdf96c --- /dev/null +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterShellArgsIntentUtilsTest.java @@ -0,0 +1,36 @@ +// 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 0f05dc67f67..ebd0085c619 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,35 +2,121 @@ // 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; +package io.flutter.embedding.engine; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import 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 java.lang.reflect.Field; +import java.lang.reflect.Modifier; import org.junit.Test; -import org.junit.runner.RunWith; -@RunWith(AndroidJUnit4.class) public class FlutterShellArgsTest { + @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"); + 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++; + } + } - // Execute the behavior under test. - FlutterShellArgs args = FlutterShellArgs.fromIntent(intent); - HashSet argValues = new HashSet(Arrays.asList(args.toArray())); + // 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()); + } - // 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")); + @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)); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/deferredcomponents/PlayStoreDeferredComponentManagerTest.java index e594ce80057..089bbd3a030 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.loader.ApplicationInfoLoader; +import io.flutter.embedding.engine.FlutterShellArgs; import java.io.File; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,8 +129,8 @@ public class PlayStoreDeferredComponentManagerTest { TestFlutterJNI jni = new TestFlutterJNI(); Bundle bundle = new Bundle(); - bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "custom_name.so"); - bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); + bundle.putString(FlutterShellArgs.AOT_SHARED_LIBRARY_NAME.metadataKey, "custom_name.so"); + bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -162,7 +162,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString(PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so"); - bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); + bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); @@ -194,7 +194,7 @@ public class PlayStoreDeferredComponentManagerTest { Bundle bundle = new Bundle(); bundle.putString( PlayStoreDeferredComponentManager.MAPPING_KEY, "123:module:custom_name.so,3:,4:"); - bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "custom_assets"); + bundle.putString(FlutterShellArgs.FLUTTER_ASSETS_DIR.metadataKey, "custom_assets"); Context spyContext = createSpyContext(bundle); doReturn(null).when(spyContext).getAssets(); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java index 4a5f6183305..58a51c4f29e 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java @@ -23,6 +23,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.embedding.engine.FlutterShellArgs; import java.io.StringReader; import org.junit.Test; import org.junit.runner.RunWith; @@ -70,10 +71,10 @@ public class ApplicationInfoLoaderTest { @Test public void itGeneratesCorrectApplicationInfoWithCustomValues() throws Exception { Bundle bundle = new Bundle(); - bundle.putString(ApplicationInfoLoader.PUBLIC_AOT_SHARED_LIBRARY_NAME, "testaot"); - bundle.putString(ApplicationInfoLoader.PUBLIC_VM_SNAPSHOT_DATA_KEY, "testvmsnapshot"); - bundle.putString(ApplicationInfoLoader.PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, "testisolatesnapshot"); - bundle.putString(ApplicationInfoLoader.PUBLIC_FLUTTER_ASSETS_DIR_KEY, "testassets"); + bundle.putString(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"); Context context = generateMockContext(bundle, null); FlutterApplicationInfo info = ApplicationInfoLoader.load(context); assertNotNull(info); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java index eff98068a1e..a7c8f93cbf8 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -8,6 +8,7 @@ import static android.os.Looper.getMainLooper; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -203,35 +204,6 @@ public class FlutterLoaderTest { assertTrue(arguments.contains(leakVMArg)); } - @Test - public void itSetsTheLeakVMFromMetaData() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metaData = new Bundle(); - metaData.putBoolean("io.flutter.embedding.android.LeakVM", false); - ctx.getApplicationInfo().metaData = metaData; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); - - final String leakVMArg = "--leak-vm=false"; - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - assertTrue(arguments.contains(leakVMArg)); - } - @Test public void itUsesCorrectExecutorService() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); @@ -294,22 +266,32 @@ public class FlutterLoaderTest { } @Test - public void itSetsEnableImpellerFromMetaData() { + public void itSetsDeprecatedAotSharedLibraryNameIfPathIsInInternalStorage() throws IOException { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metaData = new Bundle(); - metaData.putBoolean("io.flutter.embedding.android.EnableImpeller", true); - ctx.getApplicationInfo().metaData = metaData; + FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI)); + Context mockApplicationContext = mock(Context.class); + File internalStorageDir = ctx.getFilesDir(); + Path internalStorageDirAsPathObj = internalStorageDir.toPath(); - FlutterLoader.Settings settings = new FlutterLoader.Settings(); + ctx.getApplicationInfo().nativeLibraryDir = + Paths.get("some", "path", "doesnt", "matter").toString(); assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); + flutterLoader.startInitialization(ctx); + + // Test paths for library living within internal storage. + String librarySoFileName = "library.so"; + Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName); + + String path = testPath.toString(); + Bundle metadata = new Bundle(); + metadata.putString( + "io.flutter.embedding.engine.loader.FlutterLoader.aot-shared-library-name", path); + ctx.getApplicationInfo().metaData = metadata; + + flutterLoader.ensureInitializationComplete(ctx, null); - final String enableImpellerArg = "--enable-impeller=true"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) + verify(mockFlutterJNI) .init( eq(ctx), shellArgsCaptor.capture(), @@ -318,27 +300,51 @@ public class FlutterLoaderTest { anyString(), anyLong(), anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - assertTrue(arguments.contains(enableImpellerArg)); + + List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); + + // This check works because the tests run in debug mode. If run in release (or JIT release) + // mode, actualArgs would contain the default arguments for AOT shared library name on top + // of aotSharedLibraryNameArg. + String canonicalTestPath = testPath.toFile().getCanonicalPath(); + String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; + assertTrue( + "Args sent to FlutterJni.init incorrectly did not include path " + path, + actualArgs.contains(canonicalAotSharedLibraryNameArg)); + + // Reset FlutterLoader and mockFlutterJNI to make more calls to + // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing. + flutterLoader.initialized = false; + clearInvocations(mockFlutterJNI); } @Test - public void itSetsEnableFlutterGPUFromMetaData() { + public void itSetsAotSharedLibraryNameIfPathIsInInternalStorageInReleaseMode() + throws IOException { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metaData = new Bundle(); - metaData.putBoolean("io.flutter.embedding.android.EnableFlutterGPU", true); - ctx.getApplicationInfo().metaData = metaData; + FlutterLoader flutterLoader = spy(new FlutterLoader(mockFlutterJNI)); + Context mockApplicationContext = mock(Context.class); + File internalStorageDir = ctx.getFilesDir(); + Path internalStorageDirAsPathObj = internalStorageDir.toPath(); - FlutterLoader.Settings settings = new FlutterLoader.Settings(); + ctx.getApplicationInfo().nativeLibraryDir = + Paths.get("some", "path", "doesnt", "matter").toString(); assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); + flutterLoader.startInitialization(ctx); + + // Test paths for library living within internal storage. + String librarySoFileName = "library.so"; + Path testPath = internalStorageDirAsPathObj.resolve(librarySoFileName); + + String path = testPath.toString(); + Bundle metadata = new Bundle(); + metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); + ctx.getApplicationInfo().metaData = metadata; + + flutterLoader.ensureInitializationComplete(ctx, null, true); - final String enableImpellerArg = "--enable-flutter-gpu"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) + verify(mockFlutterJNI) .init( eq(ctx), shellArgsCaptor.capture(), @@ -347,66 +353,22 @@ public class FlutterLoaderTest { anyString(), anyLong(), anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - assertTrue(arguments.contains(enableImpellerArg)); - } - @Test - public void itSetsEnableSurfaceControlFromMetaData() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metaData = new Bundle(); - metaData.putBoolean("io.flutter.embedding.android.EnableSurfaceControl", true); - ctx.getApplicationInfo().metaData = metaData; + List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); + // This check works because the tests run in debug mode. If run in release (or JIT release) + // mode, actualArgs would contain the default arguments for AOT shared library name on top + // of aotSharedLibraryNameArg. + String canonicalTestPath = testPath.toFile().getCanonicalPath(); + String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; + assertTrue( + "Args sent to FlutterJni.init incorrectly did not include path " + path, + actualArgs.contains(canonicalAotSharedLibraryNameArg)); - final String disabledControlArg = "--enable-surface-control"; - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - assertTrue(arguments.contains(disabledControlArg)); - } - - @Test - public void itSetsShaderInitModeFromMetaData() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - Bundle metaData = new Bundle(); - metaData.putBoolean("io.flutter.embedding.android.ImpellerLazyShaderInitialization", true); - ctx.getApplicationInfo().metaData = metaData; - - FlutterLoader.Settings settings = new FlutterLoader.Settings(); - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(ctx, settings); - flutterLoader.ensureInitializationComplete(ctx, null); - shadowOf(getMainLooper()).idle(); - - final String shaderModeArg = "--impeller-lazy-shader-mode"; - ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); - verify(mockFlutterJNI, times(1)) - .init( - eq(ctx), - shellArgsCaptor.capture(), - anyString(), - anyString(), - anyString(), - anyLong(), - anyInt()); - List arguments = Arrays.asList(shellArgsCaptor.getValue()); - assertTrue(arguments.contains(shaderModeArg)); + // Reset FlutterLoader and mockFlutterJNI to make more calls to + // FlutterLoader.ensureInitialized and mockFlutterJNI.init for testing. + flutterLoader.initialized = false; + clearInvocations(mockFlutterJNI); } @Test @@ -446,9 +408,11 @@ public class FlutterLoaderTest { for (Path testPath : pathsToTest) { String path = testPath.toString(); - String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path; - String[] args = {aotSharedLibraryNameArg}; - flutterLoader.ensureInitializationComplete(ctx, args); + Bundle metadata = new Bundle(); + metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); + ctx.getApplicationInfo().metaData = metadata; + + flutterLoader.ensureInitializationComplete(ctx, null); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -467,8 +431,7 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = - FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath; + String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; assertTrue( "Args sent to FlutterJni.init incorrectly did not include path " + path, actualArgs.contains(canonicalAotSharedLibraryNameArg)); @@ -523,9 +486,11 @@ public class FlutterLoaderTest { for (Path testPath : pathsToTest) { String path = testPath.toString(); - String aotSharedLibraryNameArg = FlutterLoader.aotSharedLibraryNameFlag + path; - String[] args = {aotSharedLibraryNameArg}; - flutterLoader.ensureInitializationComplete(ctx, args); + Bundle metadata = new Bundle(); + metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", path); + ctx.getApplicationInfo().metaData = metadata; + + flutterLoader.ensureInitializationComplete(ctx, null); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -544,8 +509,7 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. String canonicalTestPath = testPath.toFile().getCanonicalPath(); - String canonicalAotSharedLibraryNameArg = - FlutterLoader.aotSharedLibraryNameFlag + canonicalTestPath; + String canonicalAotSharedLibraryNameArg = "--aot-shared-library-name=" + canonicalTestPath; assertFalse( "Args sent to FlutterJni.init incorrectly included canonical path " + canonicalTestPath, actualArgs.contains(canonicalAotSharedLibraryNameArg)); @@ -572,8 +536,11 @@ public class FlutterLoaderTest { String invalidFilePath = "my\0file.so"; - String[] args = {FlutterLoader.aotSharedLibraryNameFlag + invalidFilePath}; - flutterLoader.ensureInitializationComplete(ctx, args); + Bundle metadata = new Bundle(); + metadata.putString("io.flutter.embedding.android.AOTSharedLibraryName", invalidFilePath); + ctx.getApplicationInfo().metaData = metadata; + + flutterLoader.ensureInitializationComplete(ctx, null); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -592,7 +559,7 @@ public class FlutterLoaderTest { // mode, actualArgs would contain the default arguments for AOT shared library name on top // of aotSharedLibraryNameArg. for (String arg : actualArgs) { - if (arg.startsWith(FlutterLoader.aotSharedLibraryNameFlag)) { + if (arg.startsWith("--aot-shared-library-name=")) { fail(); } } @@ -620,9 +587,11 @@ public class FlutterLoaderTest { when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile); doReturn(realSoFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath(); - String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getPath(); - String[] args = {symlinkArg}; - flutterLoader.ensureInitializationComplete(ctx, args); + Bundle metadata = new Bundle(); + metadata.putString( + "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getPath()); + ctx.getApplicationInfo().metaData = metadata; + flutterLoader.ensureInitializationComplete(ctx, null); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -638,12 +607,14 @@ public class FlutterLoaderTest { List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); String canonicalSymlinkCanonicalizedPath = realSoFile.getCanonicalPath(); + String aotSharedLibraryNameFlag = "--aot-shared-library-name="; + String symlinkAotSharedLibraryNameArg = aotSharedLibraryNameFlag + spySymlinkFile.getPath(); String canonicalAotSharedLibraryNameArg = - FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; + aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; assertFalse( "Args sent to FlutterJni.init incorrectly included absolute symlink path: " + spySymlinkFile.getAbsolutePath(), - actualArgs.contains(symlinkArg)); + actualArgs.contains(symlinkAotSharedLibraryNameArg)); assertTrue( "Args sent to FlutterJni.init incorrectly did not include canonicalized path of symlink: " + canonicalSymlinkCanonicalizedPath, @@ -674,15 +645,17 @@ public class FlutterLoaderTest { List unsafeFiles = Arrays.asList(nonSoFile, fileJustOutsideInternalStorage); Files.deleteIfExists(spySymlinkFile.toPath()); - String symlinkArg = FlutterLoader.aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath(); - String[] args = {symlinkArg}; + Bundle metadata = new Bundle(); + metadata.putString( + "io.flutter.embedding.android.AOTSharedLibraryName", spySymlinkFile.getAbsolutePath()); + ctx.getApplicationInfo().metaData = metadata; for (File unsafeFile : unsafeFiles) { // Simulate a symlink since some filesystems do not support symlinks. when(flutterLoader.getFileFromPath(spySymlinkFile.getPath())).thenReturn(spySymlinkFile); doReturn(unsafeFile.getCanonicalPath()).when(spySymlinkFile).getCanonicalPath(); - flutterLoader.ensureInitializationComplete(ctx, args); + flutterLoader.ensureInitializationComplete(ctx, null); ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI) @@ -698,8 +671,11 @@ public class FlutterLoaderTest { List actualArgs = Arrays.asList(shellArgsCaptor.getValue()); String canonicalSymlinkCanonicalizedPath = unsafeFile.getCanonicalPath(); + String aotSharedLibraryNameFlag = "--aot-shared-library-name="; + String symlinkAotSharedLibraryNameArg = + aotSharedLibraryNameFlag + spySymlinkFile.getAbsolutePath(); String canonicalAotSharedLibraryNameArg = - FlutterLoader.aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; + aotSharedLibraryNameFlag + canonicalSymlinkCanonicalizedPath; assertFalse( "Args sent to FlutterJni.init incorrectly included canonicalized path of symlink: " + canonicalSymlinkCanonicalizedPath, @@ -707,7 +683,7 @@ public class FlutterLoaderTest { assertFalse( "Args sent to FlutterJni.init incorrectly included absolute path of symlink: " + spySymlinkFile.getAbsolutePath(), - actualArgs.contains(symlinkArg)); + actualArgs.contains(symlinkAotSharedLibraryNameArg)); // Clean up created files. spySymlinkFile.delete(); @@ -719,4 +695,623 @@ public class FlutterLoaderTest { clearInvocations(mockFlutterJNI); } } -} \ No newline at end of file + + @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)); + } + } +} 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 16b82490175..d10fcc77c26 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().toArray()); + assertArrayEquals(new String[] {}, flutterActivity.getFlutterShellArgs()); 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 3c53005f354..125868b3af6 100644 --- a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java +++ b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java @@ -15,11 +15,14 @@ import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; +import io.flutter.Log; import io.flutter.plugin.common.BasicMessageChannel; import io.flutter.plugin.common.BasicMessageChannel.MessageHandler; import io.flutter.plugin.common.BasicMessageChannel.Reply; import io.flutter.plugin.common.StringCodec; +import java.util.Arrays; import java.util.ArrayList; +import java.util.List; public class MainActivity extends AppCompatActivity { private static FlutterEngine flutterEngine; @@ -31,34 +34,28 @@ public class MainActivity extends AppCompatActivity { private static final String PING = "ping"; private BasicMessageChannel messageChannel; - private String[] getArgsFromIntent(Intent intent) { - // Before adding more entries to this list, consider that arbitrary - // Android applications can generate intents with extra data and that - // there are many security-sensitive args in the binary. - ArrayList args = new ArrayList<>(); - if (intent.getBooleanExtra("trace-startup", false)) { - args.add("--trace-startup"); + // Previously, this example checked for certain flags set via Intent. Engine + // flags can no longer be set via Intent, so warn developers that Intent extras + // will be ignored and point to alternative methods for setting engine flags. + private void warnIfEngineFlagsSetViaIntent(Intent intent) { + List previouslySupportedFlagsViaIntent = Arrays.asList( + "trace-startup", "start-paused", "enable-dart-profiling"); + for (String flag : previouslySupportedFlagsViaIntent) { + if (intent.hasExtra(flag)) { + Log.w("MainActivity", "Engine flags can no longer be set via Intent on Android. If you wish to set " + flag + ", see https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Shell-Arguments.md for alternative methods."); + break; + } } - if (intent.getBooleanExtra("start-paused", false)) { - args.add("--start-paused"); - } - if (intent.getBooleanExtra("enable-dart-profiling", false)) { - args.add("--enable-dart-profiling"); - } - if (!args.isEmpty()) { - String[] argsArray = new String[args.size()]; - return args.toArray(argsArray); - } - return null; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - String[] args = getArgsFromIntent(getIntent()); + warnIfEngineFlagsSetViaIntent(getIntent()); + if (flutterEngine == null) { - flutterEngine = new FlutterEngine(this, args); + flutterEngine = new FlutterEngine(this); flutterEngine.getDartExecutor().executeDartEntrypoint( DartEntrypoint.createDefault() ); From fcccb6685ae1fd6844d3f9535a295d51d8707cf4 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 11:38:04 -0800 Subject: [PATCH 07/17] add note about deprecation suppressions --- .../flutter/embedding/android/FlutterAndroidComponentTest.java | 2 ++ .../io/flutter/embedding/engine/FlutterEngineFlagsTest.java | 2 ++ 2 files changed, 4 insertions(+) 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 a1fb6437e74..001a4a17aae 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterAndroidComponentTest.java @@ -302,6 +302,8 @@ public class FlutterAndroidComponentTest { @NonNull @Override + // Annotation required because support for setting engine shell arguments via Intent will be + // removed; see https://github.com/flutter/flutter/issues/180686. @SuppressWarnings("deprecation") public FlutterShellArgs getFlutterShellArgs() { return new FlutterShellArgs(new String[] {}); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java index abfae82d3ef..9888384e0a7 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java @@ -34,6 +34,8 @@ public class FlutterEngineFlagsTest { FlutterEngineFlags.ALL_FLAGS.size()); } + // Annotation required because support for setting engine shell arguments via Intent will be + // removed; see https://github.com/flutter/flutter/issues/180686. @SuppressWarnings("deprecation") @Test public void allFlags_haveExpectedMetaDataNamePrefix() { From 3733474a626d7aacbd98e39a9b9675d328443ab7 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 11:51:09 -0800 Subject: [PATCH 08/17] reduce loops from 2 to 1 --- .../engine/loader/FlutterLoader.java | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) 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 594ee93e245..c1726a0c4ef 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 @@ -300,23 +300,6 @@ public class FlutterLoader { + 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(FlutterEngineFlags.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( - FlutterEngineFlags.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. @@ -424,9 +407,15 @@ public class FlutterLoader { + arg + "is not recognized. Please ensure that the flag is defined in the FlutterEngineFlags."); continue; - } else if (flag.equals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME)) { - // This flag has already been handled. - continue; + } else if (flag.equals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME) + || flag.equals(FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME)) { + // Perform security check for path containing application's compiled Dart + // code and potentially user-provided compiled native code. + String aotSharedLibraryPath = + arg.substring( + FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length()); + maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs); + break; } else if (!flag.allowedInRelease && isRelease) { // Flag is not allowed in release builds. Log.w( @@ -533,7 +522,13 @@ public class FlutterLoader { } } - /** Adds the AOT shared library name argument to the shell args if the provided path is safe. */ + /** + * Adds the AOT shared library name argument to the shell args if the provided path is safe. + * + *

If the path is safe, it will be added to the beginning of the arguments list of arguments. + * The earlier specified path takes precedence over any later specified paths for the AOT shared + * library name argument. + */ private void maybeAddAotSharedLibraryNameArg( @NonNull Context applicationContext, @NonNull String aotSharedLibraryPath, @@ -551,6 +546,7 @@ public class FlutterLoader { if (safeAotSharedLibraryName != null) { shellArgs.add( + 0, FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument + safeAotSharedLibraryName); } else { From 9a32c37750cb10a4f38ce33b2a44e07507c4b479 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 11:53:54 -0800 Subject: [PATCH 09/17] add merged-platform-ui-thread --- .../io/flutter/embedding/engine/FlutterEngineFlags.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 94a2fe5bb57..b3bc313496b 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -216,6 +216,14 @@ public final class FlutterEngineFlags { private static final Flag ENABLE_DART_PROFILING = new Flag("--enable-dart-profiling", "EnableDartProfiling", true); + /** + * Sets whether the UI thread and platform thread should be merged. + * + *

Allowed in release mode for performance purposes. + */ + private static final Flag MERGED_PLATFORM_UI_THREAD = + new Flag("--merged-platform-ui-thread", "MergedPlatformUIThread", true); + // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ From 09ab372c2fabf9e9ab480edc634d53ac20d332b4 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 16:50:31 -0800 Subject: [PATCH 10/17] add internal flags --- .../embedding/engine/FlutterEngineFlags.java | 28 ++++++++++++++++--- .../lib/src/android/android_device.dart | 6 +++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index b3bc313496b..cb9519529b4 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -216,6 +216,14 @@ public final class FlutterEngineFlags { private static final Flag ENABLE_DART_PROFILING = new Flag("--enable-dart-profiling", "EnableDartProfiling", true); + /** + * Discards new profiler samples once the buffer is full. Only meaningful when set in conjunction + * with {@link ENABLE_DART_PROFILING}. + * + *

Allowed in release mode to allow the startup performance to be profiled by DevTools. + */ + private static final Flag PROFILE_STARTUP = new Flag("--profile-startup", "ProfileStartup", true); + /** * Sets whether the UI thread and platform thread should be merged. * @@ -224,6 +232,13 @@ public final class FlutterEngineFlags { private static final Flag MERGED_PLATFORM_UI_THREAD = new Flag("--merged-platform-ui-thread", "MergedPlatformUIThread", true); + /** + * Start app with an specific route defined on the framework. + * + *

Allowed in release mode to allow starting with a specific route. + */ + private static final Flag ROUTE = new Flag("--route=", "Route", true); + // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ @@ -266,7 +281,8 @@ public final class FlutterEngineFlags { 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"); + private static final Flag TRACE_STARTUP = + new Flag("--trace-startup", "TraceStartup"); // TODO(camsim99): allow in release? /** Pauses Dart code execution at launch until a debugger is attached. */ private static final Flag START_PAUSED = new Flag("--start-paused", "StartPaused"); @@ -279,9 +295,6 @@ public final class FlutterEngineFlags { private static final Flag ENDLESS_TRACE_BUFFER = new Flag("--endless-trace-buffer", "EndlessTraceBuffer"); - /** 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"); @@ -310,6 +323,13 @@ public final class FlutterEngineFlags { /** Enables logging at all severity levels. */ private static final Flag VERBOSE_LOGGING = new Flag("--verbose-logging", "VerboseLogging"); + /** + * Enables Dart checked mode that enables certain runtime checks and assertions to help catch + * errors during development. + */ + private static final Flag ENABLE_CHECKED_MODE = + new Flag("--enable-checked-mode", "EnableCheckedMode"); + /** * Passes additional flags to the Dart VM. * diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 612388310ff..90d012a3c3c 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -674,7 +674,11 @@ class AndroidDevice extends Device { if (debuggingOptions.debuggingEnabled) ...[ if (debuggingOptions.buildInfo.isDebug) ...[ ...['--ez', 'enable-checked-mode', 'true'], - ...['--ez', 'verify-entry-points', 'true'], + ...[ + '--ez', + 'verify-entry-points', + 'true', + ], // TODO(camsim99): check if this is even used ], if (debuggingOptions.startPaused) ...['--ez', 'start-paused', 'true'], if (debuggingOptions.disableServiceAuthCodes) ...[ From ecb6ea03a6a8a223943c20dd9f39f080992d4dec Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 17:22:51 -0800 Subject: [PATCH 11/17] add all switch_defs and rename manifest file --- docs/engine/Android-Flutter-Engine-Flags.md | 190 ------------------ .../FlutterActivityAndFragmentDelegate.java | 2 +- .../FlutterEngineConnectionRegistry.java | 2 +- .../embedding/engine/FlutterEngineFlags.java | 177 ++++++++++++++-- .../java/com/example/view/MainActivity.java | 2 +- 5 files changed, 168 insertions(+), 205 deletions(-) delete mode 100644 docs/engine/Android-Flutter-Engine-Flags.md diff --git a/docs/engine/Android-Flutter-Engine-Flags.md b/docs/engine/Android-Flutter-Engine-Flags.md deleted file mode 100644 index b9341c47bac..00000000000 --- a/docs/engine/Android-Flutter-Engine-Flags.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/` -`FlutterEngineFlags.java` for the list of flags that can be set for the -Android shell. - -## When to use manifest metadata versus the command line - -Use the manifest when: - -- You want a fixed, reproducible baseline of engine flags - for your app across all launches. This is ideal for CI and for enforcing a - consistent configuration for your app. -- You want to vary flags by build mode or product flavor - via manifest merging. For example, place metadata in - `src/debug/AndroidManifest.xml`, `src/profile/AndroidManifest.xml`, and - `src/release/AndroidManifest.xml` (or per-flavor manifests) to tailor flags - per variant. - -Use the command line when: - -- You want to quickly experiment with a flag for a single run of your app. -- You need to override a flag that is already set in the manifest temporarily for debugging - or testing purposes. - -**Note: If a flag is specified both on the command line and in the manifest, -the command-line value takes precedence at runtime.** - -See below for details on using each method. - -## How to set engine flags from the command line - -When you run a standalone Flutter app with the Flutter tool, engine flags -can be passed directly and are forwarded to the Android engine. Examples: - -```bash -flutter run --trace-startup \ - --enable-software-rendering \ - --dart-flags="--enable-asserts" -``` - -Notes: - -- Flags that take values use the `--flag=value` form (with `=`). The Flutter - tool forwards them in that form to the Android embedding. - -## How to set engine flags in the manifest - -All manifest metadata keys must be prefixed with the package name -`io.flutter.embedding.android` and are suffixed with the metadata name for the -related command line flag as determined in -`src/flutter/shell/platform/android/io/flutter/embedding/engine/` -`FlutterEngineFlags.java`. For example, the `--impeller-lazy-shader-mode=` -command line flag corresponds to the metadata key -`io.flutter.embedding.android.ImpellerLazyShaderInitialization`. - -For flags that take values, set the numeric, string, or boolean value (without -the leading `--flag=` prefix). - -### Examples - -Set the `--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/FlutterEngineFlags`, which marks allowed flags - with `allowedInRelease`). If a disallowed flag is set in release, it will - be ignored. -- If you need different behavior in release vs debug/profile mode, configure it - via variant-specific manifests or product flavors. - -## How to set engine flags dynamically - -As of the writing of this document, setting Flutter shell arguments via an -Android `Intent` is no longer supported. If you need per-launch or -runtime-controlled flags in an add-to-app integration, you may do so -programatically before engine initialization. - -To do that, supply engine arguments directly to a `FlutterEngine` with the -desired flags from the earliest point you can control in your -application. For example, if you are writing an add-to-app app that launches -a `FlutterActivity` or `FlutterFragment`, then you can cache a -`FlutterEngine` that is initialized with your desired -engine flags: - -```kotlin -// Your native Android application -class MyApp : Application() { - override fun onCreate() { - super.onCreate() - // Initialize the Flutter engine with desired flags - val args = arrayOf( - "--trace-startup", - "--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/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 135bdb7b5a5..70d3130e7b5 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 @@ -365,7 +365,7 @@ import java.util.Set; 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-Engine-Flags.md for alternative methods."); + + " on the command line or see https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for alternative methods."); break; } } 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 49425aa23df..458c73f3a5c 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java @@ -347,7 +347,7 @@ import java.util.Set; TAG, "Support for setting engine flags on Android via Intent will soon be dropped; see https://github.com/flutter/flutter/issues/172553 for more information on this breaking change. To migrate, set the " + FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey - + " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Android-Flutter-Engine-Flags.md for more info."); + + " metadata in the application manifest. See https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for more info."); } else { // Check manifest for software rendering configuration. useSoftwareRendering = flutterLoader.getSofwareRenderingEnabledViaManifest(); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index cb9519529b4..b2b31c1502f 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -96,7 +96,7 @@ public final class FlutterEngineFlags { * 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. + *

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); @@ -118,7 +118,7 @@ public final class FlutterEngineFlags { /** * Sets the directory containing Flutter assets. * - *

This is allowed in release to specify custom asset locations in production. + *

Allowed in release to specify custom asset locations in production. */ public static final Flag FLUTTER_ASSETS_DIR = new Flag("--flutter-assets-dir=", "FlutterAssetsDir", true); @@ -126,7 +126,7 @@ public final class FlutterEngineFlags { /** * The deprecated flag that sets the directory containing Flutter assets. * - *

Please use {@link FLUTTER_ASSETS_DIR} instead. + *

Please use {@link FLUTTER_ASSETS_DIR} infstead. */ @Deprecated public static final Flag DEPRECATED_FLUTTER_ASSETS_DIR = @@ -139,7 +139,7 @@ public final class FlutterEngineFlags { /** * Sets the old generation heap size for the Dart VM in megabytes. * - *

This is allowed in release for performance tuning. + *

Allowed in release for performance tuning. */ public static final Flag OLD_GEN_HEAP_SIZE = new Flag("--old-gen-heap-size=", "OldGenHeapSize", true); @@ -147,7 +147,7 @@ public final class FlutterEngineFlags { /** * Enables or disables the Impeller renderer. * - *

This is allowed in release to control which rendering backend is used in production. + *

Allowed in release to control which rendering backend is used in production. */ private static final Flag ENABLE_IMPELLER = new Flag("--enable-impeller=", "EnableImpeller", true); @@ -155,7 +155,7 @@ public final class FlutterEngineFlags { /** * Specifies the backend to use for Impeller rendering. * - *

This is allowed in release to select a specific graphics backend for Impeller in production. + *

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); @@ -163,7 +163,7 @@ public final class FlutterEngineFlags { /** * Enables Android SurfaceControl for rendering. * - *

This is allowed in release to opt-in to this rendering feature in production. + *

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); @@ -171,7 +171,7 @@ public final class FlutterEngineFlags { /** * Enables the Flutter GPU backend. * - *

This is allowed in release for developers to use the Flutter GPU backend in production. + *

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); @@ -179,7 +179,7 @@ public final class FlutterEngineFlags { /** * Enables lazy initialization of Impeller shaders. * - *

This is allowed in release for performance tuning of the Impeller backend. + *

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); @@ -187,7 +187,7 @@ public final class FlutterEngineFlags { /** * Enables antialiasing for lines in Impeller. * - *

This is allowed in release to control rendering quality in production. + *

Allowed in release to control rendering quality in production. */ private static final Flag IMPELLER_ANTIALIAS_LINES = new Flag("--impeller-antialias-lines", "ImpellerAntialiasLines", true); @@ -195,7 +195,7 @@ public final class FlutterEngineFlags { /** * Specifies the path to the VM snapshot data file. * - *

This is allowed in release to support different snapshot configurations. + *

Allowed in release to support different snapshot configurations. */ public static final Flag VM_SNAPSHOT_DATA = new Flag("--vm-snapshot-data=", "VmSnapshotData", true); @@ -203,7 +203,7 @@ public final class FlutterEngineFlags { /** * Specifies the path to the isolate snapshot data file. * - *

This is allowed in release to support different snapshot configurations. + *

Allowed in release to support different snapshot configurations. */ public static final Flag ISOLATE_SNAPSHOT_DATA = new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true); @@ -239,6 +239,60 @@ public final class FlutterEngineFlags { */ private static final Flag ROUTE = new Flag("--route=", "Route", true); + /** + * Specifies the name of the *.so containing AOT compiled Dart assets for launching the service + * isolate. + * + *

Allowed in release mode to support AOT compilation for service isolates in + * production. + */ + // TODO(camsim99): subject this to internal storage check + private static final Flag AOT_VM_SERVICE_SHARED_LIBRARY_NAME = + new Flag("--aot-vmservice-shared-library-name=", "AotVMServiceSharedLibraryName", true); + + /** + * Specifies the path to the cache directory. + * + *

Allowed in release mode to enable custom cache directory locations in production. + */ + private static final Flag CACHE_DIR_PATH = new Flag("--cache-dir-path=", "CacheDirPath", true); + + /** + * Specifies the path to the ICU data file. + * + *

Allowed in release mode to support custom ICU data files in production. + */ + private static final Flag ICU_DATA_FILE_PATH = + new Flag("--icu-data-file-path=", "ICUDataFilePath", true); + + /** Prefix for the symbols representing ICU data linked into the Flutter library. */ + private static final Flag ICU_SYMBOL_PREFIX = + new Flag("--icu-symbol-prefix=", "ICUSymbolPrefix", true); + + /** Path to the library file that exports the ICU data. */ + private static final Flag ICU_NATIVE_LIB_PATH = + new Flag("--icu-native-lib-path=", "ICUNativeLibPath", true); + + /** + * Allow the VM service to fallback to automatic port selection if binding to a specified port + * fails. + * + *

Allowed in release to allow the VM service to start even if the default port is + * blocked. + */ + private static final Flag ENABLE_SERVICE_PORT_FALLBACK = + new Flag("--enable-service-port-fallback", "EnableServicePortFallback", true); + + /** + * JSON encoded network policy per domain. This overrides the DisallowInsecureConnections switch. + * Embedder can specify whether to allow or disallow insecure connections at a domain level. + * + *

Allowed in release mode to enable fine-grained control over network security + * policies in production. + */ + private static final Flag DOMAIN_NETWORK_POLICY = + new Flag("--domain-network-policy=", "DomainNetworkPolicy", true); + // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ @@ -330,6 +384,82 @@ public final class FlutterEngineFlags { private static final Flag ENABLE_CHECKED_MODE = new Flag("--enable-checked-mode", "EnableCheckedMode"); + /** The hostname/IP address on which the Dart VM Service should be served. */ + private static final Flag DEVICE_VM_SERVICE_HOST = + new Flag("--vm-service-host=", "DeviceVMServiceHost", true); + + /** Specifies the path to the directory containing the snapshot assets. */ + private static final Flag SNAPSHOT_ASSET_PATH = + new Flag("--snapshot-asset-path=", "SnapshotAssetPath"); + + /** Specifies the path to the VM snapshot instructions. */ + private static final Flag VM_SNAPSHOT_INSTRUCTIONS = + new Flag("--vm-snapshot-instr=", "VmSnapshotInstructions"); + + /** Specifies the path to the isolate snapshot instructions. */ + private static final Flag ISOLATE_SNAPSHOT_INSTRUCTIONS = + new Flag("--isolate-snapshot-instr=", "IsolateSnapshotInstructions"); + + /** Disable the Dart VM Service. */ + private static final Flag DISABLE_VM_SERVICE = + new Flag("--disable-vm-service", "DisableVMService"); + + /** Disable mDNS Dart VM Service publication. */ + private static final Flag DISABLE_VM_SERVICE_PUBLICATION = + new Flag("--disable-vm-service-publication", "DisableVMServicePublication"); + + /** Disable Dart asserts. */ + private static final Flag DISABLE_DART_ASSERTS = + new Flag("--disable-dart-asserts", "DisableDartAsserts"); + /** Turns off all concurrent GC activities. */ + private static final Flag ENABLE_SERIAL_GC = new Flag("--enable-serial-gc", "EnableSerialGC"); + + /** + * By default, dart:io allows all socket connections. If this switch is set, all insecure + * connections are rejected. + */ + private static final Flag DISALLOW_INSECURE_CONNECTIONS = + new Flag("--disallow-insecure-connections", "DisallowInsecureConnections"); + + /** + * Uses separate threads for the platform, UI, GPU and IO task runners. By default, a single + * thread is used for all task runners. Only available in the flutter_tester. + */ + private static final Flag FORCE_MULTITHREADING = + new Flag("--force-multithreading", "ForceMultithreading"); + + /** + * Only cache the shader in SkSL instead of binary or GLSL. This should only be used during + * development phases. The generated SkSLs can later be used in the release build for shader + * precompilation at launch in order to eliminate the shader-compile jank. + */ + private static final Flag CACHE_SKSL = new Flag("--cache-sksl", "CacheSkSL"); + + /** + * Prevents usage of any non-test fonts unless they were explicitly Loaded via dart:ui font APIs. + * This option is only available on the desktop test shells. + */ + private static final Flag DISABLE_ASSET_FONTS = + new Flag("--disable-asset-fonts", "DisableAssetFonts"); + + /** + * Indicates whether the embedding started a prefetch of the default font manager before creating + * the engine. + */ + private static final Flag PREFETCHED_DEFAULT_FONT_MANAGER = + new Flag("--prefetched-default-font-manager", "PrefetchedDefaultFontManager"); + + /** In non-interactive mode, keep the shell running after the Dart script has completed. */ + private static final Flag RUN_FOREVER = new Flag("--run-forever", "RunForever"); + + /** Enable support for isolates that run on the platform thread. */ + private static final Flag ENABLE_PLATFORM_ISOLATES = + new Flag("--enable-platform-isolates", "EnablePlatformIsolates"); + + /** Enable the SurfaceControl backed swapchain when supported. */ + private static final Flag ENABLE_ANDROID_SURFACE_CONTROL = + new Flag("--enable-surface-control", "EnableAndroidSurfaceControl"); + /** * Passes additional flags to the Dart VM. * @@ -386,6 +516,29 @@ public final class FlutterEngineFlags { PURGE_PERSISTENT_CACHE, VERBOSE_LOGGING, DART_FLAGS, + AOT_VM_SERVICE_SHARED_LIBRARY_NAME, + SNAPSHOT_ASSET_PATH, + VM_SNAPSHOT_INSTRUCTIONS, + ISOLATE_SNAPSHOT_INSTRUCTIONS, + CACHE_DIR_PATH, + ICU_DATA_FILE_PATH, + ICU_SYMBOL_PREFIX, + ICU_NATIVE_LIB_PATH, + DEVICE_VM_SERVICE_HOST, + DISABLE_VM_SERVICE, + DISABLE_VM_SERVICE_PUBLICATION, + ENABLE_SERVICE_PORT_FALLBACK, + DISABLE_DART_ASSERTS, + ENABLE_SERIAL_GC, + DISALLOW_INSECURE_CONNECTIONS, + DOMAIN_NETWORK_POLICY, + FORCE_MULTITHREADING, + CACHE_SKSL, + DISABLE_ASSET_FONTS, + PREFETCHED_DEFAULT_FONT_MANAGER, + RUN_FOREVER, + ENABLE_PLATFORM_ISOLATES, + ENABLE_ANDROID_SURFACE_CONTROL, DISABLE_MERGED_PLATFORM_UI_THREAD, DEPRECATED_AOT_SHARED_LIBRARY_NAME, DEPRECATED_FLUTTER_ASSETS_DIR)); 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 70f47167fc2..73fd7e33fa0 100644 --- a/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java +++ b/examples/flutter_view/android/app/src/main/java/com/example/view/MainActivity.java @@ -42,7 +42,7 @@ public class MainActivity extends AppCompatActivity { "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-Engine-Flags.md for alternative methods."); + Log.w("MainActivity", "Engine flags can no longer be set via Intent on Android. If you wish to set " + flag + ", see https://github.com/flutter/flutter/blob/main/docs/engine/Flutter-Android-Engine-Flags.md for alternative methods."); break; } } From e5462835d31cb20777b66a4990e4149ba6fb08f9 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 17:29:15 -0800 Subject: [PATCH 12/17] add test flag + logs --- .../embedding/engine/FlutterEngineFlags.java | 16 +++++++++------- .../embedding/engine/loader/FlutterLoader.java | 9 +++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index b2b31c1502f..0112dee690e 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -243,8 +243,7 @@ public final class FlutterEngineFlags { * Specifies the name of the *.so containing AOT compiled Dart assets for launching the service * isolate. * - *

Allowed in release mode to support AOT compilation for service isolates in - * production. + *

Allowed in release mode to support AOT compilation for service isolates in production. */ // TODO(camsim99): subject this to internal storage check private static final Flag AOT_VM_SERVICE_SHARED_LIBRARY_NAME = @@ -277,8 +276,7 @@ public final class FlutterEngineFlags { * Allow the VM service to fallback to automatic port selection if binding to a specified port * fails. * - *

Allowed in release to allow the VM service to start even if the default port is - * blocked. + *

Allowed in release to allow the VM service to start even if the default port is blocked. */ private static final Flag ENABLE_SERVICE_PORT_FALLBACK = new Flag("--enable-service-port-fallback", "EnableServicePortFallback", true); @@ -287,8 +285,8 @@ public final class FlutterEngineFlags { * JSON encoded network policy per domain. This overrides the DisallowInsecureConnections switch. * Embedder can specify whether to allow or disallow insecure connections at a domain level. * - *

Allowed in release mode to enable fine-grained control over network security - * policies in production. + *

Allowed in release mode to enable fine-grained control over network security policies in + * production. */ private static final Flag DOMAIN_NETWORK_POLICY = new Flag("--domain-network-policy=", "DomainNetworkPolicy", true); @@ -321,6 +319,9 @@ public final class FlutterEngineFlags { private static final Flag ENABLE_VULKAN_GPU_TRACING = new Flag("--enable-vulkan-gpu-tracing", "EnableVulkanGPUTracing"); + /** Fake flag used for integration testing of the Android embedding processing engine flags. */ + private static final Flag TEST_FLAG = new Flag("--test-flag=", "TestFlag"); + /** * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's * metadata in the application block in AndroidManifest.xml. Set it to true in to leave the Dart @@ -541,7 +542,8 @@ public final class FlutterEngineFlags { ENABLE_ANDROID_SURFACE_CONTROL, DISABLE_MERGED_PLATFORM_UI_THREAD, DEPRECATED_AOT_SHARED_LIBRARY_NAME, - DEPRECATED_FLUTTER_ASSETS_DIR)); + DEPRECATED_FLUTTER_ASSETS_DIR, + TEST_FLAG)); // Flags that have been turned off. private static final List DISABLED_FLAGS = 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 c1726a0c4ef..31b184eafb7 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 @@ -327,6 +327,10 @@ public class FlutterLoader { + metadataKey + " is not recognized. Please ensure that the flag is defined in the FlutterEngineFlags."); return; + } else if (flag == FlutterEngineFlags.TEST_FLAG) { + Log.w( + TAG, + "For testing purposes only: test flag specified in the manifest was loaded by the FlutterLoader."); } else if (FlutterEngineFlags.isDisabled(flag)) { // Do not allow disabled flags. throw new IllegalArgumentException( @@ -407,6 +411,11 @@ public class FlutterLoader { + arg + "is not recognized. Please ensure that the flag is defined in the FlutterEngineFlags."); continue; + } else if (flag.equals(FlutterEngineFlags.TEST_FLAG)) { + Log.w( + TAG, + "For testing purposes only: test flag specified on the command line was loaded by the FlutterLoader."); + continue; } else if (flag.equals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME) || flag.equals(FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME)) { // Perform security check for path containing application's compiled Dart From 3f6e95ada3947a086271ec25d53acde99bf7bfa8 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 17:29:25 -0800 Subject: [PATCH 13/17] add renamed markdown file --- docs/engine/Flutter-Android-Engine-Flags.md | 190 ++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 docs/engine/Flutter-Android-Engine-Flags.md diff --git a/docs/engine/Flutter-Android-Engine-Flags.md b/docs/engine/Flutter-Android-Engine-Flags.md new file mode 100644 index 00000000000..b9341c47bac --- /dev/null +++ b/docs/engine/Flutter-Android-Engine-Flags.md @@ -0,0 +1,190 @@ +# Setting Flutter Android engine flags + +You can set flags for the Flutter engine on Android in two different ways: + +- From the command line when launching an app with the Flutter tool +- Via `AndroidManifest.xml` metadata (static, per-build configuration) + +All flags available on Android can be set via the command line **and** via +manifest metadata. See `src/flutter/shell/common/switches.cc` for +the list of all supported flags, and see +`src/flutter/shell/platform/android/io/flutter/embedding/engine/` +`FlutterEngineFlags.java` for the list of flags that can be set for the +Android shell. + +## When to use manifest metadata versus the command line + +Use the manifest when: + +- You want a fixed, reproducible baseline of engine flags + for your app across all launches. This is ideal for CI and for enforcing a + consistent configuration for your app. +- You want to vary flags by build mode or product flavor + via manifest merging. For example, place metadata in + `src/debug/AndroidManifest.xml`, `src/profile/AndroidManifest.xml`, and + `src/release/AndroidManifest.xml` (or per-flavor manifests) to tailor flags + per variant. + +Use the command line when: + +- You want to quickly experiment with a flag for a single run of your app. +- You need to override a flag that is already set in the manifest temporarily for debugging + or testing purposes. + +**Note: If a flag is specified both on the command line and in the manifest, +the command-line value takes precedence at runtime.** + +See below for details on using each method. + +## How to set engine flags from the command line + +When you run a standalone Flutter app with the Flutter tool, engine flags +can be passed directly and are forwarded to the Android engine. Examples: + +```bash +flutter run --trace-startup \ + --enable-software-rendering \ + --dart-flags="--enable-asserts" +``` + +Notes: + +- Flags that take values use the `--flag=value` form (with `=`). The Flutter + tool forwards them in that form to the Android embedding. + +## How to set engine flags in the manifest + +All manifest metadata keys must be prefixed with the package name +`io.flutter.embedding.android` and are suffixed with the metadata name for the +related command line flag as determined in +`src/flutter/shell/platform/android/io/flutter/embedding/engine/` +`FlutterEngineFlags.java`. For example, the `--impeller-lazy-shader-mode=` +command line flag corresponds to the metadata key +`io.flutter.embedding.android.ImpellerLazyShaderInitialization`. + +For flags that take values, set the numeric, string, or boolean value (without +the leading `--flag=` prefix). + +### Examples + +Set the `--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/FlutterEngineFlags`, which marks allowed flags + with `allowedInRelease`). If a disallowed flag is set in release, it will + be ignored. +- If you need different behavior in release vs debug/profile mode, configure it + via variant-specific manifests or product flavors. + +## How to set engine flags dynamically + +As of the writing of this document, setting Flutter shell arguments via an +Android `Intent` is no longer supported. If you need per-launch or +runtime-controlled flags in an add-to-app integration, you may do so +programatically before engine initialization. + +To do that, supply engine arguments directly to a `FlutterEngine` with the +desired flags from the earliest point you can control in your +application. For example, if you are writing an add-to-app app that launches +a `FlutterActivity` or `FlutterFragment`, then you can cache a +`FlutterEngine` that is initialized with your desired +engine flags: + +```kotlin +// Your native Android application +class MyApp : Application() { + override fun onCreate() { + super.onCreate() + // Initialize the Flutter engine with desired flags + val args = arrayOf( + "--trace-startup", + "--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) + } +} +``` From 421fb77440a9417be32fc4598a2c3ceb288eff00 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 17 Feb 2026 17:41:15 -0800 Subject: [PATCH 14/17] loop through flags instead of manifest --- .../embedding/engine/FlutterEngineFlags.java | 1 + .../engine/loader/FlutterLoader.java | 157 +++++++++--------- 2 files changed, 76 insertions(+), 82 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 0112dee690e..1c07aa56aff 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -576,6 +576,7 @@ public final class FlutterEngineFlags { } /** Looks up a {@link Flag} by its metadataKey. */ + // TODO(camsim99): determine if I even need this anymore public static Flag getFlagByMetadataKey(String key) { Flag flag = FLAG_BY_META_DATA_KEY.get(key); Flag replacementFlag = getReplacementFlagIfDeprecated(flag); 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 31b184eafb7..dbf1eb15431 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 @@ -45,9 +45,6 @@ public class FlutterLoader { private static final String AOT_VMSERVICE_SHARED_LIBRARY_NAME = "aot-vmservice-shared-library-name"; - // Flag set for generating GeneratedPluginRegistrant.java. - private static final String FLUTTER_EMBEDDING_KEY = "flutterEmbedding"; - // Resource names used for components of the precompiled snapshot. private static final String DEFAULT_LIBRARY = "libflutter.so"; private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin"; @@ -313,88 +310,84 @@ public class FlutterLoader { final AtomicBoolean isLeakVMSet = new AtomicBoolean(false); if (applicationMetaData != null) { - applicationMetaData.keySet().stream() - .filter(metadataKey -> !metadataKey.equals(FLUTTER_EMBEDDING_KEY)) - .forEach( - metadataKey -> { - FlutterEngineFlags.Flag flag = - FlutterEngineFlags.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 FlutterEngineFlags."); - return; - } else if (flag == FlutterEngineFlags.TEST_FLAG) { - Log.w( - TAG, - "For testing purposes only: test flag specified in the manifest was loaded by the FlutterLoader."); - } else if (FlutterEngineFlags.isDisabled(flag)) { - // Do not allow disabled flags. - throw new IllegalArgumentException( - metadataKey - + " is disabled and no longer allowed. Please remove this flag from your application manifest."); - } else if (FlutterEngineFlags.getReplacementFlagIfDeprecated(flag) != null) { - Log.w( - TAG, - "If you are trying to specify " - + flag.metadataKey - + " in your application manifest, please make sure to use the new metadata key name: " - + FlutterEngineFlags.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; - } + for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) { + if (!applicationMetaData.containsKey(flag.metadataKey)) { + continue; + } - // Handle special cases for specific flags. - if (flag == FlutterEngineFlags.OLD_GEN_HEAP_SIZE) { - // Mark if old gen heap size is set to track whether or not to set default - // internally. - oldGenHeapSizeSet.set(true); - } else if (flag == FlutterEngineFlags.LEAK_VM) { - // Mark if leak VM is set to track whether or not to set default internally. - isLeakVMSet.set(true); - } else if (flag == FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING) { - // Enabling software rendering impacts platform views, so save this value - // so that the PlatformViewsController can be properly configured. - enableSoftwareRendering = - applicationMetaData.getBoolean( - FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey, false); - } else if (flag == FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME - || flag == FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME) { - // Perform security check for path containing application's compiled Dart - // code and potentially user-provided compiled native code. - String aotSharedLibraryPath = applicationMetaData.getString(metadataKey); - maybeAddAotSharedLibraryNameArg( - applicationContext, aotSharedLibraryPath, shellArgs); - return; - } + Object valueObj = applicationMetaData.get(flag.metadataKey); + String value = valueObj != null ? valueObj.toString() : null; - // 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; - } + // Check if flag is valid: - shellArgs.add(arg); - }); + if (flag == FlutterEngineFlags.TEST_FLAG) { + Log.w( + TAG, + "For testing purposes only: test flag specified in the manifest was loaded by the FlutterLoader."); + } else if (FlutterEngineFlags.isDisabled(flag)) { + // Do not allow disabled flags. + throw new IllegalArgumentException( + metadataKey + + " is disabled and no longer allowed. Please remove this flag from your application manifest."); + } else if (FlutterEngineFlags.getReplacementFlagIfDeprecated(flag) != null) { + Log.w( + TAG, + "If you are trying to specify " + + flag.metadataKey + + " in your application manifest, please make sure to use the new metadata key name: " + + FlutterEngineFlags.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 == FlutterEngineFlags.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 == FlutterEngineFlags.LEAK_VM) { + // Mark if leak VM is set to track whether or not to set default internally. + isLeakVMSet.set(true); + } else if (flag == FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING) { + // Enabling software rendering impacts platform views, so save this value + // so that the PlatformViewsController can be properly configured. + enableSoftwareRendering = + applicationMetaData.getBoolean( + FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING.metadataKey, false); + } else if (flag == FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME + || flag == FlutterEngineFlags.DEPRECATED_AOT_SHARED_LIBRARY_NAME) { + // Perform security check for path containing application's compiled Dart + // code and potentially user-provided compiled native code. + String aotSharedLibraryPath = applicationMetaData.getString(metadataKey); + maybeAddAotSharedLibraryNameArg( + applicationContext, aotSharedLibraryPath, shellArgs); + 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 From a2a4908161cc571cc722dfca28f94ec6c93359fd Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Wed, 18 Feb 2026 10:52:04 -0800 Subject: [PATCH 15/17] some good stuff some bad --- .../embedding/engine/FlutterEngineFlags.java | 17 +- .../engine/loader/FlutterLoader.java | 24 +- .../engine/loader/FlutterLoaderTest.java | 234 +++++++++++++++--- 3 files changed, 231 insertions(+), 44 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 1c07aa56aff..d75829baed5 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -149,9 +149,16 @@ public final class FlutterEngineFlags { * *

Allowed in release to control which rendering backend is used in production. */ - private static final Flag ENABLE_IMPELLER = - new Flag("--enable-impeller=", "EnableImpeller", true); + private static final Flag DISBALE_IMPELLER = + new Flag("--enable-impeller=", "EnableOrDisableImpeller", true); + /** + * Enables Impeller. + * + *

Allowed in release to control which rendering backend is used in production. + */ + private static final Flag ENABLE_IMPELLER = + new Flag("--enable-impeller", "EnableImpeller", true); /** * Specifies the backend to use for Impeller rendering. * @@ -320,7 +327,8 @@ public final class FlutterEngineFlags { new Flag("--enable-vulkan-gpu-tracing", "EnableVulkanGPUTracing"); /** Fake flag used for integration testing of the Android embedding processing engine flags. */ - private static final Flag TEST_FLAG = new Flag("--test-flag=", "TestFlag"); + @VisibleForTesting + public static final Flag TEST_FLAG = new Flag("--test-flag", "TestFlag"); /** * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's @@ -412,6 +420,7 @@ public final class FlutterEngineFlags { /** Disable Dart asserts. */ private static final Flag DISABLE_DART_ASSERTS = new Flag("--disable-dart-asserts", "DisableDartAsserts"); + /** Turns off all concurrent GC activities. */ private static final Flag ENABLE_SERIAL_GC = new Flag("--enable-serial-gc", "EnableSerialGC"); @@ -540,9 +549,11 @@ public final class FlutterEngineFlags { RUN_FOREVER, ENABLE_PLATFORM_ISOLATES, ENABLE_ANDROID_SURFACE_CONTROL, + MERGED_PLATFORM_UI_THREAD, DISABLE_MERGED_PLATFORM_UI_THREAD, DEPRECATED_AOT_SHARED_LIBRARY_NAME, DEPRECATED_FLUTTER_ASSETS_DIR, + DISBALE_IMPELLER, TEST_FLAG)); // Flags that have been turned off. 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 dbf1eb15431..7b3446a13bc 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 @@ -311,19 +311,18 @@ public class FlutterLoader { if (applicationMetaData != null) { for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) { - if (!applicationMetaData.containsKey(flag.metadataKey)) { + String metadataKey = flag.metadataKey; + if (!applicationMetaData.containsKey(metadataKey)) { continue; } - Object valueObj = applicationMetaData.get(flag.metadataKey); - String value = valueObj != null ? valueObj.toString() : null; - // Check if flag is valid: if (flag == FlutterEngineFlags.TEST_FLAG) { Log.w( TAG, "For testing purposes only: test flag specified in the manifest was loaded by the FlutterLoader."); + continue; } else if (FlutterEngineFlags.isDisabled(flag)) { // Do not allow disabled flags. throw new IllegalArgumentException( @@ -333,9 +332,9 @@ public class FlutterLoader { Log.w( TAG, "If you are trying to specify " - + flag.metadataKey + + metadataKey + " in your application manifest, please make sure to use the new metadata key name: " - + FlutterEngineFlags.getReplacementFlagIfDeprecated(flag).metadataKey); + + FlutterEngineFlags.getReplacementFlagIfDeprecated(flag)); } else if (!flag.allowedInRelease && isRelease) { // Manifest flag is not allowed in release builds. Log.w( @@ -343,7 +342,7 @@ public class FlutterLoader { "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; + continue; } // Handle special cases for specific flags: @@ -351,7 +350,7 @@ public class FlutterLoader { if (flag == FlutterEngineFlags.OLD_GEN_HEAP_SIZE) { // Mark if old gen heap size is set to track whether or not to set default // internally. - oldGenHeapSizeSet.set(true); + oldGenHeapSizeSet.set(true); // TODO(camsim99): check if I need atomic boolean anymore. } else if (flag == FlutterEngineFlags.LEAK_VM) { // Mark if leak VM is set to track whether or not to set default internally. isLeakVMSet.set(true); @@ -368,7 +367,7 @@ public class FlutterLoader { String aotSharedLibraryPath = applicationMetaData.getString(metadataKey); maybeAddAotSharedLibraryNameArg( applicationContext, aotSharedLibraryPath, shellArgs); - return; + continue; } // Add flag to shell args. @@ -377,18 +376,19 @@ public class FlutterLoader { Object valueObj = applicationMetaData.get(metadataKey); String value = valueObj != null ? valueObj.toString() : null; if (value == null) { - Log.w( + Log.e( TAG, "Flag with metadata key " + metadataKey + " requires a value, but no value was found. Please ensure that the value is a string."); - return; + continue; } 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 @@ -417,7 +417,7 @@ public class FlutterLoader { arg.substring( FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME.commandLineArgument.length()); maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs); - break; + continue; } else if (!flag.allowedInRelease && isRelease) { // Flag is not allowed in release builds. Log.w( 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 5856baedc60..e2fe34fbe1e 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 @@ -700,7 +700,7 @@ public class FlutterLoaderTest { public void itSetsEnableSoftwareRenderingFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.EnableSoftwareRendering", - true, + null, "--enable-software-rendering"); } @@ -727,7 +727,7 @@ public class FlutterLoaderTest { public void itSetsSkiaDeterministicRenderingFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.SkiaDeterministicRendering", - true, + null, "--skia-deterministic-rendering"); } @@ -793,16 +793,28 @@ public class FlutterLoaderTest { } @Test - public void itSetsEnableImpellerFromMetadata() { + public void itSetsDisableImpellerFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true"); + "io.flutter.embedding.android.DisableImpeller", true, "--enable-impeller=true"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableImpeller", true, "--enable-impeller=true"); + "io.flutter.embedding.android.DisableImpeller", true, "--enable-impeller=true"); } + @Test + public void itSetsEnableImpellerFromMetadata() { + // Test debug mode. + testFlagFromMetadataPresent( + "io.flutter.embedding.android.EnableImpeller", null, "--enable-impeller"); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode( + "io.flutter.embedding.android.EnableImpeller", null, "--enable-impeller"); + } + + @Test public void itSetsImpellerBackendFromMetadata() { String expectedImpellerBackend = "Vulkan"; @@ -824,22 +836,22 @@ public class FlutterLoaderTest { public void itSetsEnableSurfaceControlFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control"); + "io.flutter.embedding.android.EnableSurfaceControl", null, "--enable-surface-control"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableSurfaceControl", true, "--enable-surface-control"); + "io.flutter.embedding.android.EnableSurfaceControl", null, "--enable-surface-control"); } @Test public void itSetsEnableFlutterGPUFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu"); + "io.flutter.embedding.android.EnableFlutterGPU", null, "--enable-flutter-gpu"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableFlutterGPU", true, "--enable-flutter-gpu"); + "io.flutter.embedding.android.EnableFlutterGPU", null, "--enable-flutter-gpu"); } @Test @@ -861,11 +873,11 @@ public class FlutterLoaderTest { public void itSetsImpellerAntiAliasLinesFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines"); + "io.flutter.embedding.android.ImpellerAntialiasLines", null, "--impeller-antialias-lines"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ImpellerAntialiasLines", true, "--impeller-antialias-lines"); + "io.flutter.embedding.android.ImpellerAntialiasLines", null, "--impeller-antialias-lines"); } @Test @@ -905,7 +917,7 @@ public class FlutterLoaderTest { @Test public void itSetsUseTestFontsFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.UseTestFonts", true, "--use-test-fonts"); + "io.flutter.embedding.android.UseTestFonts", null, "--use-test-fonts"); } @Test @@ -920,19 +932,19 @@ public class FlutterLoaderTest { @Test public void itSetsEnableVulkanValidationFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanValidation", true, "--enable-vulkan-validation"); + "io.flutter.embedding.android.EnableVulkanValidation", null, "--enable-vulkan-validation"); } @Test public void itSetsEnableOpenGLGPUTracingFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableOpenGLGPUTracing", true, "--enable-opengl-gpu-tracing"); + "io.flutter.embedding.android.EnableOpenGLGPUTracing", null, "--enable-opengl-gpu-tracing"); } @Test public void itSetsEnableVulkanGPUTracingFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanGPUTracing", true, "--enable-vulkan-gpu-tracing"); + "io.flutter.embedding.android.EnableVulkanGPUTracing", null, "--enable-vulkan-gpu-tracing"); } @Test @@ -947,48 +959,154 @@ public class FlutterLoaderTest { @Test public void itSetsTraceStartupFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceStartup", true, "--trace-startup"); + "io.flutter.embedding.android.TraceStartup", null, "--trace-startup"); } @Test public void itSetsStartPausedFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", true, "--start-paused"); + testFlagFromMetadataPresent("io.flutter.embedding.android.StartPaused", null, "--start-paused"); } @Test public void itSetsDisableServiceAuthCodesFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.DisableServiceAuthCodes", - true, + null, "--disable-service-auth-codes"); } @Test public void itSetsEndlessTraceBufferFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.EndlessTraceBuffer", true, "--endless-trace-buffer"); + "io.flutter.embedding.android.EndlessTraceBuffer", null, "--endless-trace-buffer"); } @Test public void itSetsEnableDartProfilingFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); + "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableDartProfiling", true, "--enable-dart-profiling"); + "io.flutter.embedding.android.EnableDartProfiling", null, "--enable-dart-profiling"); } @Test public void itSetsProfileStartupFromMetadata() { + // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileStartup", true, "--profile-startup"); + "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); } + @Test + public void itSetsMergedPlatformUiThread() { + // Test debug mode. + testFlagFromMetadataPresent( + "io.flutter.embedding.android.MergedPlatformUIThread", + null, + "--merged-platform-ui-thread"); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode( + "io.flutter.embedding.android.MergedPlatformUIThread", + null, + "--merged-platform-ui-thread"); + } + + @Test + public void itSetsRouteFromMetadata() { + String testRoutePath = "some/route"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.Route", testRoutePath, "--route=" + testRoutePath); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.Route", testRoutePath, "--route=" + testRoutePath); + } + + @Test + public void itSetsAotVmServiceSharedLibraryFromMetadata() { + String testAotVmServiceSharedLibraryName = "some/route"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.AotVMServiceSharedLibraryName", testAotVmServiceSharedLibraryName , "--route=" + testAotVmServiceSharedLibraryName); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.AotVMServiceSharedLibraryName", testAotVmServiceSharedLibraryName, "--route=" + testAotVmServiceSharedLibraryName); + } + + @Test + public void itSetsCacheDirPathFromMetadata() { + String testCacheDirPath = "some/path"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.CacheDirPath", testCacheDirPath, "--cache-dir-path=" + testCacheDirPath ); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.CacheDirPath", testCacheDirPath, "--cache-dir-path=" + testCacheDirPath); +} + +@Test +public void itSetsICUDataFilePathFromMetadata() { + String icuDataFilePath = "some/path"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.ICUDataFilePath", icuDataFilePath, "--icu-data-file-path=" + icuDataFilePath); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUDataFilePath",icuDataFilePath, "--icu-data-file-path=" + icuDataFilePath); +} + +@Test +public void itSetsICUSymbolPrefixFromMetadata() { + String icuSymbolPrefix = "somePrefix"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.ICUSymbolPrefix", icuSymbolPrefix, "--icu-symbol-prefix=" + icuSymbolPrefix); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUSymbolPrefix",icuSymbolPrefix, "--icu-symbol-prefix=" + icuSymbolPrefix); +} + +@Test +public void itSetsICUNativeLibPathFromMetadata() { + String icuNativeLibPath = "some/path"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.ICUNativeLibPath", icuNativeLibPath, "--icu-native-lib-path=" + icuNativeLibPath); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUNativeLibPath",icuNativeLibPath, "--icu-native-lib-path=" + icuNativeLibPath) +} + +@Test +public void itSetsEnableServicePortFallback() { + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.EnableServicePortFallback", null, "--enable-service-port-fallback"); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.EnableServicePortFallback", null, "--enable-service-port-fallback"); +} + +@Test +public void itSetsDomainNetworkPolicyFromMetadata() { + String expectedDomainNetworkPolicy = "policy"; + + // Test debug mode. + testFlagFromMetadataPresent("io.flutter.embedding.android.DomainNetworkPolicy", expectedDomainNetworkPolicy, "--domain-network-policy=" + expectedDomainNetworkPolicy); + + // Test release mode. + testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.DomainNetworkPolicy", expectedDomainNetworkPolicy, "--domain-network-policy=" + expectedDomainNetworkPolicy); +} + + @Test public void itSetsTraceSkiaFromMetadata() { - testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", true, "--trace-skia"); + testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", null, "--trace-skia"); } @Test @@ -1003,7 +1121,7 @@ public class FlutterLoaderTest { @Test public void itSetsTraceSystraceFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.TraceSystrace", true, "--trace-systrace"); + "io.flutter.embedding.android.TraceSystrace", null, "--trace-systrace"); } @Test @@ -1018,29 +1136,80 @@ public class FlutterLoaderTest { @Test public void itSetsProfileMicrotasksFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.ProfileMicrotasks", true, "--profile-microtasks"); + "io.flutter.embedding.android.ProfileMicrotasks", null, "--profile-microtasks"); } @Test public void itSetsDumpSkpOnShaderCompilationFromMetadata() { testFlagFromMetadataPresent( "io.flutter.embedding.android.DumpSkpOnShaderCompilation", - true, + null, "--dump-skp-on-shader-compilation"); } @Test public void itSetsPurgePersistentCacheFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.PurgePersistentCache", true, "--purge-persistent-cache"); + "io.flutter.embedding.android.PurgePersistentCache", null, "--purge-persistent-cache"); } @Test public void itSetsVerboseLoggingFromMetadata() { testFlagFromMetadataPresent( - "io.flutter.embedding.android.VerboseLogging", true, "--verbose-logging"); + "io.flutter.embedding.android.VerboseLogging", null, "--verbose-logging"); } + @Test + public void itSetsEnableCheckedModeFromMetadata() { + + } + + @Test + public void itSetsDeviceVmServiceHostFromMetadata() { + + } + + @Test + public void itSetsSnapshotAssetPathFromMetadata() { + + } + + @Test + public void itSetsVmSnapshotInstructionsFromMetadata() { + + } + + @Test + public void itSetsIsolateSnapshotDataFromMetadata() { + + } + + @test + public void itSetsDisableVmServiceFromMetadata() { + } + + @Test + public void itSetsDisableVmServicePublicationFromMetadata() { + } + + @Test + public void itSetsDisableDartAssets() { + + } + + @Test + public void itSetsEnableSerialGCFromMetadata() { + + } + + @Test + public void itSetsDisallowInsecureConnections() { + + } + + + + @Test public void itSetsDartFlagsFromMetadata() { String expectedDartFlags = "--enable-asserts --enable-vm-service"; @@ -1050,6 +1219,11 @@ public class FlutterLoaderTest { "--dart-flags=" + expectedDartFlags); } + @Test + public void itDoesNotSetTestFlagFromMetadata() { + testFlagFromMetadataNotPresent("io.flutter.embedding.android.TestFlag", null, "--test-flag"); + } + @Test public void itDoesNotSetDisableMergedPlatformUIThreadFromMetadata() { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); @@ -1274,7 +1448,9 @@ public class FlutterLoaderTest { Bundle metadata = new Bundle(); // Place metadata key and value into the metadata bundle used to mock the manifest. - if (metadataValue instanceof Boolean) { + if (metadataValue == null) { + metadata.putString(metadataKey, null); + } else if (metadataValue instanceof Boolean) { metadata.putBoolean(metadataKey, (Boolean) metadataValue); } else if (metadataValue instanceof Integer) { metadata.putInt(metadataKey, (Integer) metadataValue); From 78fe7c7be7f7b17b7545352940b4aa7ab741fc6c Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Wed, 18 Feb 2026 12:02:55 -0800 Subject: [PATCH 16/17] undo adding unecessary flags --- .../embedding/engine/FlutterEngineFlags.java | 171 +--------------- .../engine/loader/FlutterLoader.java | 8 +- .../engine/loader/FlutterLoaderTest.java | 189 +----------------- 3 files changed, 15 insertions(+), 353 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index d75829baed5..2dfab47204c 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -150,15 +150,14 @@ public final class FlutterEngineFlags { *

Allowed in release to control which rendering backend is used in production. */ private static final Flag DISBALE_IMPELLER = - new Flag("--enable-impeller=", "EnableOrDisableImpeller", true); + new Flag("--enable-impeller=", "DisableImpeller", true); /** * Enables Impeller. * *

Allowed in release to control which rendering backend is used in production. */ - private static final Flag ENABLE_IMPELLER = - new Flag("--enable-impeller", "EnableImpeller", true); + private static final Flag ENABLE_IMPELLER = new Flag("--enable-impeller", "EnableImpeller", true); /** * Specifies the backend to use for Impeller rendering. * @@ -239,65 +238,6 @@ public final class FlutterEngineFlags { private static final Flag MERGED_PLATFORM_UI_THREAD = new Flag("--merged-platform-ui-thread", "MergedPlatformUIThread", true); - /** - * Start app with an specific route defined on the framework. - * - *

Allowed in release mode to allow starting with a specific route. - */ - private static final Flag ROUTE = new Flag("--route=", "Route", true); - - /** - * Specifies the name of the *.so containing AOT compiled Dart assets for launching the service - * isolate. - * - *

Allowed in release mode to support AOT compilation for service isolates in production. - */ - // TODO(camsim99): subject this to internal storage check - private static final Flag AOT_VM_SERVICE_SHARED_LIBRARY_NAME = - new Flag("--aot-vmservice-shared-library-name=", "AotVMServiceSharedLibraryName", true); - - /** - * Specifies the path to the cache directory. - * - *

Allowed in release mode to enable custom cache directory locations in production. - */ - private static final Flag CACHE_DIR_PATH = new Flag("--cache-dir-path=", "CacheDirPath", true); - - /** - * Specifies the path to the ICU data file. - * - *

Allowed in release mode to support custom ICU data files in production. - */ - private static final Flag ICU_DATA_FILE_PATH = - new Flag("--icu-data-file-path=", "ICUDataFilePath", true); - - /** Prefix for the symbols representing ICU data linked into the Flutter library. */ - private static final Flag ICU_SYMBOL_PREFIX = - new Flag("--icu-symbol-prefix=", "ICUSymbolPrefix", true); - - /** Path to the library file that exports the ICU data. */ - private static final Flag ICU_NATIVE_LIB_PATH = - new Flag("--icu-native-lib-path=", "ICUNativeLibPath", true); - - /** - * Allow the VM service to fallback to automatic port selection if binding to a specified port - * fails. - * - *

Allowed in release to allow the VM service to start even if the default port is blocked. - */ - private static final Flag ENABLE_SERVICE_PORT_FALLBACK = - new Flag("--enable-service-port-fallback", "EnableServicePortFallback", true); - - /** - * JSON encoded network policy per domain. This overrides the DisallowInsecureConnections switch. - * Embedder can specify whether to allow or disallow insecure connections at a domain level. - * - *

Allowed in release mode to enable fine-grained control over network security policies in - * production. - */ - private static final Flag DOMAIN_NETWORK_POLICY = - new Flag("--domain-network-policy=", "DomainNetworkPolicy", true); - // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ @@ -327,8 +267,7 @@ public final class FlutterEngineFlags { new Flag("--enable-vulkan-gpu-tracing", "EnableVulkanGPUTracing"); /** Fake flag used for integration testing of the Android embedding processing engine flags. */ - @VisibleForTesting - public static final Flag TEST_FLAG = new Flag("--test-flag", "TestFlag"); + @VisibleForTesting public static final Flag TEST_FLAG = new Flag("--test-flag", "TestFlag"); /** * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's @@ -386,86 +325,6 @@ public final class FlutterEngineFlags { /** Enables logging at all severity levels. */ private static final Flag VERBOSE_LOGGING = new Flag("--verbose-logging", "VerboseLogging"); - /** - * Enables Dart checked mode that enables certain runtime checks and assertions to help catch - * errors during development. - */ - private static final Flag ENABLE_CHECKED_MODE = - new Flag("--enable-checked-mode", "EnableCheckedMode"); - - /** The hostname/IP address on which the Dart VM Service should be served. */ - private static final Flag DEVICE_VM_SERVICE_HOST = - new Flag("--vm-service-host=", "DeviceVMServiceHost", true); - - /** Specifies the path to the directory containing the snapshot assets. */ - private static final Flag SNAPSHOT_ASSET_PATH = - new Flag("--snapshot-asset-path=", "SnapshotAssetPath"); - - /** Specifies the path to the VM snapshot instructions. */ - private static final Flag VM_SNAPSHOT_INSTRUCTIONS = - new Flag("--vm-snapshot-instr=", "VmSnapshotInstructions"); - - /** Specifies the path to the isolate snapshot instructions. */ - private static final Flag ISOLATE_SNAPSHOT_INSTRUCTIONS = - new Flag("--isolate-snapshot-instr=", "IsolateSnapshotInstructions"); - - /** Disable the Dart VM Service. */ - private static final Flag DISABLE_VM_SERVICE = - new Flag("--disable-vm-service", "DisableVMService"); - - /** Disable mDNS Dart VM Service publication. */ - private static final Flag DISABLE_VM_SERVICE_PUBLICATION = - new Flag("--disable-vm-service-publication", "DisableVMServicePublication"); - - /** Disable Dart asserts. */ - private static final Flag DISABLE_DART_ASSERTS = - new Flag("--disable-dart-asserts", "DisableDartAsserts"); - - /** Turns off all concurrent GC activities. */ - private static final Flag ENABLE_SERIAL_GC = new Flag("--enable-serial-gc", "EnableSerialGC"); - - /** - * By default, dart:io allows all socket connections. If this switch is set, all insecure - * connections are rejected. - */ - private static final Flag DISALLOW_INSECURE_CONNECTIONS = - new Flag("--disallow-insecure-connections", "DisallowInsecureConnections"); - - /** - * Uses separate threads for the platform, UI, GPU and IO task runners. By default, a single - * thread is used for all task runners. Only available in the flutter_tester. - */ - private static final Flag FORCE_MULTITHREADING = - new Flag("--force-multithreading", "ForceMultithreading"); - - /** - * Only cache the shader in SkSL instead of binary or GLSL. This should only be used during - * development phases. The generated SkSLs can later be used in the release build for shader - * precompilation at launch in order to eliminate the shader-compile jank. - */ - private static final Flag CACHE_SKSL = new Flag("--cache-sksl", "CacheSkSL"); - - /** - * Prevents usage of any non-test fonts unless they were explicitly Loaded via dart:ui font APIs. - * This option is only available on the desktop test shells. - */ - private static final Flag DISABLE_ASSET_FONTS = - new Flag("--disable-asset-fonts", "DisableAssetFonts"); - - /** - * Indicates whether the embedding started a prefetch of the default font manager before creating - * the engine. - */ - private static final Flag PREFETCHED_DEFAULT_FONT_MANAGER = - new Flag("--prefetched-default-font-manager", "PrefetchedDefaultFontManager"); - - /** In non-interactive mode, keep the shell running after the Dart script has completed. */ - private static final Flag RUN_FOREVER = new Flag("--run-forever", "RunForever"); - - /** Enable support for isolates that run on the platform thread. */ - private static final Flag ENABLE_PLATFORM_ISOLATES = - new Flag("--enable-platform-isolates", "EnablePlatformIsolates"); - /** Enable the SurfaceControl backed swapchain when supported. */ private static final Flag ENABLE_ANDROID_SURFACE_CONTROL = new Flag("--enable-surface-control", "EnableAndroidSurfaceControl"); @@ -526,34 +385,12 @@ public final class FlutterEngineFlags { PURGE_PERSISTENT_CACHE, VERBOSE_LOGGING, DART_FLAGS, - AOT_VM_SERVICE_SHARED_LIBRARY_NAME, - SNAPSHOT_ASSET_PATH, - VM_SNAPSHOT_INSTRUCTIONS, - ISOLATE_SNAPSHOT_INSTRUCTIONS, - CACHE_DIR_PATH, - ICU_DATA_FILE_PATH, - ICU_SYMBOL_PREFIX, - ICU_NATIVE_LIB_PATH, - DEVICE_VM_SERVICE_HOST, - DISABLE_VM_SERVICE, - DISABLE_VM_SERVICE_PUBLICATION, - ENABLE_SERVICE_PORT_FALLBACK, - DISABLE_DART_ASSERTS, - ENABLE_SERIAL_GC, - DISALLOW_INSECURE_CONNECTIONS, - DOMAIN_NETWORK_POLICY, - FORCE_MULTITHREADING, - CACHE_SKSL, - DISABLE_ASSET_FONTS, - PREFETCHED_DEFAULT_FONT_MANAGER, - RUN_FOREVER, - ENABLE_PLATFORM_ISOLATES, - ENABLE_ANDROID_SURFACE_CONTROL, MERGED_PLATFORM_UI_THREAD, DISABLE_MERGED_PLATFORM_UI_THREAD, DEPRECATED_AOT_SHARED_LIBRARY_NAME, DEPRECATED_FLUTTER_ASSETS_DIR, DISBALE_IMPELLER, + ENABLE_ANDROID_SURFACE_CONTROL, TEST_FLAG)); // Flags that have been turned off. 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 7b3446a13bc..9c2cde73243 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 @@ -365,8 +365,7 @@ public class FlutterLoader { // 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); + maybeAddAotSharedLibraryNameArg(applicationContext, aotSharedLibraryPath, shellArgs); continue; } @@ -387,7 +386,7 @@ public class FlutterLoader { } shellArgs.add(arg); - } + } } // Add any remaining engine flags provided by the command line. These settings will take @@ -397,13 +396,14 @@ public class FlutterLoader { for (String arg : args) { FlutterEngineFlags.Flag flag = FlutterEngineFlags.getFlagByCommandLineArgument(arg); if (flag == null) { + // TODO(camsim99): Reject unknown flags specified on the command line: + // https://github.com/flutter/flutter/issues/182557. // 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 FlutterEngineFlags."); - continue; } else if (flag.equals(FlutterEngineFlags.TEST_FLAG)) { Log.w( TAG, 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 e2fe34fbe1e..05dd7fc4fbb 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 @@ -803,7 +803,7 @@ public class FlutterLoaderTest { "io.flutter.embedding.android.DisableImpeller", true, "--enable-impeller=true"); } - @Test + @Test public void itSetsEnableImpellerFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( @@ -814,7 +814,6 @@ public class FlutterLoaderTest { "io.flutter.embedding.android.EnableImpeller", null, "--enable-impeller"); } - @Test public void itSetsImpellerBackendFromMetadata() { String expectedImpellerBackend = "Vulkan"; @@ -999,111 +998,21 @@ public class FlutterLoaderTest { "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); + testFlagFromMetadataPresentInReleaseMode( + "io.flutter.embedding.android.ProfileStartup", null, "--profile-startup"); } @Test public void itSetsMergedPlatformUiThread() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.MergedPlatformUIThread", - null, - "--merged-platform-ui-thread"); + "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread"); // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.MergedPlatformUIThread", - null, - "--merged-platform-ui-thread"); + testFlagFromMetadataPresentInReleaseMode( + "io.flutter.embedding.android.MergedPlatformUIThread", null, "--merged-platform-ui-thread"); } - @Test - public void itSetsRouteFromMetadata() { - String testRoutePath = "some/route"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.Route", testRoutePath, "--route=" + testRoutePath); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.Route", testRoutePath, "--route=" + testRoutePath); - } - - @Test - public void itSetsAotVmServiceSharedLibraryFromMetadata() { - String testAotVmServiceSharedLibraryName = "some/route"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.AotVMServiceSharedLibraryName", testAotVmServiceSharedLibraryName , "--route=" + testAotVmServiceSharedLibraryName); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.AotVMServiceSharedLibraryName", testAotVmServiceSharedLibraryName, "--route=" + testAotVmServiceSharedLibraryName); - } - - @Test - public void itSetsCacheDirPathFromMetadata() { - String testCacheDirPath = "some/path"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.CacheDirPath", testCacheDirPath, "--cache-dir-path=" + testCacheDirPath ); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.CacheDirPath", testCacheDirPath, "--cache-dir-path=" + testCacheDirPath); -} - -@Test -public void itSetsICUDataFilePathFromMetadata() { - String icuDataFilePath = "some/path"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.ICUDataFilePath", icuDataFilePath, "--icu-data-file-path=" + icuDataFilePath); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUDataFilePath",icuDataFilePath, "--icu-data-file-path=" + icuDataFilePath); -} - -@Test -public void itSetsICUSymbolPrefixFromMetadata() { - String icuSymbolPrefix = "somePrefix"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.ICUSymbolPrefix", icuSymbolPrefix, "--icu-symbol-prefix=" + icuSymbolPrefix); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUSymbolPrefix",icuSymbolPrefix, "--icu-symbol-prefix=" + icuSymbolPrefix); -} - -@Test -public void itSetsICUNativeLibPathFromMetadata() { - String icuNativeLibPath = "some/path"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.ICUNativeLibPath", icuNativeLibPath, "--icu-native-lib-path=" + icuNativeLibPath); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.ICUNativeLibPath",icuNativeLibPath, "--icu-native-lib-path=" + icuNativeLibPath) -} - -@Test -public void itSetsEnableServicePortFallback() { - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.EnableServicePortFallback", null, "--enable-service-port-fallback"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.EnableServicePortFallback", null, "--enable-service-port-fallback"); -} - -@Test -public void itSetsDomainNetworkPolicyFromMetadata() { - String expectedDomainNetworkPolicy = "policy"; - - // Test debug mode. - testFlagFromMetadataPresent("io.flutter.embedding.android.DomainNetworkPolicy", expectedDomainNetworkPolicy, "--domain-network-policy=" + expectedDomainNetworkPolicy); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode("io.flutter.embedding.android.DomainNetworkPolicy", expectedDomainNetworkPolicy, "--domain-network-policy=" + expectedDomainNetworkPolicy); -} - - @Test public void itSetsTraceSkiaFromMetadata() { testFlagFromMetadataPresent("io.flutter.embedding.android.TraceSkia", null, "--trace-skia"); @@ -1159,57 +1068,6 @@ public void itSetsDomainNetworkPolicyFromMetadata() { "io.flutter.embedding.android.VerboseLogging", null, "--verbose-logging"); } - @Test - public void itSetsEnableCheckedModeFromMetadata() { - - } - - @Test - public void itSetsDeviceVmServiceHostFromMetadata() { - - } - - @Test - public void itSetsSnapshotAssetPathFromMetadata() { - - } - - @Test - public void itSetsVmSnapshotInstructionsFromMetadata() { - - } - - @Test - public void itSetsIsolateSnapshotDataFromMetadata() { - - } - - @test - public void itSetsDisableVmServiceFromMetadata() { - } - - @Test - public void itSetsDisableVmServicePublicationFromMetadata() { - } - - @Test - public void itSetsDisableDartAssets() { - - } - - @Test - public void itSetsEnableSerialGCFromMetadata() { - - } - - @Test - public void itSetsDisallowInsecureConnections() { - - } - - - - @Test public void itSetsDartFlagsFromMetadata() { String expectedDartFlags = "--enable-asserts --enable-vm-service"; @@ -1254,39 +1112,6 @@ public void itSetsDomainNetworkPolicyFromMetadata() { "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); @@ -1449,7 +1274,7 @@ public void itSetsDomainNetworkPolicyFromMetadata() { // Place metadata key and value into the metadata bundle used to mock the manifest. if (metadataValue == null) { - metadata.putString(metadataKey, null); + metadata.putString(metadataKey, null); } else if (metadataValue instanceof Boolean) { metadata.putBoolean(metadataKey, (Boolean) metadataValue); } else if (metadataValue instanceof Integer) { From ff053d38bf785a36c316c7b8fecfe2feee621cd7 Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Wed, 18 Feb 2026 15:01:39 -0800 Subject: [PATCH 17/17] add back necessary flags --- docs/engine/Flutter-Android-Engine-Flags.md | 10 +- .../embedding/engine/FlutterEngineFlags.java | 122 +++++------------- .../embedding/engine/FlutterShellArgs.java | 2 +- .../engine/loader/FlutterLoader.java | 21 ++- .../engine/FlutterEngineFlagsTest.java | 15 --- .../engine/loader/FlutterLoaderTest.java | 66 +--------- 6 files changed, 52 insertions(+), 184 deletions(-) diff --git a/docs/engine/Flutter-Android-Engine-Flags.md b/docs/engine/Flutter-Android-Engine-Flags.md index b9341c47bac..b039c5ded53 100644 --- a/docs/engine/Flutter-Android-Engine-Flags.md +++ b/docs/engine/Flutter-Android-Engine-Flags.md @@ -67,15 +67,15 @@ the leading `--flag=` prefix). ### Examples -Set the `--old-gen-heap-size=` flag to 322 MB: +Set the `--trace-to-file=` flag to `some_file.txt`: ```xml + android:name="io.flutter.embedding.android.TraceToFile" + android:value="some_file.txt"/> ... @@ -121,7 +121,7 @@ class MyApp : Application() { // Initialize the Flutter engine with desired flags val args = arrayOf( "--trace-startup", - "--old-gen-heap-size=256", + "--trace-to-file=some_file.txt", "--enable-software-rendering" ) val flutterEngine = FlutterEngine(this, args) @@ -166,7 +166,7 @@ class MyApplication : FlutterApplication() { val args = arrayOf( "--trace-startup", - "--old-gen-heap-size=256", + "--trace-to-file=some_file.txt", "--enable-software-rendering" ) val flutterEngine = FlutterEngine(this, args) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java index 2dfab47204c..a78735efc59 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineFlags.java @@ -149,8 +149,8 @@ public final class FlutterEngineFlags { * *

Allowed in release to control which rendering backend is used in production. */ - private static final Flag DISBALE_IMPELLER = - new Flag("--enable-impeller=", "DisableImpeller", true); + private static final Flag TOGGLE_IMPELLER = + new Flag("--enable-impeller=", "ToggleImpeller", true); /** * Enables Impeller. @@ -166,54 +166,6 @@ public final class FlutterEngineFlags { private static final Flag IMPELLER_BACKEND = new Flag("--impeller-backend=", "ImpellerBackend", true); - /** - * Enables Android SurfaceControl for rendering. - * - *

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

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

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

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

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

Allowed in release to support different snapshot configurations. - */ - public static final Flag ISOLATE_SNAPSHOT_DATA = - new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true); - /** * Enables Dart profiling for use with DevTools. * @@ -230,6 +182,13 @@ public final class FlutterEngineFlags { */ private static final Flag PROFILE_STARTUP = new Flag("--profile-startup", "ProfileStartup", true); + /** + * Measures startup time and switches to an endless trace buffer. + * + *

Allowed in release mode to allow the startup performance to be profiled by DevTools. + */ + private static final Flag TRACE_STARTUP = new Flag("--trace-startup", "TraceStartup", true); + /** * Sets whether the UI thread and platform thread should be merged. * @@ -238,6 +197,22 @@ public final class FlutterEngineFlags { private static final Flag MERGED_PLATFORM_UI_THREAD = new Flag("--merged-platform-ui-thread", "MergedPlatformUIThread", true); + /** + * Specifies the path to the VM snapshot data file. + * + *

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

Allowed in release to support different snapshot configurations. + */ + public static final Flag ISOLATE_SNAPSHOT_DATA = + new Flag("--isolate-snapshot-data=", "IsolateSnapshotData", true); + // Manifest flags NOT allowed in release mode: /** Ensures deterministic Skia rendering by skipping CPU feature swaps. */ @@ -258,14 +233,6 @@ public final class FlutterEngineFlags { 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"); - /** Fake flag used for integration testing of the Android embedding processing engine flags. */ @VisibleForTesting public static final Flag TEST_FLAG = new Flag("--test-flag", "TestFlag"); @@ -282,10 +249,6 @@ public final class FlutterEngineFlags { */ 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"); // TODO(camsim99): allow in release? - /** Pauses Dart code execution at launch until a debugger is attached. */ private static final Flag START_PAUSED = new Flag("--start-paused", "StartPaused"); @@ -325,9 +288,8 @@ public final class FlutterEngineFlags { /** Enables logging at all severity levels. */ private static final Flag VERBOSE_LOGGING = new Flag("--verbose-logging", "VerboseLogging"); - /** Enable the SurfaceControl backed swapchain when supported. */ - private static final Flag ENABLE_ANDROID_SURFACE_CONTROL = - new Flag("--enable-surface-control", "EnableAndroidSurfaceControl"); + /** Only cache the shader in SkSL instead of binary or GLSL. */ + private static final Flag CACHE_SKSL = new Flag("--cache-sksl", "CacheSksl"); /** * Passes additional flags to the Dart VM. @@ -357,20 +319,9 @@ public final class FlutterEngineFlags { 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, @@ -382,15 +333,20 @@ public final class FlutterEngineFlags { TRACE_TO_FILE, PROFILE_MICROTASKS, DUMP_SKP_ON_SHADER_COMPILATION, - PURGE_PERSISTENT_CACHE, VERBOSE_LOGGING, DART_FLAGS, MERGED_PLATFORM_UI_THREAD, DISABLE_MERGED_PLATFORM_UI_THREAD, DEPRECATED_AOT_SHARED_LIBRARY_NAME, DEPRECATED_FLUTTER_ASSETS_DIR, - DISBALE_IMPELLER, - ENABLE_ANDROID_SURFACE_CONTROL, + TOGGLE_IMPELLER, + OLD_GEN_HEAP_SIZE, + VM_SNAPSHOT_DATA, + ISOLATE_SNAPSHOT_DATA, + CACHE_SKSL, + PURGE_PERSISTENT_CACHE, + TRACE_STARTUP, + LEAK_VM, TEST_FLAG)); // Flags that have been turned off. @@ -423,14 +379,6 @@ public final class FlutterEngineFlags { FLAG_BY_META_DATA_KEY = Collections.unmodifiableMap(metaMap); } - /** Looks up a {@link Flag} by its metadataKey. */ - // TODO(camsim99): determine if I even need this anymore - 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('='); 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 60754c1e1b8..26058798bfb 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -89,7 +89,7 @@ public class FlutterShellArgs { } int vmServicePort = intent.getIntExtra(ARG_KEY_VM_SERVICE_PORT, 0); if (vmServicePort > 0) { - args.add(ARG_VM_SERVICE_PORT + Integer.toString(vmServicePort)); + args.add(ARG_VM_SERVICE_PORT + vmServicePort); } if (intent.getBooleanExtra(ARG_KEY_DISABLE_SERVICE_AUTH_CODES, false)) { args.add(ARG_DISABLE_SERVICE_AUTH_CODES); 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 9c2cde73243..6ea5875ce67 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -34,7 +34,6 @@ 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 { @@ -306,8 +305,8 @@ public class FlutterLoader { .getApplicationInfo( applicationContext.getPackageName(), PackageManager.GET_META_DATA); Bundle applicationMetaData = applicationInfo.metaData; - final AtomicBoolean oldGenHeapSizeSet = new AtomicBoolean(false); - final AtomicBoolean isLeakVMSet = new AtomicBoolean(false); + boolean oldGenHeapSizeSet = false; + boolean isLeakVMSet = false; if (applicationMetaData != null) { for (FlutterEngineFlags.Flag flag : FlutterEngineFlags.ALL_FLAGS) { @@ -350,10 +349,10 @@ public class FlutterLoader { if (flag == FlutterEngineFlags.OLD_GEN_HEAP_SIZE) { // Mark if old gen heap size is set to track whether or not to set default // internally. - oldGenHeapSizeSet.set(true); // TODO(camsim99): check if I need atomic boolean anymore. + oldGenHeapSizeSet = true; } else if (flag == FlutterEngineFlags.LEAK_VM) { // Mark if leak VM is set to track whether or not to set default internally. - isLeakVMSet.set(true); + isLeakVMSet = true; } else if (flag == FlutterEngineFlags.ENABLE_SOFTWARE_RENDERING) { // Enabling software rendering impacts platform views, so save this value // so that the PlatformViewsController can be properly configured. @@ -398,12 +397,8 @@ public class FlutterLoader { if (flag == null) { // TODO(camsim99): Reject unknown flags specified on the command line: // https://github.com/flutter/flutter/issues/182557. - // 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 FlutterEngineFlags."); + shellArgs.add(arg); + continue; } else if (flag.equals(FlutterEngineFlags.TEST_FLAG)) { Log.w( TAG, @@ -480,7 +475,7 @@ public class FlutterLoader { shellArgs.add("--log-tag=" + settings.getLogTag()); } - if (!oldGenHeapSizeSet.get()) { + if (!oldGenHeapSizeSet) { // Default to half of total memory. ActivityManager activityManager = (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -502,7 +497,7 @@ public class FlutterLoader { shellArgs.add("--prefetched-default-font-manager"); - if (!isLeakVMSet.get()) { + if (!isLeakVMSet) { shellArgs.add(FlutterEngineFlags.LEAK_VM.commandLineArgument + "true"); } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java index 9888384e0a7..f5e111165ef 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineFlagsTest.java @@ -51,21 +51,6 @@ public class FlutterEngineFlagsTest { } } - @Test - public void getFlagByMetadataKey_returnsExpectedFlagWhenValidKeySpecified() { - FlutterEngineFlags.Flag flag = - FlutterEngineFlags.getFlagByMetadataKey( - "io.flutter.embedding.android.AOTSharedLibraryName"); - assertEquals(FlutterEngineFlags.AOT_SHARED_LIBRARY_NAME, flag); - } - - @Test - public void getFlagByMetadataKey_returnsNullWhenInvalidKeySpecified() { - FlutterEngineFlags.Flag flag = - FlutterEngineFlags.getFlagByMetadataKey("io.flutter.embedding.android.InvalidMetaDataKey"); - assertNull("Should return null for an invalid meta-data key", flag); - } - @Test public void getFlagByCommandLineArgument_returnsExpectedFlagWhenValidArgumentSpecified() { FlutterEngineFlags.Flag flag = 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 05dd7fc4fbb..66dad8c83ff 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -793,14 +793,14 @@ public class FlutterLoaderTest { } @Test - public void itSetsDisableImpellerFromMetadata() { + public void itSetsToggleImpellerFromMetadata() { // Test debug mode. testFlagFromMetadataPresent( - "io.flutter.embedding.android.DisableImpeller", true, "--enable-impeller=true"); + "io.flutter.embedding.android.ToggleImpeller", true, "--enable-impeller=true"); // Test release mode. testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.DisableImpeller", true, "--enable-impeller=true"); + "io.flutter.embedding.android.ToggleImpeller", true, "--enable-impeller=true"); } @Test @@ -831,54 +831,6 @@ public class FlutterLoaderTest { "--impeller-backend=" + expectedImpellerBackend); } - @Test - public void itSetsEnableSurfaceControlFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableSurfaceControl", null, "--enable-surface-control"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableSurfaceControl", null, "--enable-surface-control"); - } - - @Test - public void itSetsEnableFlutterGPUFromMetadata() { - // Test debug mode. - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableFlutterGPU", null, "--enable-flutter-gpu"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.EnableFlutterGPU", null, "--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", null, "--impeller-antialias-lines"); - - // Test release mode. - testFlagFromMetadataPresentInReleaseMode( - "io.flutter.embedding.android.ImpellerAntialiasLines", null, "--impeller-antialias-lines"); - } - @Test public void itSetsVmSnapshotDataFromMetadata() { String expectedVmSnapshotData = "vm_snapshot_data"; @@ -934,18 +886,6 @@ public class FlutterLoaderTest { "io.flutter.embedding.android.EnableVulkanValidation", null, "--enable-vulkan-validation"); } - @Test - public void itSetsEnableOpenGLGPUTracingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableOpenGLGPUTracing", null, "--enable-opengl-gpu-tracing"); - } - - @Test - public void itSetsEnableVulkanGPUTracingFromMetadata() { - testFlagFromMetadataPresent( - "io.flutter.embedding.android.EnableVulkanGPUTracing", null, "--enable-vulkan-gpu-tracing"); - } - @Test public void itSetsLeakVMFromMetadata() { // Test that LeakVM can be set via manifest.