diff --git a/dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart b/dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart index d63084f6c74..309c4d98f93 100644 --- a/dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart +++ b/dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart @@ -14,13 +14,12 @@ Future main() async { try { await runProjectTest((FlutterProject flutterProject) async { section('APK contains plugin classes'); - await flutterProject.setMinSdkVersion(21); - await flutterProject.addPlugin('google_maps_flutter:^2.2.1'); + await flutterProject.addPlugin('google_maps_flutter:^2.12.1'); await inDirectory(flutterProject.rootPath, () async { await flutter( 'build', - options: ['apk', '--debug', '--target-platform=android-arm'], + options: ['apk', '--debug', '--target-platform=android-arm64'], ); final File apk = File( '${flutterProject.rootPath}/build/app/outputs/flutter-apk/app-debug.apk', @@ -30,8 +29,8 @@ Future main() async { } // https://github.com/flutter/flutter/issues/72185 await checkApkContainsMethods(apk, [ - 'io.flutter.plugins.googlemaps.GoogleMapController void onFlutterViewAttached(android.view.View)', - 'io.flutter.plugins.googlemaps.GoogleMapController void onFlutterViewDetached()', + 'io.flutter.plugins.googlemaps.GoogleMapController android.view.View getView()', + 'io.flutter.plugins.googlemaps.GoogleMapController void dispose()', ]); }); }); diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart index 0a707570bc1..1ce448316a5 100644 --- a/dev/devicelab/lib/framework/apk_utils.dart +++ b/dev/devicelab/lib/framework/apk_utils.dart @@ -300,18 +300,6 @@ android { }); } - Future setMinSdkVersion(int sdkVersion) async { - final File buildScript = appBuildFile; - - buildScript.openWrite(mode: FileMode.append).write(''' -android { - defaultConfig { - minSdk = $sdkVersion - } -} - '''); - } - Future getPackages() async { await inDirectory(Directory(rootPath), () async { await flutter('pub', options: ['get']); diff --git a/dev/integration_tests/deferred_components_test/android/component1/build.gradle b/dev/integration_tests/deferred_components_test/android/component1/build.gradle index 3ee14127d36..465af6bd40e 100644 --- a/dev/integration_tests/deferred_components_test/android/component1/build.gradle +++ b/dev/integration_tests/deferred_components_test/android/component1/build.gradle @@ -43,7 +43,7 @@ android { } defaultConfig { - minSdk = 21 + minSdk = 24 targetSdk = 35 versionCode = flutterVersionCode.toInteger() versionName = flutterVersionName diff --git a/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle b/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle index 9e53b71abfc..7b995c685a3 100644 --- a/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle +++ b/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle @@ -24,7 +24,7 @@ android { defaultConfig { applicationId = "io.flutter.addtoapp" - minSdk = 21 + minSdk = 24 targetSdk = 35 versionCode = 1 versionName = "1.0" diff --git a/dev/integration_tests/pure_android_host_apps/android_custom_host_app/SampleApp/build.gradle b/dev/integration_tests/pure_android_host_apps/android_custom_host_app/SampleApp/build.gradle index 56ec3b21e2b..08c6cac00d7 100644 --- a/dev/integration_tests/pure_android_host_apps/android_custom_host_app/SampleApp/build.gradle +++ b/dev/integration_tests/pure_android_host_apps/android_custom_host_app/SampleApp/build.gradle @@ -24,7 +24,7 @@ android { defaultConfig { applicationId "io.flutter.add2app" - minSdk = 21 + minSdk = 24 targetSdk = 35 versionCode 1 versionName "1.0" diff --git a/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/app/build.gradle b/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/app/build.gradle index f21e519eb06..a627e9d2bba 100644 --- a/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/app/build.gradle +++ b/dev/integration_tests/pure_android_host_apps/android_host_app_v2_embedding/app/build.gradle @@ -24,7 +24,7 @@ android { defaultConfig { applicationId "io.flutter.add2app" - minSdk = 21 + minSdk = 24 targetSdk = 35 versionCode 1 versionName "1.0" diff --git a/engine/src/flutter/shell/platform/android/build.gradle b/engine/src/flutter/shell/platform/android/build.gradle index 6959c340fbc..314e0f69c07 100644 --- a/engine/src/flutter/shell/platform/android/build.gradle +++ b/engine/src/flutter/shell/platform/android/build.gradle @@ -21,10 +21,10 @@ apply plugin: "com.android.library" android { namespace "io.flutter.embedding" - compileSdk 36 + compileSdk = 36 defaultConfig { - minSdkVersion 21 + minSdk = 24 } sourceSets { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/Build.java b/engine/src/flutter/shell/platform/android/io/flutter/Build.java index 519cbeff557..443b243c1f4 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/Build.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/Build.java @@ -10,7 +10,7 @@ import androidx.annotation.VisibleForTesting; public class Build { /** For use in place of the Android Build.VERSION_CODES class. */ public static class API_LEVELS { - @VisibleForTesting public static final int FLUTTER_MIN = 21; + @VisibleForTesting public static final int FLUTTER_MIN = 24; /** Android 5.0 (Lollipop) */ public static final int API_21 = 21; /** Android 5.1 (Lollipop MR1) */ diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 71a0fddd7c1..5fcb5476425 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -783,7 +783,8 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { result.setParent(rootAccessibilityView); } - if (semanticsNode.previousNodeId != -1 && Build.VERSION.SDK_INT >= API_LEVELS.API_22) { + if (semanticsNode.previousNodeId != -1) { + // Requires at least android api 22. result.setTraversalAfter(rootAccessibilityView, semanticsNode.previousNodeId); } 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 2a29153ddc8..beed05cddf8 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 @@ -38,6 +38,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.test.core.app.ActivityScenario; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.Build.API_LEVELS; import io.flutter.FlutterInjector; import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; @@ -601,7 +602,7 @@ public class FlutterActivityTest { } @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_28) + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_28) public void fullyDrawn_beforeAndroidQ() { Intent intent = FlutterActivityWithReportFullyDrawn.createDefaultIntent(ctx); ActivityController activityController = diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 645e0980d96..a193f024ee1 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -306,7 +306,7 @@ public class FlutterViewTest { // set to -1 values, so it is clear if the wrong algorithm is used. @Test @TargetApi(30) - @Config(sdk = 30) + @Config(sdk = API_LEVELS.API_30) public void reportSystemInsetWhenNotFullscreen() { // Without custom shadows, the default system ui visibility flags is 0. FlutterView flutterView = new FlutterView(ctx); @@ -348,7 +348,7 @@ public class FlutterViewTest { // set to -1 values, so it is clear if the wrong algorithm is used. @Test @TargetApi(34) - @Config(minSdk = 34) + @Config(minSdk = API_LEVELS.API_34) public void reportSystemInsetWhenNotFullscreenForSystemBar() { // Without custom shadows, the default system ui visibility flags is 0. FlutterView flutterView = new FlutterView(ctx); @@ -426,7 +426,7 @@ public class FlutterViewTest { // This test uses the pre-API 30 Algorithm for window insets. @Test @TargetApi(28) - @Config(sdk = 28) + @Config(sdk = API_LEVELS.API_28) public void reportSystemInsetWhenNotFullscreenLegacy() { // Without custom shadows, the default system ui visibility flags is 0. FlutterView flutterView = new FlutterView(ctx); @@ -459,7 +459,7 @@ public class FlutterViewTest { @SuppressWarnings("deprecation") // getSystemUiVisibility, getWindowSystemUiVisibility, required to test pre api 30 behavior. @Test - @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarRight() { FlutterView flutterView = spy(new FlutterView(ctx)); setExpectedDisplayRotation(Surface.ROTATION_90); @@ -521,7 +521,7 @@ public class FlutterViewTest { } @Test - @Config(minSdk = API_LEVELS.API_23, maxSdk = API_LEVELS.API_29, qualifiers = "land") + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "land") public void calculateShouldZeroSidesInLandscapeRotation270API23Plus() { FlutterView flutterView = spy(new FlutterView(ctx)); setExpectedDisplayRotation(Surface.ROTATION_270); @@ -531,7 +531,7 @@ public class FlutterViewTest { @SuppressWarnings("deprecation") // getSystemUiVisibility, getWindowSystemUiVisibility required to test pre api 30 behavior. @Test - @Config(minSdk = 23, maxSdk = 29, qualifiers = "land") + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_29, qualifiers = "land") public void systemInsetHandlesFullscreenNavbarLeft() { FlutterView flutterView = spy(new FlutterView(ctx)); setExpectedDisplayRotation(Surface.ROTATION_270); @@ -573,7 +573,7 @@ public class FlutterViewTest { // set to -1 values, so it is clear if the wrong algorithm is used. @Test @TargetApi(30) - @Config(sdk = 30, qualifiers = "land") + @Config(sdk = API_LEVELS.API_30, qualifiers = "land") public void systemInsetGetInsetsFullscreen() { FlutterView flutterView = spy(new FlutterView(ctx)); setExpectedDisplayRotation(Surface.ROTATION_270); @@ -611,7 +611,7 @@ public class FlutterViewTest { // This test uses the pre-API 30 Algorithm for window insets. @Test @TargetApi(28) - @Config(sdk = 28, qualifiers = "land") + @Config(sdk = API_LEVELS.API_28, qualifiers = "land") public void systemInsetGetInsetsFullscreenLegacy() { FlutterView flutterView = spy(new FlutterView(ctx)); setExpectedDisplayRotation(Surface.ROTATION_270); @@ -652,7 +652,7 @@ public class FlutterViewTest { // set to -1 values, so it is clear if the wrong algorithm is used. @Test @TargetApi(30) - @Config(sdk = 30, qualifiers = "land") + @Config(sdk = API_LEVELS.API_30, qualifiers = "land") public void systemInsetDisplayCutoutSimple() { FlutterView flutterView = spy(new FlutterView(ctx)); assertEquals(0, flutterView.getSystemUiVisibility()); @@ -700,7 +700,7 @@ public class FlutterViewTest { @SuppressWarnings("deprecation") @Test - @Config(minSdk = 28) + @Config(minSdk = API_LEVELS.API_28) public void onApplyWindowInsetsSetsDisplayCutouts() { // Use an Activity context so that FlutterView.onAttachedToWindow completes. try (ActivityScenario scenario = ActivityScenario.launch(Activity.class)) { @@ -1030,7 +1030,7 @@ public class FlutterViewTest { @Test @SuppressLint("PrivateApi") - @Config(sdk = Build.VERSION_CODES.P) + @Config(sdk = API_LEVELS.API_28) public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid28() throws Exception { FlutterView flutterView = new FlutterView(ctx); @@ -1041,7 +1041,7 @@ public class FlutterViewTest { } @Test - @Config(sdk = Build.VERSION_CODES.P) + @Config(sdk = API_LEVELS.API_28) @SuppressLint("PrivateApi") public void findViewByAccessibilityIdTraversal_returnsChildViewOnAndroid28() throws Exception { FlutterView flutterView = new FlutterView(ctx); @@ -1058,7 +1058,7 @@ public class FlutterViewTest { } @Test - @Config(sdk = Build.VERSION_CODES.Q) + @Config(sdk = API_LEVELS.API_29) @SuppressLint("PrivateApi") public void findViewByAccessibilityIdTraversal_returnsRootViewOnAndroid29() throws Exception { FlutterView flutterView = new FlutterView(ctx); @@ -1126,7 +1126,7 @@ public class FlutterViewTest { // set to -1 values, so it is clear if the wrong algorithm is used. @Test @TargetApi(30) - @Config(sdk = 30) + @Config(sdk = API_LEVELS.API_30) public void setPaddingTopToZeroForFullscreenMode() { FlutterView flutterView = new FlutterView(ctx); FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni)); @@ -1163,7 +1163,7 @@ public class FlutterViewTest { @Test @TargetApi(28) @Config( - sdk = 28, + sdk = API_LEVELS.API_28, shadows = { FlutterViewTest.ShadowFullscreenView.class, }) 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 46e9271b1e5..fba45f0f172 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 @@ -5,7 +5,6 @@ package io.flutter.embedding.engine.loader; import static android.os.Looper.getMainLooper; -import static io.flutter.Build.API_LEVELS; import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertNotNull; @@ -16,13 +15,10 @@ import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; -import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; import android.os.Bundle; @@ -38,7 +34,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.robolectric.annotation.Config; @RunWith(AndroidJUnit4.class) public class FlutterLoaderTest { @@ -404,18 +399,4 @@ public class FlutterLoaderTest { List arguments = Arrays.asList(shellArgsCaptor.getValue()); assertTrue(arguments.contains(shaderModeArg)); } - - @Test - @TargetApi(API_LEVELS.API_23) - @Config(sdk = API_LEVELS.API_23) - public void itReportsFpsToVsyncWaiterAndroidM() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); - - Context appContextSpy = spy(ctx); - - assertFalse(flutterLoader.initialized()); - flutterLoader.startInitialization(appContextSpy); - verify(appContextSpy, never()).getSystemService(anyString()); - } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/localization/LocalizationPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/localization/LocalizationPluginTest.java index a4dd64f0750..6e9ce5e1d4f 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/localization/LocalizationPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/localization/LocalizationPluginTest.java @@ -247,109 +247,6 @@ public class LocalizationPluginTest { assertEquals(result[2], ""); } - // Tests the legacy pre API 24 algorithm. - @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_23, qualifiers = "es-rMX") - public void computePlatformResolvedLocale_emptySupportedLocales_beforeAndroidN() { - FlutterJNI flutterJNI = new FlutterJNI(); - DartExecutor dartExecutor = mock(DartExecutor.class); - flutterJNI.setLocalizationPlugin( - new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor))); - String[] supportedLocales = new String[] {}; - String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales); - assertEquals(result.length, 0); - } - - @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_23, qualifiers = "") - public void computePlatformResolvedLocale_selectFirstLocaleWhenNoUserSetting_beforeAndroidN() { - FlutterJNI flutterJNI = new FlutterJNI(); - DartExecutor dartExecutor = mock(DartExecutor.class); - flutterJNI.setLocalizationPlugin( - new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor))); - String[] supportedLocales = - new String[] { - "fr", "FR", "", - "zh", "", "", - "en", "CA", "" - }; - String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales); - assertEquals(result.length, 3); - assertEquals(result[0], "fr"); - assertEquals(result[1], "FR"); - assertEquals(result[2], ""); - } - - @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_23, qualifiers = "fr-rCH") - public void computePlatformResolvedLocale_selectFirstLocaleWhenNoExactMatch_beforeAndroidN() { - FlutterJNI flutterJNI = new FlutterJNI(); - DartExecutor dartExecutor = mock(DartExecutor.class); - flutterJNI.setLocalizationPlugin( - new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor))); - // Example from https://developer.android.com/guide/topics/resources/multilingual-support#postN - String[] supportedLocales = - new String[] { - "en", "", "", - "de", "DE", "", - "es", "ES", "", - "fr", "FR", "", - "it", "IT", "" - }; - String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales); - assertEquals(result.length, 3); - assertEquals(result[0], "en"); - assertEquals(result[1], ""); - assertEquals(result[2], ""); - } - - @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_23, qualifiers = "it-rIT") - public void computePlatformResolvedLocale_selectExactMatchLocale_beforeAndroidN() { - FlutterJNI flutterJNI = new FlutterJNI(); - DartExecutor dartExecutor = mock(DartExecutor.class); - flutterJNI.setLocalizationPlugin( - new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor))); - - String[] supportedLocales = - new String[] { - "en", "", "", - "de", "DE", "", - "es", "ES", "", - "fr", "FR", "", - "it", "IT", "" - }; - String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales); - assertEquals(result.length, 3); - assertEquals(result[0], "it"); - assertEquals(result[1], "IT"); - assertEquals(result[2], ""); - } - - @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_23, qualifiers = "fr-rCH") - public void computePlatformResolvedLocale_selectOnlyLanguageLocale_beforeAndroidN() { - FlutterJNI flutterJNI = new FlutterJNI(); - DartExecutor dartExecutor = mock(DartExecutor.class); - flutterJNI.setLocalizationPlugin( - new LocalizationPlugin(ctx, new LocalizationChannel(dartExecutor))); - - String[] supportedLocales = - new String[] { - "en", "", "", - "de", "DE", "", - "es", "ES", "", - "fr", "FR", "", - "fr", "", "", - "it", "IT", "" - }; - String[] result = flutterJNI.computePlatformResolvedLocale(supportedLocales); - assertEquals(result.length, 3); - assertEquals(result[0], "fr"); - assertEquals(result[1], ""); - assertEquals(result[2], ""); - } - @Test public void localeFromString_languageOnly() { Locale locale = LocalizationPlugin.localeFromString("en"); diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java index 75a30450e72..dcb53d07a67 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java @@ -17,6 +17,7 @@ import android.view.Display; import android.view.inputmethod.InputMethodManager; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.Build.API_LEVELS; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; @@ -25,7 +26,7 @@ import org.robolectric.annotation.Config; @TargetApi(API_LEVELS.API_28) public class SingleViewPresentationTest { @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_30) + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_30) public void returnsOuterContextInputMethodManager() { // There's a bug in Android Q caused by the IMM being instanced per display. // https://github.com/flutter/flutter/issues/38375. We need the context returned by @@ -56,7 +57,7 @@ public class SingleViewPresentationTest { } @Test - @Config(minSdk = API_LEVELS.API_21, maxSdk = API_LEVELS.API_30) + @Config(minSdk = API_LEVELS.FLUTTER_MIN, maxSdk = API_LEVELS.API_30) public void returnsOuterContextInputMethodManager_createDisplayContext() { // The IMM should also persist across display contexts created from the base context. diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/util/PathUtilsTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/util/PathUtilsTest.java index 1594635f28f..6db0ace12b1 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/util/PathUtilsTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/util/PathUtilsTest.java @@ -65,9 +65,7 @@ public class PathUtilsTest { public void canGetCacheDir() { Context context = mock(Context.class); when(context.getCacheDir()).thenReturn(new File(APP_DATA_PATH + "/cache")); - if (Build.VERSION.SDK_INT >= API_LEVELS.API_21) { - when(context.getCodeCacheDir()).thenReturn(new File(APP_DATA_PATH + "/code_cache")); - } + when(context.getCodeCacheDir()).thenReturn(new File(APP_DATA_PATH + "/code_cache")); assertTrue(PathUtils.getCacheDirectory(context).startsWith(APP_DATA_PATH)); } @@ -75,14 +73,10 @@ public class PathUtilsTest { public void canOnlyGetCachePathWhenDiskFullAndCacheDirNotCreated() { Context context = mock(Context.class); when(context.getCacheDir()).thenReturn(null); - if (Build.VERSION.SDK_INT >= API_LEVELS.API_21) { - when(context.getCodeCacheDir()).thenReturn(null); - } - if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) { - when(context.getDataDir()).thenReturn(new File(APP_DATA_PATH)); - } else { - when(context.getApplicationInfo().dataDir).thenReturn(APP_DATA_PATH); - } + // Requires at least api 21. + when(context.getCodeCacheDir()).thenReturn(null); + // Requires at least api 24. + when(context.getDataDir()).thenReturn(new File(APP_DATA_PATH)); assertEquals(PathUtils.getCacheDirectory(context), APP_DATA_PATH + "/cache"); } } diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 552f217fffe..8751bef6b9b 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -697,8 +697,8 @@ public class AccessibilityBridgeTest { verify(mockRootView, times(1)).setAccessibilityPaneTitle(eq("new_node2")); } - @Config(sdk = API_LEVELS.API_21) @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itCanPerformSetText() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); @@ -736,8 +736,8 @@ public class AccessibilityBridgeTest { .dispatchSemanticsAction(1, AccessibilityBridge.Action.SET_TEXT, expectedText); } - @Config(sdk = API_LEVELS.API_21) @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itCanPredictSetText() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); @@ -775,8 +775,8 @@ public class AccessibilityBridgeTest { assertEquals(nodeInfo.getText().toString(), expectedText); } - @Config(sdk = API_LEVELS.API_21) @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itBuildsAttributedString() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); @@ -841,8 +841,8 @@ public class AccessibilityBridgeTest { assertEquals(actual.getSpanEnd(spellOutSpan), 9); } - @Config(sdk = API_LEVELS.API_21) @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itSetsTextCorrectly() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); @@ -976,8 +976,8 @@ public class AccessibilityBridgeTest { assertEquals(actual.toString(), root.identifier); } - @Config(sdk = API_LEVELS.API_21) @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itCanCreateAccessibilityNodeInfoWithSetText() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); @@ -1605,7 +1605,7 @@ public class AccessibilityBridgeTest { accessibilityBridge.performAction( 1, AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, bundle); AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1); - // The seletction should be at the beginning of the third line. + // The selection should be at the beginning of the third line. assertEquals(nodeInfo.getTextSelectionStart(), 21); assertEquals(nodeInfo.getTextSelectionEnd(), 21); @@ -1617,7 +1617,7 @@ public class AccessibilityBridgeTest { accessibilityBridge.performAction( 1, AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, bundle); nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(1); - // The seletction should be at the beginning of the second line. + // The selection should be at the beginning of the second line. assertEquals(nodeInfo.getTextSelectionStart(), 11); assertEquals(nodeInfo.getTextSelectionEnd(), 11); } diff --git a/engine/src/flutter/shell/platform/android/test_runner/build.gradle b/engine/src/flutter/shell/platform/android/test_runner/build.gradle index de525bdf39a..e6e26e8bceb 100644 --- a/engine/src/flutter/shell/platform/android/test_runner/build.gradle +++ b/engine/src/flutter/shell/platform/android/test_runner/build.gradle @@ -33,10 +33,10 @@ println "==========================================" android { namespace 'io.flutter.app.test' - compileSdk 36 + compileSdk = 36 defaultConfig { - minSdkVersion 21 + minSdk = 24 } compileOptions { diff --git a/packages/flutter_tools/.gitignore b/packages/flutter_tools/.gitignore index 14759dbad5d..05a7e23003b 100644 --- a/packages/flutter_tools/.gitignore +++ b/packages/flutter_tools/.gitignore @@ -4,3 +4,6 @@ !ide_templates/intellij/.idea !templates/**/.idea + +# Do not need gradle cache of flutter gradle plugin code checked in. +gradle/bin/** diff --git a/packages/flutter_tools/gradle/README.md b/packages/flutter_tools/gradle/README.md index acad1c7bdd0..9c5a0c1b48e 100644 --- a/packages/flutter_tools/gradle/README.md +++ b/packages/flutter_tools/gradle/README.md @@ -27,9 +27,15 @@ Tests can be run in Android Studio, or directly with Gradle: `./gradlew test` the engines `third_party` directory at `/engine/src/flutter/third_party/gradle/bin/gradle`). -Alternatively, you can run all the tests in one file by passing in the fully qualified class name, -e.g. `./gradlew test --tests com.flutter.gradle.BaseApplicationNameHandlerTest`, or one test in -one file by passing in the fully qualified class name followed by the method name, +If you can not run the test task try running `./gradlew tasks`. If that does not work then there is +a configuration error. The most common one is using the wrong version of java. Java can be +overridden by setting the `JAVA_HOME` environment variable. +This example sets the java version to 17 downloaded with brew and then runs the tests: +`JAVA_HOME=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home/ ./gradlew test` + +You can run all the tests in one file by passing in the fully qualified class name, +e.g. `./gradlew test --tests com.flutter.gradle.BaseApplicationNameHandlerTest`, or one test in +one file by passing in the fully qualified class name followed by the method name, e.g `./gradlew test --tests "com.flutter.gradle.BaseApplicationNameHandlerTest.setBaseName respects Flutter tool property"`. Sometimes changing a test name and then running it will cause an IDE error. To get Android Studio back diff --git a/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt b/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt index b177c058394..3c92813062f 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt @@ -105,11 +105,12 @@ object DependencyVersionChecker { @VisibleForTesting internal val errorKGPVersion: Version = Version(1, 7, 0) // If this value is changed, then make sure to change the documentation on https://docs.flutter.dev/reference/supported-platforms + // Non inclusive. @VisibleForTesting - internal val warnMinSdkVersion: Int = 21 + internal val warnMinSdkVersion: Int = 24 @VisibleForTesting - internal val errorMinSdkVersion: Int = 1 + internal val errorMinSdkVersion: Int = 23 /** * Checks if the project's Android build time dependencies are each within the respective diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt index f1eb5ab9e3f..fe2e2679a0d 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt @@ -23,7 +23,7 @@ open class FlutterExtension { val compileSdkVersion: Int = 36 /** Sets the minSdkVersion used by default in Flutter app projects. */ - val minSdkVersion: Int = 21 + val minSdkVersion: Int = 24 /** * Sets the targetSdkVersion used by default in Flutter app projects. diff --git a/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt index 072ebed91a9..0279beb236f 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt @@ -228,7 +228,7 @@ class DependencyVersionCheckerTest { @Test fun `min SDK version in warn range results in warning logs`() { - val exampleWarnSDKVersion = 19 + val exampleWarnSDKVersion = 23 val flavorName1 = "flavor1" val flavorName2 = "flavor2" val mockProject = @@ -340,8 +340,8 @@ class DependencyVersionCheckerTest { assertEquals( dependencyValidationException.message, - "Error: Your project's minimum Android SDK (flavor='flavor') version (0) is lower than " + - "Flutter's minimum supported version of 1. Please upgrade your minimum Android SDK " + + "Error: Your project's minimum Android SDK (flavor='flavor') version ($version) is lower than " + + "Flutter's minimum supported version of $errorMinSdkVersion. Please upgrade your minimum Android SDK " + "(flavor='flavor') version. \n" + "Alternatively, use the flag \"--android-skip-build-dependency-validation\" to " + "bypass this check.\n" + @@ -358,7 +358,7 @@ class DependencyVersionCheckerTest { val mockExtraPropertiesExtension = mockk() val projectDir = "projectDir" val flavor = "flavor" - val version = 20 + val version = 23 every { mockExtraPropertiesExtension.set(any(), any()) } returns Unit every { mockLogger.error(any()) } returns Unit @@ -377,8 +377,8 @@ class DependencyVersionCheckerTest { assertEquals( warningMessageSlot.captured, "Warning: Flutter support for your project's minimum Android SDK (flavor='flavor') " + - "version (20) will soon be dropped. Please upgrade your minimum Android SDK " + - "(flavor='flavor') version to a version of at least 21 soon.\n" + + "version ($version) will soon be dropped. Please upgrade your minimum Android SDK " + + "(flavor='flavor') version to a version of at least $warnMinSdkVersion soon.\n" + "Alternatively, use the flag \"--android-skip-build-dependency-validation\" to " + "bypass this check.\n" + "\n" + @@ -475,6 +475,9 @@ private object MockProjectFactory { val variant = mockk() every { variant.name } returns it.flavor every { variant.minSdk } returns mockk { every { apiLevel } returns it.version } + // Gradle prints a warning about this api being removed in gradle 9 but there is + // no way to suppress individual warnings. We can remove this line when our tests + // use gradle 9 or higher. every { variant.minSdkVersion } returns mockk { every { apiLevel } returns it.version } onVariantsFnSlot.captured.invoke(variant) } diff --git a/packages/flutter_tools/lib/src/android/README.md b/packages/flutter_tools/lib/src/android/README.md index a6845f4261b..2687e79f8ae 100644 --- a/packages/flutter_tools/lib/src/android/README.md +++ b/packages/flutter_tools/lib/src/android/README.md @@ -19,7 +19,7 @@ All of the Android SDK/NDK versions noted in `gradle_utils.dart` (`compileSdkVersion`, `minSdkVersion`, `targetSdkVersion`, `ndkVersion`) versions should match the values in Flutter Gradle Plugin (`FlutterExtension`), so updating any of these versions also requires an update in -[flutter.groovy](../../../gradle/src/main/groovy/flutter.groovy). +[FlutterExtension](../../../gradle/src/main/kotlin/FlutterExtention.kt). When updating the Android `compileSdkVersion`, `minSdkVersion`, or `targetSdkVersion`, make sure that: @@ -30,9 +30,11 @@ SDK versions are updated (you should see these fail if you do not fix them preemptively). Also, make sure to also update to the same version in the following places: -- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/groovy/flutter.groovy`. -- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts`. +- The versions for error/warn in `packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt`. - The version in the dependencies block in `packages/flutter_tools/gradle/build.gradle.kts`. +- The flutter min version in `engine/src/flutter/shell/platform/android/io/flutter/Build.java` +- The versions used when engine testing in `engine/src/flutter/shell/platform/android/test_runner/build.gradle` +- The versions used when working in engine/shell in `engine/src/flutter/shell/platform/android/build.gradle` #### Gradle When updating the Gradle version used in project templates diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 6adbcdd820f..860efa66aff 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -51,7 +51,7 @@ const String templateKotlinGradlePluginVersion = '2.1.0'; // // Please see the README before changing any of these values. const String compileSdkVersion = '36'; -const String minSdkVersion = '21'; +const String minSdkVersion = '24'; const String targetSdkVersion = '36'; const String ndkVersion = '27.0.12077973'; @@ -158,9 +158,10 @@ final RegExp gradleOrgVersionMatch = RegExp( ); // This matches uncommented minSdkVersion lines in the module-level build.gradle -// file which have minSdkVersion 16,17, 18, 19, or 20. +// file which have minSdkVersion 16, 17, 18, 19, 20, 21, 22, 23 set with space sytax, +// equals syntax and when using minSdk or minSdkVersion. final RegExp tooOldMinSdkVersionMatch = RegExp( - r'(?<=^\s*)minSdkVersion (1[6789]|20)(?=\s*(?://|$))', + r'(?<=^\s*)minSdk(Version)?\s=?\s?(1[6789]|2[0123])(?=\s*(?://|$))', multiLine: true, ); diff --git a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart index d9b5c9f9587..98ddb0f5c54 100644 --- a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart @@ -349,7 +349,7 @@ tasks.register("clean", Delete) { }); }); - group('migrate min sdk versions less than 21 to flutter.minSdkVersion ' + group('migrate min sdk versions less than 24 to flutter.minSdkVersion ' 'when in a FlutterProject that is an app', () { late MemoryFileSystem memoryFileSystem; late BufferLogger bufferLogger; @@ -392,13 +392,43 @@ tasks.register("clean", Delete) { ); }); - testWithoutContext('do nothing when >=api 21', () async { + testWithoutContext('replace when api 21', () async { const String minSdkVersion21 = 'minSdkVersion 21'; project.appGradleFile.writeAsStringSync(sampleModuleGradleBuildFile(minSdkVersion21)); await migration.migrate(); expect( project.appGradleFile.readAsStringSync(), - sampleModuleGradleBuildFile(minSdkVersion21), + sampleModuleGradleBuildFile(replacementMinSdkText), + ); + }); + + testWithoutContext('replace when api 22', () async { + const String minSdkVersion20 = 'minSdkVersion = 22'; + project.appGradleFile.writeAsStringSync(sampleModuleGradleBuildFile(minSdkVersion20)); + await migration.migrate(); + expect( + project.appGradleFile.readAsStringSync(), + sampleModuleGradleBuildFile(replacementMinSdkText), + ); + }); + + testWithoutContext('replace when api 23', () async { + const String minSdkVersion20 = 'minSdk = 23'; + project.appGradleFile.writeAsStringSync(sampleModuleGradleBuildFile(minSdkVersion20)); + await migration.migrate(); + expect( + project.appGradleFile.readAsStringSync(), + sampleModuleGradleBuildFile(replacementMinSdkText), + ); + }); + + testWithoutContext('do nothing when >=api 24', () async { + const String minSdkVersion24 = 'minSdkVersion 24'; + project.appGradleFile.writeAsStringSync(sampleModuleGradleBuildFile(minSdkVersion24)); + await migration.migrate(); + expect( + project.appGradleFile.readAsStringSync(), + sampleModuleGradleBuildFile(minSdkVersion24), ); }); @@ -415,7 +445,7 @@ tasks.register("clean", Delete) { testWithoutContext('avoid rewriting comments', () async { const String code = '// minSdkVersion 19 // old default\n' - ' minSdkVersion 23 // new version'; + ' minSdkVersion 24 // new version'; project.appGradleFile.writeAsStringSync(sampleModuleGradleBuildFile(code)); await migration.migrate(); expect(project.appGradleFile.readAsStringSync(), sampleModuleGradleBuildFile(code)); @@ -447,7 +477,7 @@ tasks.register("clean", Delete) { ); }); - testWithoutContext('do nothing when minSdkVersion is set ' + testWithoutContext('migrate when minSdkVersion is set ' 'using = syntax', () async { const String equalsSyntaxMinSdkVersion19 = 'minSdkVersion = 19'; project.appGradleFile.writeAsStringSync( @@ -456,7 +486,7 @@ tasks.register("clean", Delete) { await migration.migrate(); expect( project.appGradleFile.readAsStringSync(), - sampleModuleGradleBuildFile(equalsSyntaxMinSdkVersion19), + sampleModuleGradleBuildFile(replacementMinSdkText), ); }); }); diff --git a/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart index 6faab6e4bb4..1d7ee14d8dc 100644 --- a/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart +++ b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart @@ -138,7 +138,7 @@ class DeferredComponentModule { } defaultConfig { - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName