diff --git a/engine/src/flutter/shell/platform/android/flutter_main.cc b/engine/src/flutter/shell/platform/android/flutter_main.cc index 7f5e50d3ae..7f6d807550 100644 --- a/engine/src/flutter/shell/platform/android/flutter_main.cc +++ b/engine/src/flutter/shell/platform/android/flutter_main.cc @@ -95,7 +95,8 @@ void FlutterMain::Init(JNIEnv* env, jstring kernelPath, jstring appStoragePath, jstring engineCachesPath, - jlong initTimeMillis) { + jlong initTimeMillis, + jint api_level) { std::vector args; args.push_back("flutter"); for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) { @@ -118,8 +119,12 @@ void FlutterMain::Init(JNIEnv* env, "Dart DevTools."); } } + // The API level must be provided from java, as the NDK function + // android_get_device_api_level() is only available on API 24 and greater, and + // Flutter still supports 21, 22, and 23. - AndroidRenderingAPI android_rendering_api = SelectedRenderingAPI(settings); + AndroidRenderingAPI android_rendering_api = + SelectedRenderingAPI(settings, api_level); switch (android_rendering_api) { case AndroidRenderingAPI::kSoftware: case AndroidRenderingAPI::kSkiaOpenGLES: @@ -233,7 +238,7 @@ bool FlutterMain::Register(JNIEnv* env) { { .name = "nativeInit", .signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/" - "lang/String;Ljava/lang/String;Ljava/lang/String;J)V", + "lang/String;Ljava/lang/String;Ljava/lang/String;JI)V", .fnPtr = reinterpret_cast(&Init), }, { @@ -271,7 +276,8 @@ bool FlutterMain::IsKnownBadSOC(std::string_view hardware) { // static AndroidRenderingAPI FlutterMain::SelectedRenderingAPI( - const flutter::Settings& settings) { + const flutter::Settings& settings, + int api_level) { if (settings.enable_software_rendering) { FML_CHECK(!settings.enable_impeller) << "Impeller does not support software rendering. Either disable " @@ -301,7 +307,6 @@ AndroidRenderingAPI FlutterMain::SelectedRenderingAPI( // Even if this check returns true, Impeller may determine it cannot use // Vulkan for some other reason, such as a missing required extension or // feature. - int api_level = android_get_device_api_level(); if (api_level < kMinimumAndroidApiLevelForVulkan) { return kVulkanUnsupportedFallback; } diff --git a/engine/src/flutter/shell/platform/android/flutter_main.h b/engine/src/flutter/shell/platform/android/flutter_main.h index f6e27bcdc9..2354caa8c9 100644 --- a/engine/src/flutter/shell/platform/android/flutter_main.h +++ b/engine/src/flutter/shell/platform/android/flutter_main.h @@ -26,7 +26,8 @@ class FlutterMain { flutter::AndroidRenderingAPI GetAndroidRenderingAPI(); static AndroidRenderingAPI SelectedRenderingAPI( - const flutter::Settings& settings); + const flutter::Settings& settings, + int api_level); static bool IsDeviceEmulator(std::string_view product_model); @@ -47,7 +48,8 @@ class FlutterMain { jstring kernelPath, jstring appStoragePath, jstring engineCachesPath, - jlong initTimeMillis); + jlong initTimeMillis, + jint api_level); void SetupDartVMServiceUriCallback(JNIEnv* env); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index d67f5daa65..be6ac39e68 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -180,7 +180,8 @@ public class FlutterJNI { @Nullable String bundlePath, @NonNull String appStoragePath, @NonNull String engineCachesPath, - long initTimeMillis); + long initTimeMillis, + int apiLevel); /** * Perform one time initialization of the Dart VM and Flutter engine. @@ -193,6 +194,7 @@ public class FlutterJNI { * @param appStoragePath The path to the application data directory. * @param engineCachesPath The path to the application cache directory. * @param initTimeMillis The time, in milliseconds, taken for initialization. + * @param apiLevel The current Android API level. */ public void init( @NonNull Context context, @@ -200,13 +202,14 @@ public class FlutterJNI { @Nullable String bundlePath, @NonNull String appStoragePath, @NonNull String engineCachesPath, - long initTimeMillis) { + long initTimeMillis, + int apiLevel) { if (FlutterJNI.initCalled) { Log.w(TAG, "FlutterJNI.init called more than once"); } FlutterJNI.nativeInit( - context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis, apiLevel); FlutterJNI.initCalled = true; } @@ -247,7 +250,7 @@ public class FlutterJNI { * VM Service URI for the VM instance. * *

