Add integration test for cutout rotation evaluation (#160354)

Test that the position of a cutout as reported by the Android engine
repositions based on screen orientation.

Related to https://github.com/flutter/engine/pull/55992

Part of https://github.com/flutter/flutter/issues/155658

to test run flutter drive integration_test/display_cutout_test.dart 
from dev/integration_tests/display_cutout_rotation

Pr also force upgrades pub dependencies because I was getting presubmit
failure in version solve.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Reid Baker 2025-01-24 09:50:38 -05:00 committed by GitHub
parent bf7daf22a5
commit 02e11f95a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 796 additions and 0 deletions

View File

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "0dc4eb31df6fe16c1bac10bef3904eb378056c35"
channel: "[user-branch]"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 0dc4eb31df6fe16c1bac10bef3904eb378056c35
base_revision: 0dc4eb31df6fe16c1bac10bef3904eb378056c35
- platform: android
create_revision: 0dc4eb31df6fe16c1bac10bef3904eb378056c35
base_revision: 0dc4eb31df6fe16c1bac10bef3904eb378056c35
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -0,0 +1,3 @@
# display_cutout_rotation
To run test locally use `flutter drive integration_test/display_cutout_test.dart` from this folder.

View File

@ -0,0 +1,5 @@
include: ../../analysis_options.yaml
analyzer:
exclude:
- build/**

View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,43 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.display_cutout_rotation"
compileSdk = flutter.compileSdkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.display_cutout_rotation"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@ -0,0 +1,11 @@
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,49 @@
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="display_cutout_rotation"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,42 @@
// 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.
@file:Suppress("PackageName")
package com.example.display_cutout_rotation
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// https://developer.android.com/training/system-ui
// Set app into fullscreen mode without insets from system bars.
// Matches api 35 default behavior and is required by test which assumes no other inset
// except for a cutout.
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
// The default behavior on SDK level 34 and below is for display cutouts to be consumed
// before the insets would reach the engine. In order to receive the display cutouts in the
// engine, the test app must request that it be allowed to draw its content behind cutouts.
// See
// https://developer.android.com/reference/android/view/WindowManager.LayoutParams#layoutInDisplayCutoutMode
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS was added in api 30 so we need to check api level
// before setting the value. Not setting this value will prevent flutter from drawing in
// cutout areas which our test is explicitly requires.
if (Build.VERSION.SDK_INT >= 30) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
}
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,11 @@
<!-- 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip

View File

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
}
include(":app")

View File

@ -0,0 +1,131 @@
// 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:ui';
import 'package:display_cutout_rotation/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
// Test assumes a custom driver that enables
// "com.android.internal.display.cutout.emulation.tall".
testWidgets('cutout should be on top in portrait mode', (WidgetTester tester) async {
// Force rotation
await setOrientationAndWaitUntilRotation(tester, DeviceOrientation.portraitUp);
// Load app widget.
await tester.pumpWidget(const MyApp());
final BuildContext context = tester.element(find.byType(Text));
final Iterable<DisplayFeature> displayFeatures = getCutouts(tester, context);
// Test is expecting one cutout setup in the test harness.
expect(displayFeatures.length, 1, reason: 'Single cutout display feature expected');
// Verify that app code thinks there is a top cutout.
expect(
displayFeatures.first.bounds.top,
0,
reason:
'cutout should start at the top, does the test device have a '
'camera cutout or window inset?',
);
});
testWidgets('cutout should be on left in landscape left', (WidgetTester tester) async {
await setOrientationAndWaitUntilRotation(tester, DeviceOrientation.landscapeLeft);
// Load app widget.
await tester.pumpWidget(const MyApp());
final BuildContext context = tester.element(find.byType(Text));
// Verify that app code thinks there is a left cutout.
final Iterable<DisplayFeature> displayFeatures = getCutouts(tester, context);
// Test is expecting one cutout setup in the test harness.
expect(displayFeatures.length, 1, reason: 'Single cutout display feature expected');
expect(
displayFeatures.first.bounds.left,
0,
reason:
'cutout should start at the left, does the test device have a '
'camera cutout or window inset?',
);
});
testWidgets('cutout handles rotation', (WidgetTester tester) async {
await setOrientationAndWaitUntilRotation(tester, DeviceOrientation.portraitUp);
const MyApp widgetUnderTest = MyApp();
// Load app widget.
await tester.pumpWidget(widgetUnderTest);
BuildContext context = tester.element(find.byType(Text));
Iterable<DisplayFeature> displayFeatures = getCutouts(tester, context);
// Test is expecting one cutout setup in the test harness.
expect(displayFeatures.length, 1, reason: 'Single cutout display feature expected');
// Verify that app code thinks there is a top cutout.
expect(
displayFeatures.first.bounds.top,
0,
reason:
'cutout should start at the top, does the test device have a '
'camera cutout or window inset?',
);
await setOrientationAndWaitUntilRotation(tester, DeviceOrientation.landscapeLeft);
await tester.pumpWidget(widgetUnderTest);
// Requery for display features after rotation.
context = tester.element(find.byType(Text));
displayFeatures = getCutouts(tester, context);
// Test is expecting one cutout setup in the test harness.
expect(displayFeatures.length, 1, reason: 'Single cutout display feature expected');
expect(
displayFeatures.first.bounds.left,
0,
reason: 'cutout should start at the left or handle camera',
);
});
tearDown(() {
// After each test reset to device perfered orientations to avoid
// test pollution.
SystemChrome.setPreferredOrientations(<DeviceOrientation>[]);
});
});
}
/*
* Force rotation then poll to ensure rotation has happened.
*
* Rotations have an async communication to engine which then has an async
* communication to the android operating system.
*/
Future<void> setOrientationAndWaitUntilRotation(
WidgetTester tester,
DeviceOrientation orientation,
) async {
SystemChrome.setPreferredOrientations(<DeviceOrientation>[orientation]);
Orientation expectedOrientation;
switch (orientation) {
case DeviceOrientation.portraitUp:
case DeviceOrientation.portraitDown:
expectedOrientation = Orientation.portrait;
case DeviceOrientation.landscapeRight:
case DeviceOrientation.landscapeLeft:
expectedOrientation = Orientation.landscape;
}
while (true) {
final BuildContext context = tester.element(find.byType(Text));
if (expectedOrientation == MediaQuery.of(context).orientation) {
break;
}
await tester.pumpAndSettle();
}
}
Iterable<DisplayFeature> getCutouts(WidgetTester tester, BuildContext context) {
final List<DisplayFeature> displayFeatures = MediaQuery.of(context).displayFeatures;
return displayFeatures.where(
(DisplayFeature feature) => feature.type == DisplayFeatureType.cutout,
);
}

View File

@ -0,0 +1,50 @@
// 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:ui';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
final class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
final List<DisplayFeature> displayFeatures = MediaQuery.of(context).displayFeatures;
displayFeatures.retainWhere(
(DisplayFeature feature) => feature.type == DisplayFeatureType.cutout,
);
String text;
// None of this complexity is required for the test but it helps when
// visually debugging or watching a video of a remote device.
if (displayFeatures.isEmpty) {
text = 'CutoutNone';
} else if (displayFeatures.length > 1) {
text = 'CutoutMany';
} else {
final Rect cutout = displayFeatures[0].bounds;
if (cutout.top == 0) {
text = 'CutoutTop';
} else if (cutout.left == 0) {
text = 'CutoutLeft';
} else {
text = 'CutoutNeither';
}
}
// Tests assume there is some text element displayed.
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Text('Cutout status: $text', key: Key(text)),
);
}
}

View File

@ -0,0 +1,123 @@
name: display_cutout_rotation
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.7.0-0
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: 1.0.8
characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
dev_dependencies:
flutter_driver:
sdk: flutter
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: 5.0.0
integration_test:
sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
fake_async: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker: 10.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
lints: 5.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
webdriver: 3.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
# PUBSPEC CHECKSUM: 7b61

View File

@ -0,0 +1,106 @@
// 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.
// ignore_for_file: avoid_print
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<void> 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', <String>['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', <String>[
'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', <String>[
'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', <String>[
'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', <String>[
'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<String, dynamic> result = jsonDecode(data) as Map<String, dynamic>;
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', <String>[
'shell',
'cmd',
'overlay',
'disable',
'com.android.internal.display.cutout.emulation.tall',
]);
print('Reverting Adb changes...');
if (shouldResetDevSettings) {
print('Disabling developer settings...');
Process.runSync('adb', <String>[
'shell',
'settings',
'put',
'global',
'development_settings_enabled',
'0',
]);
}
}
return;
}