From 3d83f89e019f958eedf8e8f91b4ae1c395616deb Mon Sep 17 00:00:00 2001 From: Camille Simon Date: Tue, 10 Feb 2026 11:06:24 -0800 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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