diff --git a/.ci.yaml b/.ci.yaml index 0d1ac640cf..e87155c9f7 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1128,6 +1128,16 @@ targets: ["devicelab", "hostonly", "linux"] task_name: linux_desktop_impeller + - name: Linux_android_emu android_display_cutout + recipe: devicelab/devicelab_drone + timeout: 60 + bringup: true + properties: + tags: > + ["devicelab", "linux"] + task_name: android_display_cutout + presubmit_max_attempts: "2" + - name: Linux android_release_builds_exclude_dev_dependencies_test recipe: devicelab/devicelab_drone timeout: 60 diff --git a/TESTOWNERS b/TESTOWNERS index aa93e814ba..3df8554c93 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -301,6 +301,7 @@ /dev/devicelab/bin/tasks/windows_desktop_impeller.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/mac_desktop_impeller.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/linux_desktop_impeller.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/android_display_cutout.dart @reidbaker @flutter/android ## Host only framework tests # Linux docs_deploy_beta diff --git a/dev/devicelab/bin/tasks/android_display_cutout.dart b/dev/devicelab/bin/tasks/android_display_cutout.dart new file mode 100644 index 0000000000..4003ba156a --- /dev/null +++ b/dev/devicelab/bin/tasks/android_display_cutout.dart @@ -0,0 +1,12 @@ +// Copyright 2014 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. + +import 'package:flutter_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createDisplayCutoutTest()); +} diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart index d14a9ed762..184a00b286 100644 --- a/dev/devicelab/lib/tasks/integration_tests.dart +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -139,6 +139,44 @@ TaskFunction createSolidColorTest({required bool enableImpeller}) { ).call; } +// Can run on emulator or physical android device. +// Device must have developer settings enabled. +// Device must be android api 30 or higher. +TaskFunction createDisplayCutoutTest() { + return IntegrationTest( + '${flutterDirectory.path}/dev/integration_tests/display_cutout_rotation/', + 'integration_test/display_cutout_test.dart', + setup: (Device device) async { + if (device is! AndroidDevice) { + // Only android devices support this cutoutTest. + throw TaskResult.failure('This test should only target android'); + } + // Test requires developer settings added in 28 and behavior added in 30. + final String sdkResult = await device.shellEval('getprop', ['ro.build.version.sdk']); + if (sdkResult.startsWith('2') || sdkResult.startsWith('1') || sdkResult.length == 1) { + throw TaskResult.failure('This test should only target android 30+.'); + } + print('Adding Synthetic notch...'); + // This command will cause any running android activity to be recreated. + await device.shellExec('cmd', [ + 'overlay', + 'enable', + 'com.android.internal.display.cutout.emulation.tall', + ]); + }, + tearDown: (Device device) async { + if (device is AndroidDevice) { + print('Removing Synthetic notch...'); + await device.shellExec('cmd', [ + 'overlay', + 'disable', + 'com.android.internal.display.cutout.emulation.tall', + ]); + } + }, + ).call; +} + TaskFunction dartDefinesTask() { return DriverTest( '${flutterDirectory.path}/dev/integration_tests/ui', @@ -217,7 +255,6 @@ class DriverTest { ...extraOptions, ]; await flutter('drive', options: options, environment: environment); - return TaskResult.success(null); }); } @@ -231,6 +268,8 @@ class IntegrationTest { this.createPlatforms = const [], this.withTalkBack = false, this.environment, + this.setup, + this.tearDown, }); final String testDirectory; @@ -240,12 +279,19 @@ class IntegrationTest { final bool withTalkBack; final Map? environment; + /// Run before flutter drive with the result from devices.workingDevice. + final Future Function(Device device)? setup; + + /// Run after flutter drive with the result from devices.workingDevice. + final Future Function(Device device)? tearDown; + Future call() { return inDirectory(testDirectory, () async { final Device device = await devices.workingDevice; await device.unlock(); final String deviceId = device.deviceId; await flutter('packages', options: ['get']); + await setup?.call(await devices.workingDevice); if (createPlatforms.isNotEmpty) { await flutter( @@ -265,6 +311,7 @@ class IntegrationTest { final List options = ['-v', '-d', deviceId, testTarget, ...extraOptions]; await flutter('test', options: options, environment: environment); + await tearDown?.call(await devices.workingDevice); if (withTalkBack) { await disableTalkBack(); diff --git a/dev/integration_tests/display_cutout_rotation/README.md b/dev/integration_tests/display_cutout_rotation/README.md index 72ccf694c8..934210c076 100644 --- a/dev/integration_tests/display_cutout_rotation/README.md +++ b/dev/integration_tests/display_cutout_rotation/README.md @@ -1,5 +1,5 @@ # display_cutout_rotation -To run test locally use `flutter drive integration_test/display_cutout_test.dart` from this folder. +To run test locally use `flutter test integration_test/display_cutout_test.dart` from this folder. OR from `flutter/dev/devicelab` run `dart bin/test_runner.dart test -t android_display_cutout`. diff --git a/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart b/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart index cbbc711b6d..a42117faad 100644 --- a/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart +++ b/dev/integration_tests/display_cutout_rotation/integration_test/display_cutout_test.dart @@ -14,7 +14,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('end-to-end test', () { - // Test assumes a custom driver that enables + // Test assumes that the device already has enabled // "com.android.internal.display.cutout.emulation.tall". testWidgets('cutout should be on top in portrait mode', (WidgetTester tester) async { // Force rotation diff --git a/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart b/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart deleted file mode 100644 index 2dea8af05e..0000000000 --- a/dev/integration_tests/display_cutout_rotation/test_driver/display_cutout_test_test.dart +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2014 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. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_driver/flutter_driver.dart'; - -// display_cutout needs a custom driver becuase cutout manipulations needs to be -// done to a device/emulator in order for the tests to pass. -Future main() async { - if (!(Platform.isLinux || Platform.isMacOS)) { - // Not a fundemental limitation, developer shortcut. - print('This test must be run on a POSIX host. Skipping...'); - return; - } - final bool adbExists = Process.runSync('which', ['adb']).exitCode == 0; - if (!adbExists) { - print(r'This test needs ADB to exist on the $PATH.'); - exitCode = 1; - return; - } - // Test requires developer settings added in 28 and behavior added in 30 - final ProcessResult checkApiLevel = Process.runSync('adb', [ - 'shell', - 'getprop', - 'ro.build.version.sdk', - ]); - final String apiStdout = checkApiLevel.stdout.toString(); - // Api level 30 or higher. - if (apiStdout.startsWith('2') || apiStdout.startsWith('1') || apiStdout.length == 1) { - print('This test must be run on api 30 or higher. Skipping...'); - return; - } - // Developer settings are required on target device for cutout manipulation. - bool shouldResetDevSettings = false; - final ProcessResult checkDevSettingsResult = Process.runSync('adb', [ - 'shell', - 'settings', - 'get', - 'global', - 'development_settings_enabled', - ]); - if (checkDevSettingsResult.stdout.toString().startsWith('0')) { - print('Enabling developer settings...'); - // Developer settings not enabled, enable them and mark that the origional - // state should be restored after. - shouldResetDevSettings = true; - Process.runSync('adb', [ - 'shell', - 'settings', - 'put', - 'global', - 'development_settings_enabled', - '1', - ]); - } - // Assumption of diplay_cutout_test.dart is that there is a "tall" notch. - print('Adding Synthetic notch...'); - Process.runSync('adb', [ - 'shell', - 'cmd', - 'overlay', - 'enable', - 'com.android.internal.display.cutout.emulation.tall', - ]); - print('Starting test.'); - try { - final FlutterDriver driver = await FlutterDriver.connect(); - final String data = await driver.requestData(null, timeout: const Duration(minutes: 1)); - await driver.close(); - final Map result = jsonDecode(data) as Map; - print('Test finished!'); - print(result); - exitCode = result['result'] == 'true' ? 0 : 1; - } catch (e) { - print(e); - exitCode = 1; - } finally { - print('Removing Synthetic notch...'); - Process.runSync('adb', [ - 'shell', - 'cmd', - 'overlay', - 'disable', - 'com.android.internal.display.cutout.emulation.tall', - ]); - print('Reverting Adb changes...'); - if (shouldResetDevSettings) { - print('Disabling developer settings...'); - Process.runSync('adb', [ - 'shell', - 'settings', - 'put', - 'global', - 'development_settings_enabled', - '0', - ]); - } - } - return; -}