Its value is set by the native engine once {@link #init(Context, String[], String, String, - * String, long)} is run. + * String, long, int)} is run. */ @Nullable public static String getVMServiceUri() { @@ -258,7 +261,7 @@ public class FlutterJNI { * VM Service URI for the VM instance. * *

Its value is set by the native engine once {@link #init(Context, String[], String, String, - * String, long)} is run. + * String, long, int)} is run. * * @deprecated replaced by {@link #getVMServiceUri()}. */ @@ -448,7 +451,8 @@ public class FlutterJNI { * #attachToNative()}. * *

Static methods that should be only called once such as {@link #init(Context, String[], - * String, String, String, long)} shouldn't be called again on the spawned FlutterJNI instance. + * String, String, String, long, int)} shouldn't be called again on the spawned FlutterJNI + * instance. */ @UiThread @NonNull 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 96654be4fe..b269697e4a 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 @@ -390,7 +390,8 @@ public class FlutterLoader { kernelPath, result.appStoragePath, result.engineCachesPath, - initTimeMillis); + initTimeMillis, + Integer.valueOf(android.os.Build.VERSION.SDK_INT)); initialized = true; } catch (Exception e) { diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_unittests.cc b/engine/src/flutter/shell/platform/android/platform_view_android_unittests.cc index 646337716e..94124428df 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_unittests.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_unittests.cc @@ -21,15 +21,10 @@ TEST(AndroidPlatformView, DISABLED_SelectsVulkanBasedOnApiLevel) { settings.enable_software_rendering = false; settings.enable_impeller = true; - int api_level = android_get_device_api_level(); - EXPECT_GT(api_level, 0); - if (api_level >= 29) { - EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings), - AndroidRenderingAPI::kImpellerVulkan); - } else { - EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings), - AndroidRenderingAPI::kImpellerOpenGLES); - } + EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings, 29), + AndroidRenderingAPI::kImpellerVulkan); + EXPECT_EQ(FlutterMain::SelectedRenderingAPI(settings, 24), + AndroidRenderingAPI::kImpellerOpenGLES); } TEST(AndroidPlatformView, SoftwareRenderingNotSupportedWithImpeller) { @@ -37,7 +32,7 @@ TEST(AndroidPlatformView, SoftwareRenderingNotSupportedWithImpeller) { settings.enable_software_rendering = true; settings.enable_impeller = true; - ASSERT_DEATH(FlutterMain::SelectedRenderingAPI(settings), ""); + ASSERT_DEATH(FlutterMain::SelectedRenderingAPI(settings, 29), ""); } TEST(AndroidPlatformView, FallsBackToGLESonEmulator) { 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 d8697b7e40..373fca5992 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 @@ -9,6 +9,7 @@ import static io.flutter.Build.API_LEVELS; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; @@ -99,7 +100,14 @@ public class FlutterLoaderTest { final String oldGenHeapArg = "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(oldGenHeapArg)); } @@ -122,7 +130,14 @@ public class FlutterLoaderTest { "--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(resourceCacheMaxBytesThresholdArg)); } @@ -140,7 +155,14 @@ public class FlutterLoaderTest { final String leakVMArg = "--leak-vm=true"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(leakVMArg)); } @@ -162,7 +184,14 @@ public class FlutterLoaderTest { final String leakVMArg = "--leak-vm=false"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(leakVMArg)); } @@ -191,7 +220,14 @@ public class FlutterLoaderTest { final String enableImpellerArg = "--enable-impeller"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertFalse(arguments.contains(enableImpellerArg)); } @@ -209,7 +245,14 @@ public class FlutterLoaderTest { final String enableVulkanValidationArg = "--enable-vulkan-validation"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertFalse(arguments.contains(enableVulkanValidationArg)); } @@ -231,7 +274,14 @@ public class FlutterLoaderTest { final String enableImpellerArg = "--enable-impeller=true"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(enableImpellerArg)); } @@ -253,7 +303,14 @@ public class FlutterLoaderTest { final String disabledControlArg = "--enable-surface-control"; ArgumentCaptor shellArgsCaptor = ArgumentCaptor.forClass(String[].class); verify(mockFlutterJNI, times(1)) - .init(eq(ctx), shellArgsCaptor.capture(), anyString(), anyString(), anyString(), anyLong()); + .init( + eq(ctx), + shellArgsCaptor.capture(), + anyString(), + anyString(), + anyString(), + anyLong(), + anyInt()); List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(disabledControlArg)); }